//----------------------------------------------------------------------------- // // // File: aqinst.cpp // // Description: Implementation of the Advanced Queueing Server Instance // // Author: mikeswa // // Copyright (C) 1997 Microsoft Corporation // //----------------------------------------------------------------------------- #include #include "dcontext.h" #include "connmgr.h" #include #include #include "dsnevent.h" #include "asyncq.inl" #include "aqutil.h" #include "smtpconn.h" #include "aqrpcsvr.h" #include "aqsize.h" #include "propstrm.h" #include "tran_evntlog.h" #include "asyncadm.inl" #define PRELOCAL_QUEUE_ID 0x00000001 #define PRECAT_QUEUE_ID 0x00000002 #define PREROUTING_QUEUE_ID 0x00000004 #define PRESUBMIT_QUEUE_ID 0x00000008 // externs defined in blockmgr and cmmprops to control debug code DWORD g_fFillPropertyPages = 0; DWORD g_fValidateSignatures = 0; DWORD g_fForceCrashOnError = 0; HRESULT MailTransport_Completion_SubmitMessage(HRESULT hrStatus, PVOID pvContext); HRESULT MailTransport_Completion_PreCategorization(HRESULT hrStatus, PVOID pvContext); HRESULT MailTransport_Completion_PostCategorization(HRESULT hrStatus, PVOID pvContext); const CLSID CLSID_ExchangeStoreDriver = {0x7BD80399,0xE37E,0x11d1,{0x9B,0xE2,0x00,0xA0,0xC9,0x5E,0x61,0x43}}; CPool CAQSvrInst::CAQLocalDeliveryNotify::s_pool(AQLD_SIG); //---[ CAQSvrInst::fShouldRetryMessage ]--------------------------------------- // // // Description: // Attempts to determine if the message has hit a hard failure (like the // backing store has been deleted). This uses GetBinding to determine // The error returned by the store driver. if it is FILE_NOT_FOUND, // then the backing store for the message has been deleted... or is no // longer valid (i.e. - the store restarting). // Parameters: // pIMailMsgProperties // fShouldBounceUsageIfRetry TRUE - Should bounce usage on retry // FALSE - Never bounce usage // If the message is alreade associated with a msgref, this // should always be FALSE since bouncing the usage count // is done through the CMsgRef. // Returns: // TRUE If we think we should retry the message // FALSE If new *know* that the message should be dropped. If unsure, // we will return TRUE. // History: // 1/4/2000 - MikeSwa Created // 4/10/2000 - MikeSwa Modified to better detect store shutdown/failure // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fShouldRetryMessage(IMailMsgProperties *pIMailMsgProperties, BOOL fShouldBounceUsageIfRetry) { TraceFunctEnterEx((LPARAM) pIMailMsgProperties, "fShouldRetryMessage"); BOOL fShouldRetry = TRUE; BOOL fHasShutdownLock = FALSE; HRESULT hr = S_OK; IMailMsgQueueMgmt *pIMailMsgQueueMgmt = NULL; IMailMsgValidateContext *pIMailMsgValidateContext = NULL; _ASSERT(pIMailMsgProperties); if (!fTryShutdownLock()) goto Exit; fHasShutdownLock = TRUE; // // First check and see if the message context is still OK - if that // doesn't work, we use the HrValidateMessageConteNt call below // and force a RFC822 rendering of the message (which can be a // huge perf hit). // // QI for validation interface hr = pIMailMsgProperties->QueryInterface( IID_IMailMsgValidateContext, (LPVOID *)&pIMailMsgValidateContext); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to QI for IMailMsgValidateContext 0x%08X",hr); goto Exit; } // Validate the message context hr = pIMailMsgValidateContext->ValidateContext(); DebugTrace((LPARAM) this, "ValidateContext returned 0x%08X", hr); if (hr == S_OK) //this message is fine goto Exit; else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { fShouldRetry = FALSE; goto Exit; } // // If the above didn't work... try harder by verifying content. This // will open the handles... so we need to close them // hr = HrValidateMessageContent(pIMailMsgProperties); if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { //The mailmsg has been deleted... we can just drop it. DebugTrace((LPARAM) pIMailMsgProperties, "WARNING: Backing store for mailmsg has been deleted."); fShouldRetry = FALSE; goto Exit; } else if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "GetBinding failed with hr - 0x%08X", hr); goto Exit; } Exit: // // Bounce usage count if we have are sticking it back in the queue // and the caller does not object // if (fShouldRetry && fShouldBounceUsageIfRetry) { hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgQueueMgmt, (void **) &pIMailMsgQueueMgmt); if (SUCCEEDED(hr) && pIMailMsgQueueMgmt) { pIMailMsgQueueMgmt->ReleaseUsage(); pIMailMsgQueueMgmt->AddUsage(); pIMailMsgQueueMgmt->Release(); } } if (pIMailMsgValidateContext) pIMailMsgValidateContext->Release(); if (fHasShutdownLock) ShutdownUnlock(); TraceFunctLeave(); return fShouldRetry; } //thin wrapper for CAQSvrInst::fPreCatQueueCompletion member BOOL fPreCatQueueCompletionWrapper(IMailMsgProperties *pIMailMsgProperties, PVOID pvContext) { // Testing : Cause intermittent failures in this queue if (fShouldFail(g_cPreCatQueueFailurePercent)) { ((CAQSvrInst *)pvContext)->ScheduleInternalRetry(LI_TYPE_PENDING_CAT); return FALSE; } if (!((CAQSvrInst *)pvContext)->fPreCatQueueCompletion(pIMailMsgProperties)) return !((CAQSvrInst *)pvContext)->fShouldRetryMessage(pIMailMsgProperties); else return TRUE; } //thin wrapper for CAQSvrInst::fPreLocalDeliveryCompletion BOOL fPreLocalDeliveryQueueCompletionWrapper(CMsgRef *pmsgref, PVOID pvContext) { if (!((CAQSvrInst *)pvContext)->fPreLocalDeliveryQueueCompletion(pmsgref)) return !(pmsgref->fShouldRetry()); else return TRUE; } //thin wrapper for CAQSvrInst::fPostDSNQueueCompletion member BOOL fPostDSNQueueCompletionWrapper(IMailMsgProperties *pIMailMsgProperties, PVOID pvContext) { return (SUCCEEDED(((CAQSvrInst *)pvContext)->HrInternalSubmitMessage(pIMailMsgProperties))); } //thin wrapper for CAQSvrInst::fPreRoutingQueueCompletion BOOL fPreRoutingQueueCompletionWrapper(IMailMsgProperties *pIMailMsgProperties, PVOID pvContext) { // Testing : Cause intermittent failures in this queue if (fShouldFail(g_cPreRoutingQueueFailurePercent)) { ((CAQSvrInst *)pvContext)->ScheduleInternalRetry(LI_TYPE_PENDING_ROUTING); return FALSE; } if (!((CAQSvrInst *)pvContext)->fRouteAndQueueMsg(pIMailMsgProperties)) return !((CAQSvrInst *)pvContext)->fShouldRetryMessage(pIMailMsgProperties); else return TRUE; } //thin wrappers for handling internal asyncq queue failures BOOL fAsyncQHandleFailedMailMsg(IMailMsgProperties *pIMailMsgProperties, PVOID pvContext) { ((CAQSvrInst *)pvContext)->HandleAQFailure(AQ_FAILURE_INTERNAL_ASYNCQ, E_OUTOFMEMORY, pIMailMsgProperties); return TRUE; } BOOL fAsyncQHandleFailedMsgRef(CMsgRef *pmsgref, PVOID pvContext) { _ASSERT(pmsgref); if (pmsgref) pmsgref->RetryOnDelete(); return TRUE; } //Thin wrapper(s) for AsyncQueueRetry - kick starting queues void LocalDeliveryRetry(PVOID pvContext) { ((CAQSvrInst *)pvContext)->AsyncQueueRetry(PRELOCAL_QUEUE_ID); } void CatRetry(PVOID pvContext) { ((CAQSvrInst *)pvContext)->AsyncQueueRetry(PRECAT_QUEUE_ID); } void RoutingRetry(PVOID pvContext) { ((CAQSvrInst *)pvContext)->AsyncQueueRetry(PREROUTING_QUEUE_ID); } void SubmitRetry(PVOID pvContext) { ((CAQSvrInst *)pvContext)->AsyncQueueRetry(PRESUBMIT_QUEUE_ID); } //---[ CAQSvrInst::fPreSubmissionQueueCompletionWrapper ]---------------------- // // // Description: // Completion function for PreSubmit Queue // Parameters: // pIMailMsgPropeties IMailMsg to submit // pvContext Ptr to CAQSvrInst // Returns: // TRUE completed successfully // FALSE message needs to be retried // History: // 10/8/1999 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fPreSubmissionQueueCompletionWrapper( IMailMsgProperties *pIMailMsgProperties, PVOID pvContext) { BOOL fRetry = FALSE; HRESULT hr = S_OK; CAQSvrInst *paqinst = (CAQSvrInst *)pvContext; _ASSERT(paqinst); // Testing : Cause intermittent failures in this queue if (fShouldFail(g_cPreSubmitQueueFailurePercent)) { paqinst->ScheduleInternalRetry(LI_TYPE_PENDING_SUBMIT); return FALSE; } InterlockedDecrement((PLONG) &(paqinst->m_cCurrentMsgsPendingSubmit)); hr = (paqinst->HrInternalSubmitMessage(pIMailMsgProperties)); if (FAILED(hr)) { if (paqinst->fShouldRetryMessage(pIMailMsgProperties)) { fRetry = TRUE; InterlockedIncrement((PLONG) &(paqinst->m_cCurrentMsgsPendingSubmit)); // // We need to kick off a retry as well for the presubmit queue // paqinst->ScheduleInternalRetry(LI_TYPE_PENDING_SUBMIT); } } SleepForPerfAnalysis(g_dwSubmitQueueSleepMilliseconds); return (!fRetry); } DEBUG_DO_IT(CAQSvrInst *g_paqinstLastDeleted = NULL;); //used to find the last deleted in CDB #define TRACE_COUNTERS \ {\ DebugTrace(0xC0DEC0DE, "INFO: %d msgs pending submission event for server 0x%08X", m_cCurrentMsgsPendingSubmitEvent, this); \ DebugTrace(0xC0DEC0DE, "INFO: %d msgs pending pre-cat event for server 0x%08X", m_cCurrentMsgsPendingPreCatEvent, this); \ DebugTrace(0xC0DEC0DE, "INFO: %d msgs pending post-cat event for server 0x%08X", m_cCurrentMsgsPendingPostCatEvent, this); \ DebugTrace(0xC0DEC0DE, "INFO: %d msgs submited (total post cat) for delivery on server 0x%08X", m_cTotalMsgsQueued, this);\ DebugTrace(0xC0DEC0DE, "INFO: %d msgs pending categorization for server 0x%08X", m_cCurrentMsgsPendingCat, this);\ DebugTrace(0xC0DEC0DE, "INFO: %d msgs ack'd on server 0x%08X", m_cMsgsAcked, this);\ DebugTrace(0xC0DEC0DE, "INFO: %d msgs ack'd for retry on server 0x%08X", m_cMsgsAckedRetry, this);\ DebugTrace(0xC0DEC0DE, "INFO: %d msgs delivered local on server 0x%08X", m_cMsgsDeliveredLocal, this);\ } // {407525AC-62B5-11d2-A694-00C04FA3490A} static const GUID g_guidDefaultRouter = { 0x407525ac, 0x62b5, 0x11d2, { 0xa6, 0x94, 0x0, 0xc0, 0x4f, 0xa3, 0x49, 0xa } }; //GUID for local queue // {34E2DCCC-C91A-11d2-A6B1-00C04FA3490A} static const GUID g_guidLocalQueue = { 0x34e2dccc, 0xc91a, 0x11d2, { 0xa6, 0xb1, 0x0, 0xc0, 0x4f, 0xa3, 0x49, 0xa } }; // GUID for presubmission queue // {D99AAC44-BEE9-4f9f-8D47-FB12E1443B9A} static const GUID g_guidPreSubmissionQueue = { 0xd99aac44, 0xbee9, 0x4f9f, { 0x8d, 0x47, 0xfb, 0x12, 0xe1, 0x44, 0x3b, 0x9a } }; // GUID for precat queue // {B608067E-85DB-4f4e-9FE9-008A4072CCDC} static const GUID g_guidPreCatQueue = { 0xb608067e, 0x85db, 0x4f4e, { 0x9f, 0xe9, 0x0, 0x8a, 0x40, 0x72, 0xcc, 0xdc } }; // GUID for prerouting queue // {F1B4C8FD-2928-427d-AC0D-23AF0DCFC31F} static const GUID g_guidPreRoutingQueue = { 0xf1b4c8fd, 0x2928, 0x427d, { 0xac, 0xd, 0x23, 0xaf, 0xd, 0xcf, 0xc3, 0x1f } }; // GUID for post-DSN generation queue // {D076B629-6030-405f-ADC9-D888703E072E} static const GUID g_guidPostDSNGenerationQueue = { 0xd076b629, 0x6030, 0x405f, { 0xad, 0xc9, 0xd8, 0x88, 0x70, 0x3e, 0x7, 0x2e } }; //---[ CAQSvrInst::CAQSvrInst ]------------------------------------------------ // // // Description: // Class constuctor // Parameters: // SMTP_SERVER_INSTANCE *pssi - ptr to SMTP server instance object // pISMTPServer - interface used to handle local deliverys // Returns: // - // //----------------------------------------------------------------------------- CAQSvrInst::CAQSvrInst(DWORD dwServerInstance, ISMTPServer *pISMTPServer) : m_mglSupersedeIDs(&m_cSupersededMsgs), #if _MSC_VER >= 1200 #pragma warning(push) #endif #pragma warning(disable:4355) m_asyncqPreLocalDeliveryQueue("LocalAsyncQueue", LOCAL_LINK_NAME, &g_guidLocalQueue, 0, this), m_asyncqPreSubmissionQueue("PreSubmissionQueue", PRESUBMISSION_QUEUE_NAME, &g_guidPreSubmissionQueue, 0, this), m_asyncqPreCatQueue("PreCatQueue", PRECAT_QUEUE_NAME, &g_guidPreCatQueue, 0, this), m_asyncqPreRoutingQueue("PreRoutingQueue", PREROUTING_QUEUE_NAME, &g_guidPreRoutingQueue, 0, this), m_asyncqPostDSNQueue("PostDSNGenerationQueue", POSTDSN_QUEUE_NAME, &g_guidPostDSNGenerationQueue, 0, this), m_dsnsink((IUnknown *)(IAQServerEvent *)this), #if _MSC_VER >= 1200 #pragma warning(pop) #else #pragma warning(default:4355) #endif m_slPrivateData("CAQSvrInst", SHARE_LOCK_INST_TRACK_DEFAULTS | SHARE_LOCK_INST_TRACK_SHARED_THREADS | SHARE_LOCK_INST_TRACK_CONTENTION, 500) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::CAQSvrInst"); _ASSERT(pISMTPServer); m_dwSignature = CATMSGQ_SIG; m_dwFlavorSignature = g_dwFlavorSignature; m_cbClasses = g_cbClasses; //Init counters m_cTotalMsgsQueued = 0; //# of messages on dest queues (after fanout) m_cMsgsAcked = 0; //# of messages that have been acknowledged m_cMsgsAckedRetry = 0; //# of messages acked with retry all m_cMsgsDeliveredLocal= 0; //# of messages delivered to local store m_cCurrentMsgsPendingSubmitEvent = 0; //current # of messages in //submission event m_cCurrentMsgsPendingPreCatEvent = 0; // current # of messages in // precat event m_cCurrentMsgsPendingPostCatEvent = 0; //current # of messages in //post-categorization event m_cCurrentMsgsSubmitted = 0; //# total msgs in system m_cCurrentMsgsPendingCat = 0; //# Msgs that have not be categorized m_cCurrentMsgsPendingRouting = 0; //# Msgs that have been cat. //but have not been completely queued m_cCurrentMsgsPendingDelivery = 0; //# Msgs pending remote delivery m_cCurrentMsgsPendingLocal = 0; //# Msgs pending local delivery m_cCurrentMsgsPendingRetry = 0; //# Msgs with unsuccessful attempts m_cCurrentQueueMsgInstances = 0; //# of msgs instances pending //remote deliver (>= #msgs) m_cCurrentRemoteDestQueues = 0; //# of DestMsgQueues created m_cCurrentRemoteNextHops = 0; //# of Next Hop links created m_cCurrentRemoteNextHopsEnabled = 0; //# of links that can have connections m_cCurrentRemoteNextHopsPendingRetry = 0; //# of links pending retry m_cCurrentRemoteNextHopsPendingSchedule = 0; //# of links pending schedule m_cCurrentRemoteNextHopsFrozenByAdmin = 0; //# of links frozen by admin m_cTotalMsgsSubmitted = 0; //total # of messages submitted to AQ m_cTotalExternalMsgsSubmitted = 0; //Sumitted via an external interface m_cMsgsAckedRetryLocal = 0; m_cCurrentMsgsPendingLocalRetry = 0; m_cDMTRetries = 0; m_cTotalMsgsTURNETRNDelivered = 0; m_cCurrentMsgsPendingDeferredDelivery = 0; m_cCurrentResourceFailedMsgsPendingRetry = 0; m_cTotalMsgsBadmailed = 0; m_cBadmailNoRecipients = 0; m_cBadmailHopCountExceeded = 0; m_cBadmailFailureGeneral = 0; m_cBadmailBadPickupFile = 0; m_cBadmailEvent = 0; m_cBadmailNdrOfDsn = 0; m_cTotalDSNFailures = 0; m_cCurrentMsgsInLocalDelivery = 0; m_cTotalResetRoutes = 0; m_cCurrentPendingResetRoutes = 0; m_cCurrentMsgsPendingSubmit = 0; //Counters to keep track of the number of messages in Cat m_cCatMsgCalled = 0; m_cCatCompletionCalled = 0; //DSN Related counters m_cDelayedDSNs = 0; m_cNDRs = 0; m_cDeliveredDSNs = 0; m_cRelayedDSNs = 0; m_cExpandedDSNs = 0; m_cSupersededMsgs = 0; //number of messages superseded m_dwDelayExpireMinutes = g_dwDelayExpireMinutes; m_dwNDRExpireMinutes = g_dwNDRExpireMinutes; m_dwLocalDelayExpireMinutes = g_dwDelayExpireMinutes; m_dwLocalNDRExpireMinutes = g_dwNDRExpireMinutes; m_dwInitMask = 0; m_prstrDefaultDomain = NULL; m_prstrBadMailDir = NULL; m_prstrCopyNDRTo = NULL; m_prstrServerFQDN = NULL; m_dwDSNLanguageID = 0; m_dwDSNOptions = DSN_OPTIONS_DEFAULT; if (pISMTPServer) pISMTPServer->AddRef(); m_pISMTPServer = pISMTPServer; // Get the ISMTPServerEx interface { HRESULT hr; m_pISMTPServerEx = NULL; hr = m_pISMTPServer->QueryInterface( IID_ISMTPServerEx, (LPVOID *)&m_pISMTPServerEx); if (FAILED(hr)) { ErrorTrace((LPARAM) m_pISMTPServer, "Unable to QI for ISMTPServerEx 0x%08X",hr); m_pISMTPServerEx = NULL; } m_pISMTPServerAsync = NULL; hr = m_pISMTPServer->QueryInterface( IID_ISMTPServerAsync, (LPVOID *)&m_pISMTPServerAsync); if (FAILED(hr)) { ErrorTrace((LPARAM) m_pISMTPServer, "Unable to QI for ISMTPServerAsync 0x%08X",hr); m_pISMTPServerAsync = NULL; } } m_hCat = INVALID_HANDLE_VALUE; m_dwServerInstance = dwServerInstance; m_pConnMgr = NULL; m_pIMessageRouterDefault = NULL; //Retry stuff m_dwFirstTierRetrySeconds = g_dwFirstTierRetrySeconds; m_cLocalRetriesPending = 0; //used for moderating local retries m_cCatRetriesPending = 0; //used for moderating cat retires m_cRoutingRetriesPending = 0; //used for moderating routing retries m_cSubmitRetriesPending = 0; //used for moderating submit retries m_pIRouterReset = NULL; //Add to global list of virtual servers m_liVirtualServers.Blink = &g_liVirtualServers; g_pslGlobals->ExclusiveLock(); m_liVirtualServers.Flink = g_liVirtualServers.Flink; g_liVirtualServers.Flink->Blink = &m_liVirtualServers; g_liVirtualServers.Flink = &m_liVirtualServers; g_pslGlobals->ExclusiveUnlock(); // // Assume (until proven otherwise) mailmsg returns the handle count // m_fMailMsgReportsNumHandles = TRUE; m_defq.Initialize(this); TraceFunctLeave(); } //---[ CAQSvrInst::~CAQSvrInst ]-------------------------------------------- // // // Description: // Class destuctor // Parameters: // - // Returns: // - // //----------------------------------------------------------------------------- CAQSvrInst::~CAQSvrInst() { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::~CAQSvrInst"); //make sure that all cleanup was done HrDeinitialize(); //can be called multiple times if (m_pISMTPServer) m_pISMTPServer->Release(); if (m_pISMTPServerEx) m_pISMTPServerEx->Release(); if (m_pISMTPServerAsync) m_pISMTPServerAsync->Release(); if (m_pConnMgr) { m_pConnMgr->Release(); m_pConnMgr = NULL; } if (m_prstrDefaultDomain) m_prstrDefaultDomain->Release(); if (m_prstrBadMailDir) m_prstrBadMailDir->Release(); if (m_prstrCopyNDRTo) m_prstrCopyNDRTo->Release(); if (m_prstrServerFQDN) m_prstrServerFQDN->Release(); //Take out of list of global list g_pslGlobals->ExclusiveLock(); m_liVirtualServers.Flink->Blink = m_liVirtualServers.Blink; m_liVirtualServers.Blink->Flink = m_liVirtualServers.Flink; g_pslGlobals->ExclusiveUnlock(); m_liVirtualServers.Flink = NULL; m_liVirtualServers.Flink = NULL; MARK_SIG_AS_DELETED(m_dwSignature); DEBUG_DO_IT(g_paqinstLastDeleted = this;); TraceFunctLeave(); } //---[ CAQSvrInst::HrInitialize ]-------------------------------------------- // // // Description: // Initialization of CAQSvrInst virtual server instance object. // Parameters: // IN szUserName User name to log on DS with // IN szDomainName Domain name to log on to DS with // IN szPassword Password to authenticate to DS with // IN pServiceStatusFn Server status callback function // IN pvServiceContext Context to pass back for callback function // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrInitialize( IN LPSTR szUserName, IN LPSTR szDomainName, IN LPSTR szPassword, IN PSRVFN pServiceStatusFn, IN PVOID pvServiceContext) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrInitialize"); HRESULT hr = S_OK; IMailTransportSetRouterReset *pISetRouterReset = NULL; // // init SEO // hr = m_CSMTPSeoMgr.HrInit(m_dwServerInstance); if (FAILED(hr)) { goto Exit; } m_pIMessageRouterDefault = new CAQDefaultMessageRouter( (GUID *) &g_guidDefaultRouter, this); if (!m_pIMessageRouterDefault) { hr = E_OUTOFMEMORY; goto Exit; } m_fmq.Initialize(this); hr = CAQRpcSvrInst::HrInitializeAQServerInstanceRPC(this, m_dwServerInstance, m_pISMTPServer); if (FAILED(hr)) goto Exit; //Initialize Message Categorization hr = CatInit( NULL, pServiceStatusFn, pvServiceContext, m_pISMTPServer, (IAdvQueueDomainType *) this, m_dwServerInstance, &m_hCat); if (FAILED(hr)) { m_hCat = INVALID_HANDLE_VALUE; goto Exit; } //Pass RouterReset Interface to ISMTPServer if (m_pISMTPServer) { hr = m_pISMTPServer->QueryInterface(IID_IMailTransportSetRouterReset, (void **) &pISetRouterReset); if (SUCCEEDED(hr)) { m_dwInitMask |= CMQ_INIT_ROUTER_RESET; _ASSERT(pISetRouterReset); hr = pISetRouterReset->RegisterResetInterface(m_dwServerInstance, (IMailTransportRouterReset *) this); _ASSERT(SUCCEEDED(hr)); //something is wrong if this failed pISetRouterReset->Release(); pISetRouterReset = NULL; } hr = m_pISMTPServer->QueryInterface(IID_IMailTransportRouterReset, (void **) &m_pIRouterReset); if (FAILED(hr)) m_pIRouterReset = NULL; } hr = m_dmt.HrInitialize(this, &m_asyncqPreLocalDeliveryQueue, &m_asyncqPreCatQueue, &m_asyncqPreRoutingQueue, &m_asyncqPreSubmissionQueue); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_DMT; hr = m_dct.HrInit(); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_DCT; m_pConnMgr = new CConnMgr; if (NULL == m_pConnMgr) { hr = E_OUTOFMEMORY; goto Exit; } hr = m_pConnMgr->HrInitialize(this); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_CONMGR; hr = m_dsnsink.HrInitialize(); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_DSN; hr = m_asyncqPreCatQueue.HrInitialize(g_cMaxSyncCatQThreads, g_cItemsPerCatQAsyncThread, g_cItemsPerCatQSyncThread, this, fPreCatQueueCompletionWrapper, fAsyncQHandleFailedMailMsg, NULL, g_cMaxPendingCat); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_PRECATQ; hr = m_asyncqPreLocalDeliveryQueue.HrInitialize(g_cMaxSyncLocalQThreads, g_cItemsPerLocalQAsyncThread, g_cItemsPerLocalQSyncThread, this, fPreLocalDeliveryQueueCompletionWrapper, fAsyncQHandleFailedMsgRef, HrWalkPreLocalQueueForDSN, g_cMaxPendingLocal); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_PRELOCQ; hr = m_asyncqPostDSNQueue.HrInitialize(g_cMaxSyncPostDSNQThreads, g_cItemsPerPostDSNQAsyncThread, g_cItemsPerPostDSNQSyncThread, this, fPostDSNQueueCompletionWrapper, fAsyncQHandleFailedMailMsg, NULL); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_POSTDSNQ; hr = m_asyncqPreRoutingQueue.HrInitialize(g_cMaxSyncRoutingQThreads, g_cItemsPerRoutingQAsyncThread, g_cItemsPerRoutingQSyncThread, this, fPreRoutingQueueCompletionWrapper, fAsyncQHandleFailedMailMsg, NULL); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_ROUTINGQ; hr = m_asyncqPreSubmissionQueue.HrInitialize(g_cMaxSyncSubmitQThreads, g_cItemsPerSubmitQAsyncThread, g_cItemsPerSubmitQSyncThread, this, fPreSubmissionQueueCompletionWrapper, fAsyncQHandleFailedMailMsg, NULL); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_SUBMISSIONQ; hr = m_aqwWorkQueue.HrInitialize(g_cItemsPerWorkQAsyncThread); if (FAILED(hr)) goto Exit; m_dwInitMask |= CMQ_INIT_WORKQ; m_dwInitMask |= CMQ_INIT_OK; //everything was initialized // create the router object hr = HrTriggerInitRouter(); Exit: TraceFunctLeave(); return hr; } //---[ CAQSvrInst::HrDeinitialize() ]---------------------------------------- // // // Description: // Signals server shutdown // Parameters: // - // Returns: // S_OK on success // Whatever error codes are generated during the shutdown process // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrDeinitialize"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; DWORD i = 0; IMailTransportSetRouterReset *pISetRouterReset = NULL; #ifdef DEBUG DWORD cLocalDeliveryShutdownSeconds = 60; #endif // // Tell categorizer to stop categorizing before we block on the // shutdown lock. // if (INVALID_HANDLE_VALUE != m_hCat) { CatPrepareForShutdown(m_hCat); } // // We have hit this assert a few times due to NT stress failures. // This is only really useful if at least one messsage has been // sent. The assert is basically useless if we hit it in KD during // NT stress runs. // if (m_cTotalMsgsSubmitted) m_dbgcnt.StartCountdown(); ServerStopHintFunction(); //Get Exclusive shutdown lock SignalShutdown(); ServerStopHintFunction(); //Turn off RPC for this instance hrTmp = CAQRpcSvrInst::HrDeinitializeAQServerInstanceRPC(this, m_dwServerInstance); if (FAILED(hrTmp)) { ErrorTrace((LPARAM) this, "Error shutting down Aqueue RPC hr - 0x%08X", hrTmp); if (SUCCEEDED(hr)) hr = hrTmp; } // wait for all outstanding local deliveries to complete while (!(m_asyncqPreLocalDeliveryQueue.fNoPendingAsyncCompletions())) { _ASSERT(--cLocalDeliveryShutdownSeconds > 0); ServerStopHintFunction(); Sleep(1000); } ServerStopHintFunction(); m_fmq.Deinitialize(); ServerStopHintFunction(); m_defq.Deinitialize(); m_dwInitMask &= ~CMQ_INIT_DCT; //no de-initialize function to call //stop any pending categorization ServerStopHintFunction(); if (INVALID_HANDLE_VALUE != m_hCat) { m_dbgcnt.SuspendCountdown(); hrTmp = CatCancel(m_hCat); if FAILED(hrTmp) { ErrorTrace((LPARAM) this, "ERROR: Categorization shutdown error hr - 0x%08X", hrTmp); if (SUCCEEDED(hr)) hr = hrTmp; } //shutdow message categorization CatTerm(m_hCat); m_dbgcnt.ResetCountdown(); m_hCat = INVALID_HANDLE_VALUE; } //Tell ISMTPServer to set RouterReset Interface to NULL ServerStopHintFunction(); if (m_pISMTPServer) { hr = m_pISMTPServer->QueryInterface(IID_IMailTransportSetRouterReset, (void **) &pISetRouterReset); if (SUCCEEDED(hr)) { m_dwInitMask &= ~CMQ_INIT_ROUTER_RESET; _ASSERT(pISetRouterReset); hr = pISetRouterReset->RegisterResetInterface(m_dwServerInstance, NULL); _ASSERT(SUCCEEDED(hr)); //something is wrong if this failed pISetRouterReset->Release(); pISetRouterReset = NULL; } } ServerStopHintFunction(); if (m_pIRouterReset) { m_pIRouterReset->Release(); m_pIRouterReset = NULL; } ServerStopHintFunction(); if (CMQ_INIT_DMT & m_dwInitMask) { m_dwInitMask ^= CMQ_INIT_DMT; hrTmp = m_dmt.HrDeinitialize(); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; } //Deinitializing the connection manager will also release the retry //sink to make all it's callbacks. ServerStopHintFunction(); if (NULL != m_pConnMgr) { if (CMQ_INIT_CONMGR & m_dwInitMask) { _ASSERT(m_pISMTPServer); m_dwInitMask ^= CMQ_INIT_CONMGR; hrTmp = m_pConnMgr->HrDeinitialize(); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; } m_pConnMgr->Release(); m_pConnMgr = NULL; } //deinitialize pre-local delivery queue ServerStopHintFunction(); if (CMQ_INIT_PRELOCQ & m_dwInitMask) { hrTmp = m_asyncqPreLocalDeliveryQueue.HrDeinitialize( HrWalkMsgRefQueueForShutdown, this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_PRELOCQ; } //deinitialize pre-cat delivery queue ServerStopHintFunction(); if (CMQ_INIT_PRECATQ & m_dwInitMask) { hrTmp = m_asyncqPreCatQueue.HrDeinitialize( HrWalkMailMsgQueueForShutdown, this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_PRECATQ; } //deinitialize post DNS queue ServerStopHintFunction(); if (CMQ_INIT_POSTDSNQ & m_dwInitMask) { hrTmp = m_asyncqPostDSNQueue.HrDeinitialize( HrWalkMailMsgQueueForShutdown, this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_POSTDSNQ; } //deinitialize pre-routing queue ServerStopHintFunction(); if (CMQ_INIT_ROUTINGQ & m_dwInitMask) { hrTmp = m_asyncqPreRoutingQueue.HrDeinitialize( HrWalkMailMsgQueueForShutdown, this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_ROUTINGQ; } //deinitialize pre-submit queue ServerStopHintFunction(); if (CMQ_INIT_SUBMISSIONQ & m_dwInitMask) { hrTmp = m_asyncqPreSubmissionQueue.HrDeinitialize( HrWalkMailMsgQueueForShutdown, this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_SUBMISSIONQ; } ServerStopHintFunction(); m_mglSupersedeIDs.Deinitialize(this); ServerStopHintFunction(); if (CMQ_INIT_WORKQ & m_dwInitMask) { hrTmp = m_aqwWorkQueue.HrDeinitialize(this); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; m_dwInitMask ^= CMQ_INIT_WORKQ; } //the following bits don't have specific delinitialize functions m_dwInitMask &= ~(CMQ_INIT_DSN | CMQ_INIT_OK); ServerStopHintFunction(); if (m_pIMessageRouterDefault) { m_pIMessageRouterDefault->Release(); m_pIMessageRouterDefault = NULL; } // let SEO do it's cleanup m_CSMTPSeoMgr.Deinit(); m_dbgcnt.EndCountdown(); _ASSERT((!m_dwInitMask) || FAILED(hr)); TraceFunctLeave(); return hr; } //---[ CAQSvrInst::HrGetIConnectionManager ]--------------------------------- // // // Description: // Returns the IConnectionManager interface for this AdvancedQueuing instance // Parameters: // OUT ppIConnectionManger Returned interface // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrGetIConnectionManager( OUT IConnectionManager **ppIConnectionManager) { HRESULT hr = S_OK; _ASSERT(ppIConnectionManager); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } m_pConnMgr->AddRef(); *ppIConnectionManager = m_pConnMgr; ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::cCountMsgsForHandleThrottling ]----------------------------- // // // Description: // returns the number of messages in the system that // is used elsewhere to decide to start/stop handle throttling // // // Parameters: // pIMailMsgProperties pointer to MailMsg interface to query // // Returns: // DWORD returned is: // // - With Windows2000 RTM mailmsg.dll: // count of all messages in the system // // - With Windows2000 SP1 mailmsg.dll: // count of open property stream handles // OR // count of all messages in the system // if the former can't be obtained // // History: // 1/28/00 aszafer Created //----------------------------------------------------------------------------- #ifndef IMMPID_MPV_TOTAL_OPEN_PROPERTY_STREAM_HANDLES #define IMMPID_MPV_TOTAL_OPEN_PROPERTY_STREAM_HANDLES 0x3004 #endif DWORD CAQSvrInst::cCountMsgsForHandleThrottling(IN IMailMsgProperties *pIMailMsgProperties) { HRESULT hr = MAILMSG_E_PROPNOTFOUND; //Count as failure if we do not call DWORD dwStreamOpenHandlesCount = 0; TraceFunctEnterEx((LPARAM) this, "Entering CAQSvrInst::cCountMsgsForHandleThrottling"); // // We should never call into mailmsg if we know we do not have the correct version. // This will load the property stream and cause us to potentially access the // properties in an unsafe way. // if (m_fMailMsgReportsNumHandles && pIMailMsgProperties) { hr = pIMailMsgProperties->GetDWORD( IMMPID_MPV_TOTAL_OPEN_PROPERTY_STREAM_HANDLES, &dwStreamOpenHandlesCount); if(FAILED(hr)) { m_fMailMsgReportsNumHandles = FALSE; //must be RTM version of mailmsg.dll DebugTrace((LPARAM) this, "GetDWORD(IMMPID*OPEN_PROPERTY_STREAM_HANDLES) failed hr %08lx", hr); DebugTrace((LPARAM) this, "returning g_cIMsgInSystem + m_cCurrentMsgsPendingSubmit"); } } TraceFunctLeaveEx((LPARAM) this); return SUCCEEDED(hr) ? dwStreamOpenHandlesCount : g_cIMsgInSystem + m_cCurrentMsgsPendingSubmit ; } //---[ CAQSvrInst::QueueMsgForLocalDelivery ]---------------------------------- // // // Description: // Queues a single message for local delivery // Parameters: // IN pmsgref Message Ref to deliver locally // Returns: // - // History: // 1/26/99 - MikeSwa Created // //----------------------------------------------------------------------------- void CAQSvrInst::QueueMsgForLocalDelivery(CMsgRef *pmsgref, BOOL fLocalLink) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::QueueMsgForLocalDelivery"); HRESULT hr = S_OK; CAQStats aqstat; CLinkMsgQueue *plmq = NULL; // // Get the stats from the msgref // pmsgref->GetStatsForMsg(&aqstat); // // Get the local link and update the stats // plmq = m_dmt.plmqGetLocalLink(); if (plmq) { hr = plmq->HrNotify(&aqstat, TRUE); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "HrNotify failed... local stats innaccurate 0x%08X", hr); hr = S_OK; } } InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingLocal); hr = m_asyncqPreLocalDeliveryQueue.HrQueueRequest(pmsgref); if (FAILED(hr)) { hr = plmq->HrNotify(&aqstat, FALSE); pmsgref->RetryOnDelete(); InterlockedDecrement((PLONG) &m_cCurrentMsgsPendingLocal); } // // Make sure we release the local link if we got it // if (plmq) plmq->Release(); TraceFunctLeave(); } //---[ CAQSvrInst::fRouteAndQueueMsg ]----------------------------------------- // // // Description: // Add a Categorized Message to the CMT to be queue for delivery // Parameters: // IN pIMailMsgProperties Msg to routing and queue for delivery // Returns: // TRUE if message has been successfully routed and queued for delivery // (or if errors have been handled internally) // FALSE if message needs to be requeued for a later retry // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fRouteAndQueueMsg(IN IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::fRouteAndQueueMsg"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; DWORD cDomains = 0; //number of domains message will be deliver to DWORD cQueues = 0; //number of queues for the message DWORD i = 0; //loop counter DWORD cLocalRecips = 0; DWORD cRemoteRecips = 0; DWORD dwDMTVersion = 0; CMsgRef *pmsgref = NULL; CDestMsgQueue **rgpdmq = NULL; BOOL fLocked = FALSE; BOOL fRoutingLock = FALSE; BOOL fLocal = FALSE; BOOL fRemote = FALSE; BOOL fOnDMQ = FALSE; BOOL fDMTLocked = FALSE; BOOL fKeepTrying = TRUE; BOOL fGotMsgType = FALSE; BOOL fReturn = TRUE; DWORD dwMessageType = 0; IMailMsgQueueMgmt *pIMailMsgQueueMgmt = NULL; IMailMsgRecipients *pIRecipList = NULL; IMessageRouter *pIMessageRouter = NULL; _ASSERT(CATMSGQ_SIG == m_dwSignature); _ASSERT(pIMailMsgProperties); if (NULL == pIMailMsgProperties) { hr = E_INVALIDARG; ErrorTrace((LPARAM) this, "NULL pIMailMsgProperties"); goto Exit; } hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgQueueMgmt, (PVOID *) &pIMailMsgQueueMgmt); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to QI for IID_IMailMsgQueueMgmt"); goto Exit; } hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgRecipients, (PVOID *) &pIRecipList); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to QI for IID_IMailMsgRecipients"); goto Exit; } //get a shared lock to guard against shutdown. if (!fTryShutdownLock()) { DebugTrace((LPARAM) pIMailMsgProperties, "Shutdown detecting while routing a message... bailing"); hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; hr = pIRecipList->DomainCount(&cDomains); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to get a domain count from the message - hr 0x08X", hr); goto Exit; } if (!cDomains) //and hence no recipients { DebugTrace((LPARAM) pIMailMsgProperties, "No domains on message - turfing message"); //This could be a completely valid case (like an empty DL) //In this case we just turf the message and act as if everything is fine DecMsgsInSystem(); //update our counters HandleBadMail(pIMailMsgProperties, TRUE, NULL, AQUEUE_E_NO_RECIPIENTS, FALSE); InterlockedIncrement((PLONG) &m_cBadmailNoRecipients); //Delete the message HrDeleteIMailMsg(pIMailMsgProperties); hr = S_OK; goto Exit; } //Check Message to see if there are unresolved recipients to NDR hr = HrNDRUnresolvedRecipients(pIMailMsgProperties, pIRecipList); if (FAILED(hr)) { HandleAQFailure(AQ_FAILURE_CANNOT_NDR_UNRESOLVED_RECIPS, hr, pIMailMsgProperties); ErrorTrace((LPARAM) this, "ERROR: Unable to NDR message - hr 0x%08X", hr); //just drop message for now... we cannot let it continue until this succeeds hr = S_OK; goto Exit; } if (S_FALSE == hr) { //There is no work to be done for this message - delete it HrDeleteIMailMsg(pIMailMsgProperties); DebugTrace((LPARAM) pIMailMsgProperties, "INFO: Deleting message after NDRing all unresolved recips"); hr = S_OK; DecMsgsInSystem(); //update our counters goto Exit; } rgpdmq = (CDestMsgQueue **) pvMalloc(cDomains * sizeof(CDestMsgQueue *)); if (NULL == rgpdmq) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to alloc array of %d Queues", cDomains); hr = E_OUTOFMEMORY; goto Exit; } RoutingShareLock(); fRoutingLock = TRUE; hr = HrTriggerGetMessageRouter(pIMailMsgProperties, &pIMessageRouter); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to get message router - HR 0x%08X", hr); goto Exit; } hr = pIMessageRouter->GetMessageType(pIMailMsgProperties, &dwMessageType); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to get message type - HR 0x%08X", hr); goto Exit; } //We own a reference to the message type now fGotMsgType = TRUE; pmsgref = new((DWORD) cDomains) CMsgRef(cDomains, pIMailMsgQueueMgmt, pIMailMsgProperties, this, dwMessageType, pIMessageRouter->GetTransportSinkID()); if (NULL == pmsgref) { ErrorTrace((LPARAM) pIMailMsgProperties, "Unable to allocate CMsgRef for %d domains", cDomains); hr = E_OUTOFMEMORY; goto Exit; } //Loop until we get consistant info on where to queue message (based on //DMT version number). while (fKeepTrying) { dwDMTVersion = m_dmt.dwGetDMTVersion(); hr = pmsgref->HrInitialize(pIRecipList, pIMessageRouter, dwMessageType, &cLocalRecips, &cRemoteRecips, &cQueues, rgpdmq); if (FAILED(hr)) { if (HRESULT_FROM_WIN32(ERROR_RETRY) == hr) { //Some sort of config/routing change... we need to retry the //message fGotMsgType = FALSE; hr = HrReGetMessageType(pIMailMsgProperties, pIMessageRouter, &dwMessageType); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "HrReGetMessageType failed with hr - 0x%08X", hr); goto Exit; } fGotMsgType = TRUE; } else //It was a genuine error... bail { ErrorTrace((LPARAM) pIMailMsgProperties, "CMsgRef::HrInitialze failed with hr 0x%08X", hr); goto Exit; } } //Before Enqueuing Messages or firing off local delivery... throttle usage count if (g_cMaxIMsgHandlesThreshold < cCountMsgsForHandleThrottling(pIMailMsgProperties)) { DebugTrace((LPARAM) 0xC0DEC0DE, "INFO: Closing IMsg Content - %d messsages in queue", cCountMsgsForHandleThrottling(pIMailMsgProperties)); //bounce usage count off of zero pIMailMsgQueueMgmt->ReleaseUsage(); pIMailMsgQueueMgmt->AddUsage(); } m_dmt.AquireDMTShareLock(); if (m_dmt.dwGetDMTVersion() != dwDMTVersion) { DebugTrace((LPARAM) this, "DMT version change: was %d, now %d", dwDMTVersion, m_dmt.dwGetDMTVersion()); //DMT Version changed... that means that our queues may have been //removed from the DMT. Time to retry m_dmt.ReleaseDMTShareLock(); _ASSERT(fKeepTrying); InterlockedIncrement((PLONG) &m_cDMTRetries); fGotMsgType = FALSE; hr = HrReGetMessageType(pIMailMsgProperties, pIMessageRouter, &dwMessageType); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "HrReGetMessageType failed with hr - 0x%08X", hr); goto Exit; } fGotMsgType = TRUE; continue; //Try again } fKeepTrying = FALSE; fDMTLocked = TRUE; //enqueue the message reference for each destination it is going to for (i = 0; i < cQueues; i++) { if (NULL != rgpdmq[i]) { InterlockedIncrement(&m_cTotalMsgsQueued); //enqueue message and assign message type to first enqueue hr = rgpdmq[i]->HrEnqueueMsg(pmsgref, !fOnDMQ); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "HrEnqueueMsg failed - 0x%08X", hr); InterlockedDecrement(&m_cTotalMsgsQueued); goto Exit; } fOnDMQ = TRUE; // // Check and see if this queue is explicitly routed remote. // It may be a gateway delivery queue. // if (!fRemote) fRemote = rgpdmq[i]->fIsRemote(); } else { fLocal = TRUE; } } _ASSERT(fDMTLocked); m_dmt.ReleaseDMTShareLock(); fDMTLocked = FALSE; if (fLocal) //kick off local delivery { QueueMsgForLocalDelivery(pmsgref, FALSE); } } Exit: if (fDMTLocked) { m_dmt.ReleaseDMTShareLock(); fDMTLocked = FALSE; } // Check and process any special queues in the DMT if (fRoutingLock && fLocked) m_dmt.ProcessSpecialLinks(m_dwDelayExpireMinutes, TRUE); // // Must Release IMessageRouter before starting shutdown since the // IMessageRouter sink may have a reference to us (via // IRouterReset) // if (NULL != pIMessageRouter) { if (!fOnDMQ && fGotMsgType) //we have a reference to this message type { hrTmp = pIMessageRouter->ReleaseMessageType(dwMessageType, 1); _ASSERT(SUCCEEDED(hrTmp)); } pIMessageRouter->Release(); } if (fRoutingLock) RoutingShareUnlock(); if (fLocked) ShutdownUnlock(); if (pIMailMsgQueueMgmt) pIMailMsgQueueMgmt->Release(); if (pIRecipList) pIRecipList->Release(); if (NULL != rgpdmq) FreePv(rgpdmq); if (fRemote && pmsgref) { InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingDelivery); //msgref needs to decrement the remote count when it is released pmsgref->CountMessageInRemoteTotals(); } if (NULL != pmsgref) { if (FAILED(hr) && (fOnDMQ || fLocal)) { //If we have a msgref and it has been queued, we must //wait until all other references are released to retry it pmsgref->RetryOnDelete(); hr = S_OK; //don't let caller retry } pmsgref->Release(); } //if we did not succeed, msgs is still in pre-routing queue if (FAILED(hr)) { fReturn = FALSE; //kick off retry if necessary ScheduleInternalRetry(LI_TYPE_PENDING_ROUTING); } else InterlockedDecrement((PLONG) &m_cCurrentMsgsPendingRouting); TRACE_COUNTERS; SleepForPerfAnalysis(g_dwRoutingQueueSleepMilliseconds); TraceFunctLeave(); return fReturn; } //---[ CAQSvrInst::HrAckMsg ]------------------------------------------------ // // // Description: // Acknowledge the (un)delivery of a message. Will call the msgref AckMsg, // which will requeue it to the appropriate queues // Parameters: // pMsgAck Pointer to Message Ack structure // fLocal TRUE if this is an ack for a local delivery // Returns: // S_OK on success // ERROR_INVALID_HANDLE if the context handle was invalid //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrAckMsg(IN MessageAck *pMsgAck, BOOL fLocal) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrAckMsg"); HRESULT hr = S_OK; DWORD i = 0; //loop counter CDeliveryContext *pdcntxt = NULL; _ASSERT(pMsgAck); _ASSERT(pMsgAck->pvMsgContext); pdcntxt = (CDeliveryContext *) pMsgAck->pvMsgContext; if ((NULL == pdcntxt) || !(pdcntxt->FVerifyHandle(pMsgAck->pIMailMsgProperties))) { hr = ERROR_INVALID_HANDLE; goto Exit; } if (!fLocal) { InterlockedIncrement(&m_cMsgsAcked); if (MESSAGE_STATUS_RETRY & pMsgAck->dwMsgStatus) InterlockedIncrement((PLONG) &m_cMsgsAckedRetry); } else //local { if (MESSAGE_STATUS_RETRY & pMsgAck->dwMsgStatus) InterlockedIncrement((PLONG) &m_cMsgsAckedRetryLocal); } hr = pdcntxt->HrAckMessage(pMsgAck); if (FAILED(hr)) goto Exit; Exit: //clean up all the things we have used here if (pdcntxt) pdcntxt->Recycle(); TRACE_COUNTERS; TraceFunctLeave(); return hr; } //---[ CAQSvrInst::HrNotify ]------------------------------------------------------------ // // // Description: // Passes notification off to Connection Mangaer // Parameters: // // Returns: // // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrNotify(IN CAQStats *paqstats, BOOL fAdd) { HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } hr = m_pConnMgr->HrNotify(paqstats, fAdd); ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::HrGetInternalDomainInfo ]---------------------------------- // // // Description: // Expose ability to get internal Domain Info to internal components // Parameters: // IN cbDomainnameLength Length of string to search for // IN szDomainName Domain Name to search for // OUT ppIntDomainInfo Domain info returned (must be released) // Returns: // S_OK if match is found // AQUEUE_E_INVALID_DOMAIN if no match is found // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrGetInternalDomainInfo( IN DWORD cbDomainNameLength, IN LPSTR szDomainName, OUT CInternalDomainInfo **ppDomainInfo) { HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } _ASSERT(CMQ_INIT_DCT & m_dwInitMask); hr = m_dct.HrGetInternalDomainInfo(cbDomainNameLength, szDomainName, ppDomainInfo); ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::HrGetDefaultDomainInfo ]---------------------------- // // // Description: // Expose ability to get internal default Domain Info to internal components // Parameters: // OUT ppIntDomainInfo Domain info returned (must be released) // Returns: // S_OK if found // AQUEUE_E_INVALID_DOMAIN if not found // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrGetDefaultDomainInfo( OUT CInternalDomainInfo **ppDomainInfo) { HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } _ASSERT(CMQ_INIT_DCT & m_dwInitMask); hr = m_dct.HrGetDefaultDomainInfo(ppDomainInfo); ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::HrGetDomainEntry ]---------------------------------------- // // // Description: // Get Domain Entry // Parameters: // IN cbDomainnameLength Length of string to search for // IN szDomainName Domain Name to search for // OUT ppdentry Domain Entry for domain (from DMT) // Returns: // S_OK on success // AQUEUE_E_INVALID_DOMAIN if domain is not found // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrGetDomainEntry(IN DWORD cbDomainNameLength, IN LPSTR szDomainName, OUT CDomainEntry **ppdentry) { HRESULT hr = S_OK; BOOL fLocked = FALSE; _ASSERT(cbDomainNameLength); _ASSERT(szDomainName); _ASSERT(ppdentry); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; hr = m_dmt.HrGetDomainEntry(cbDomainNameLength, szDomainName, ppdentry); if (FAILED(hr)) goto Exit; Exit: if (fLocked) ShutdownUnlock(); return hr; } //---[ CAQSvrInst::HrIterateDMTSubDomains ]---------------------------------------- // // // Description: // Get Domain Entry // Parameters: // IN cbDomainnameLength Length of string to search for // IN szDomainName Domain Name to search for // IN pfn Iterator function // IN pvcontext Context passed to each call // Returns: // S_OK on success // AQUEUE_E_INVALID_DOMAIN if domain is not found // //-------------------------------------------------------------------------------- HRESULT CAQSvrInst::HrIterateDMTSubDomains(IN LPSTR szDomainName, IN DWORD cbDomainNameLength, IN DOMAIN_ITR_FN pfn, IN PVOID pvContext) { HRESULT hr = S_OK; BOOL fLocked = FALSE; DOMAIN_STRING strDomain; _ASSERT(cbDomainNameLength); _ASSERT(szDomainName); _ASSERT(pfn); _ASSERT(pvContext); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; strDomain.Length = (USHORT) cbDomainNameLength; strDomain.MaximumLength = (USHORT) cbDomainNameLength; strDomain.Buffer = szDomainName; hr = m_dmt.HrIterateOverSubDomains(&strDomain, pfn,pvContext); if (FAILED(hr)) goto Exit; Exit: if (fLocked) ShutdownUnlock(); return hr; } //---[ CAQSvrInst::HrIterateDMTSubDomains ]---------------------------------------- // // // Description: // Get Domain Entry // Parameters: // IN cbDomainnameLength Length of string to search for // IN szDomainName Domain Name to search for // IN pfn Iterator function // IN pvcontext Context passed to each call // Returns: // S_OK on success // AQUEUE_E_INVALID_DOMAIN if domain is not found // //-------------------------------------------------------------------------------- HRESULT CAQSvrInst::HrIterateDCTSubDomains(IN LPSTR szDomainName, IN DWORD cbDomainNameLength, IN DOMAIN_ITR_FN pfn, IN PVOID pvContext) { HRESULT hr = S_OK; BOOL fLocked = FALSE; DOMAIN_STRING strDomain; _ASSERT(cbDomainNameLength); _ASSERT(szDomainName); _ASSERT(pfn); _ASSERT(pvContext); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; strDomain.Length = (USHORT) cbDomainNameLength; strDomain.MaximumLength = (USHORT) cbDomainNameLength; strDomain.Buffer = szDomainName; hr = m_dct.HrIterateOverSubDomains(&strDomain, pfn,pvContext); if (FAILED(hr)) goto Exit; Exit: if (fLocked) ShutdownUnlock(); return hr; } //---[ CAQSvrInst::HrTriggerGetMessageRouter ]-------------------------------------- // // // Description: // Wrapper function that signals the MAIL_TRANSPORT_ON_GET_ROUTER_EVENT // Parameters: // IN pIMailMsgProperties - IMailMsgProperties to get // OUT pIMessageRouter // // Returns: // // History: // 5/20/98 - MikeSwa Created // jstamerj 1998/07/10 18:30:41: Implemented Server event // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrTriggerGetMessageRouter( IN IMailMsgProperties *pIMailMsgProperties, OUT IMessageRouter **ppIMessageRouter) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::HrTriggerGetMessageRouter"); HRESULT hr = S_OK; BOOL fLocked = FALSE; _ASSERT(ppIMessageRouter); _ASSERT(pIMailMsgProperties); _ASSERT(m_pIMessageRouterDefault); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; if(m_pISMTPServer) { EVENTPARAMS_ROUTER EventParams; // // Initialiez EventParams // EventParams.dwVirtualServerID = m_dwServerInstance; EventParams.pIMailMsgProperties = pIMailMsgProperties; EventParams.pIMessageRouter = NULL; EventParams.pIRouterReset = m_pIRouterReset; EventParams.pIRoutingEngineDefault = this; hr = TriggerServerEvent( SMTP_MAILTRANSPORT_GET_ROUTER_FOR_MESSAGE_EVENT, &EventParams); if(SUCCEEDED(hr)) { if(EventParams.pIMessageRouter) { // // The implementor of GetMessageRouter returned // IMessageRouter with a refcount of one for us // *ppIMessageRouter = EventParams.pIMessageRouter; } else { // // The server event succeeded, but no sink supplied an // IMessageRouter (including default functionality) // hr = E_FAIL; } } } else { ErrorTrace((LPARAM)this, "Unable to trigger event to GetMessageRouter; using default"); // // Try calling our default (builtin) GetMessageRouter // hr = GetMessageRouter( pIMailMsgProperties, //IN IMsg NULL, //IN pIMessageRouter (Current) ppIMessageRouter); //OUT ppIMessageRouter (New) } Exit: if (fLocked) ShutdownUnlock(); TraceFunctLeaveEx((LPARAM)this); return hr; } //---[ CAQSvrInst::HrTriggerLogEvent ]----------------------------------------- // // // Description: // Wrapper function that signals the SMTP_LOG_EVENT event // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrTriggerLogEvent( IN DWORD idMessage, IN WORD idCategory, IN WORD cSubstrings, IN LPCSTR *rgszSubstrings, IN WORD wType, IN DWORD errCode, IN WORD iDebugLevel, IN LPCSTR szKey, IN DWORD dwOptions, IN DWORD iMessageString, IN HMODULE hModule) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::HrTriggerLogEvent"); HRESULT hr = S_OK; if (m_pISMTPServerEx) { hr = m_pISMTPServerEx->TriggerLogEvent( idMessage, idCategory, cSubstrings, rgszSubstrings, wType, errCode, iDebugLevel, szKey, dwOptions, iMessageString, hModule); } else { // // If we do not have at least W2K SP2... we will not have this // interface. // ErrorTrace((LPARAM) this, "Need W2KSP2: Unable to log event %d with erCode %d"); } TraceFunctLeaveEx((LPARAM)this); return hr; } //---[ CAQSvrInst::HrTriggerInitRouter ]--------------------------------------- // // // Description: // Wrapper function that signals the MAIL_TRANSPORT_ON_GET_ROUTER_EVENT // but only has it create a new router object // Parameters: // none // // Returns: // // History: // 5/20/98 - MikeSwa Created // jstamerj 1998/07/10 18:30:41: Implemented Server event // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrTriggerInitRouter() { TraceFunctEnter("CAQSvrInst::HrTriggerInitRouter"); HRESULT hr = S_OK; BOOL fLocked = FALSE; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; if (m_pISMTPServer) { EVENTPARAMS_ROUTER EventParams; // // Initialiez EventParams // EventParams.dwVirtualServerID = m_dwServerInstance; EventParams.pIMailMsgProperties = NULL; EventParams.pIMessageRouter = NULL; EventParams.pIRouterReset = m_pIRouterReset; EventParams.pIRoutingEngineDefault = NULL; hr = TriggerServerEvent( SMTP_MAILTRANSPORT_GET_ROUTER_FOR_MESSAGE_EVENT, &EventParams); } else { hr = S_OK; } Exit: if (fLocked) ShutdownUnlock(); TraceFunctLeaveEx((LPARAM)this); return hr; } //---[ CAQSvrInst::QueryInterface ]------------------------------------------ // // // Description: // QueryInterface for IAdvQueue // Parameters: // // Returns: // S_OK on success // // Notes: // This implementation makes it possible for any server component to get // the IAdvQueueConfig interface. // // History: // 7/29/98 - MikeSwa Modified (added IAdvQueueDomainType) // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::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_IAdvQueue == riid) { *ppvObj = static_cast(this); } else if (IID_IAdvQueueConfig == riid) { *ppvObj = static_cast(this); } else if (IID_IAdvQueueDomainType == riid) { *ppvObj = static_cast(this); } else if (IID_IAdvQueueAdmin == riid) { *ppvObj = static_cast(this); } else if (IID_IMailTransportRouterSetLinkState == riid) { *ppvObj = static_cast(this); } else if (IID_IAQServerEvent == riid) { *ppvObj = static_cast(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; } static_cast(*ppvObj)->AddRef(); Exit: return hr; } //---[ CAQSvrInst::SubmitMessage ]--------------------------------------------- // // // Description: // External function to submit messages for delivery // Parameters: // pIMailMsgProperties Msg to submit for delivery // Returns: // S_OK always // History: // 10/7/1999 - MikeSwa Moved from inline function // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::SubmitMessage(IN IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::SubmitMessage"); HRESULT hr = S_OK; if (NULL == pIMailMsgProperties) { ErrorTrace((LPARAM)NULL, "SubmitMessage called with NULL pIMailMsgProperties"); return E_INVALIDARG; } InterlockedIncrement((PLONG) &m_cTotalExternalMsgsSubmitted); InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingSubmit); // Before we queue this message, stamp the message submission time hr = HrSetSubmissionTimeIfNecessary(pIMailMsgProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to stamp submission time on message - hr 0x%08x", hr); goto Error_Exit; } hr = m_asyncqPreSubmissionQueue.HrQueueRequest( pIMailMsgProperties, FALSE, cCountMsgsForHandleThrottling(pIMailMsgProperties)); if (FAILED(hr)) { goto Error_Exit; } Exit: TraceFunctLeave(); return S_OK; Error_Exit: InterlockedDecrement((PLONG) &m_cCurrentMsgsPendingSubmit); HandleAQFailure(AQ_FAILURE_INTERNAL_ASYNCQ, hr, pIMailMsgProperties); goto Exit; } //---[ CAQSvrInst::HrInternalSubmitMessage ]----------------------------------- // // // Description: // Implements IAdvQueue::SubmitMessage // Parameters: // pIMailMsgProperties... Messaage to queue // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrInternalSubmitMessage( IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) pIMailMsgProperties, "CAQSvrInst::SubmitMessage"); _ASSERT(CATMSGQ_SIG == m_dwSignature); HRESULT hr = S_OK; DWORD dwMsgStatus = MP_STATUS_SUCCESS; EVENTPARAMS_SUBMISSION Params; FILETIME ftDeferred; DWORD cbProp = 0; DWORD dwContext = 0; _ASSERT(pIMailMsgProperties); if (NULL == pIMailMsgProperties) { ErrorTrace((LPARAM)NULL, "SubmitMessage called with NULL pIMailMsgProperties"); return E_INVALIDARG; } //Check and see if we need to request a retry for failed msgs m_fmq.StartProcessingIfNecessary(); hr = pIMailMsgProperties->GetProperty(IMMPID_MP_DEFERRED_DELIVERY_FILETIME, sizeof(FILETIME), &cbProp, (BYTE *) &ftDeferred); if (SUCCEEDED(hr) && !fInPast(&ftDeferred, &dwContext)) { //Defer delivery until a later time if deferred delivery time is //present, and in the past hr = S_OK; InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingDeferredDelivery); m_defq.Enqueue(pIMailMsgProperties, &ftDeferred); goto Exit; } // Set submission time for this message hr = HrSetSubmissionTimeIfNecessary(pIMailMsgProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to stamp submission time on message - hr 0x%08x", hr); return hr; } InterlockedIncrement((PLONG) &m_cTotalMsgsSubmitted); // // Set the message status (if currently unset) // hr = pIMailMsgProperties->GetDWORD( IMMPID_MP_MESSAGE_STATUS, &dwMsgStatus); if( (hr == MAILMSG_E_PROPNOTFOUND) || (g_fResetMessageStatus) ) { // // Initialize the message status // hr = pIMailMsgProperties->PutDWORD( IMMPID_MP_MESSAGE_STATUS, MP_STATUS_SUCCESS); dwMsgStatus = MP_STATUS_SUCCESS; } // //$$TODO: Jump from here to whatever state dwMsgStatus indicates // MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_BEGIN_SUBMIT_MESSAGE; m_pISMTPServer->WriteLog( &msgTrackInfo, pIMailMsgProperties, NULL, NULL ); // // AddRef this object here; release in completion // AddRef(); Params.pIMailMsgProperties = pIMailMsgProperties; Params.pfnCompletion = MailTransport_Completion_SubmitMessage; Params.pCCatMsgQueue = this; pIMailMsgProperties->AddRef(); InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingSubmitEvent); TRACE_COUNTERS; // // Call server event if we can and if dwMsgStatus does not // indicate the mssage has already been submitted // if(SUCCEEDED(hr) && (m_pISMTPServer) && (dwMsgStatus < MP_STATUS_SUBMITTED)) { hr = TriggerServerEvent( SMTP_MAILTRANSPORT_SUBMISSION_EVENT, &Params); DebugTrace((LPARAM)pIMailMsgProperties, "TriggerServerEvent returned hr %08lx", hr); } // // If TriggerServerEvent returned an error OR m_pISMTPServer is // null, or the message was already submitted, call the event // completion routine directly // if((m_pISMTPServer == NULL) || FAILED(hr) || (dwMsgStatus >= MP_STATUS_SUBMITTED)) { DebugTrace((LPARAM)this, "Skipping the submission event"); // Call the SEO Dispatcher completion routine directly so we // don't loose this mail... hr = SubmissionEventCompletion(S_OK, &Params); } // // SEO dispatcher will call the completion routine // (MailTransport_Completion_SubmitMessage) regardless wether or // not all the sinks work synchronously or async. Because of // this, this function is now done. // Exit: TraceFunctLeaveEx((LPARAM) pIMailMsgProperties); return hr; } //---[ CAQSvrInst::HandleFailedMessage ]-------------------------------------- // // // Description: // Handles a failed message from SMTP... usually by NDRing the message or // by treating the message as badmail. // // NOTE: Message or input file will be deleted by this operation. // Parameters: // pIMailMsgProperties MailMsg that needs to be handles // fUseIMailMsgProperties Use the IMailMsg if set, else use the szFilename, // szFileName use the filename if no msg // dwFailureReasons One of the failure reasons described in aqueue.idl // hrFailureCode Additional information that describes a failure // code encountered by SMTP. // Returns: // S_OK on success // E_INVALIDARG if pIMailMsgProperties is NULL // History: // 7/28/98 - MikeSwa Created // 10/14/98 - MikeSwa Added filename string for badmail // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::HandleFailedMessage( IN IMailMsgProperties *pIMailMsgProperties, IN BOOL fUseIMailMsgProperties, IN LPSTR szFileName, IN DWORD dwFailureReason, IN HRESULT hrFailureCode) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HandleFailedMessage"); HRESULT hr = S_OK; HRESULT hrBadMail = hrFailureCode; DWORD iCurrentDomain = 0; DWORD cDomains = 0; IMailMsgRecipients *pIMailMsgRecipients = NULL; CDSNParams dsnparams; BOOL fNDR = TRUE; //FALSE -> Badmail handling SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); dsnparams.dwStartDomain = 0; dsnparams.dwDSNActions = DSN_ACTION_FAILURE_ALL; dsnparams.pIMailMsgProperties = pIMailMsgProperties; dsnparams.hrStatus = hrFailureCode; MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_AQ_FAILED_MESSAGE; msgTrackInfo.dwRcptReportStatus = dwFailureReason; m_pISMTPServer->WriteLog( &msgTrackInfo, pIMailMsgProperties, NULL, NULL ); // // Switch over the various general failure reasons and handle them as // appropriate. // switch(dwFailureReason) { case MESSAGE_FAILURE_HOP_COUNT_EXCEEDED: // // Attempt to NDR // _ASSERT(pIMailMsgProperties); dsnparams.hrStatus = AQUEUE_E_MAX_HOP_COUNT_EXCEEDED; fNDR = TRUE; break; case MESSAGE_FAILURE_GENERAL: // // Attempt to NDR // fNDR = TRUE; break; case MESSAGE_FAILURE_CAT: // // Attempt to NDR... set DSN context to CAT // dsnparams.dwDSNActions |= DSN_ACTION_CONTEXT_CAT; fNDR = TRUE; break; case MESSAGE_FAILURE_BAD_PICKUP_DIR_FILE: // // Badmail, since we do not have the P1 information needed to badmail // _ASSERT(szFileName); hrBadMail = AQUEUE_E_PICKUP_DIR; fNDR = FALSE; //This should be handled as badmail break; default: _ASSERT(0 && "Unhandled failed msg case!"); } if (fNDR && pIMailMsgProperties && fUseIMailMsgProperties) { hr = HrLinkAllDomains(pIMailMsgProperties); if (FAILED(hr)) goto Exit; //Fire DSN Generation event hr = HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) { HandleBadMail(pIMailMsgProperties, fUseIMailMsgProperties, szFileName, hrBadMail, FALSE); if (dwFailureReason == MESSAGE_FAILURE_GENERAL) { InterlockedIncrement((PLONG) &m_cBadmailFailureGeneral); } else { _ASSERT(dwFailureReason == MESSAGE_FAILURE_HOP_COUNT_EXCEEDED); InterlockedIncrement((PLONG) &m_cBadmailHopCountExceeded); } hr = S_OK; //handled error internally ErrorTrace((LPARAM) this, "ERROR: Unable to NDR failed mail - hr 0x%08X", hr); goto Exit; } } else { //Handle as badmail HandleBadMail(pIMailMsgProperties, fUseIMailMsgProperties, szFileName, hrBadMail, FALSE); InterlockedIncrement((PLONG) &m_cBadmailBadPickupFile); _ASSERT(dwFailureReason == MESSAGE_FAILURE_BAD_PICKUP_DIR_FILE); } if ( fUseIMailMsgProperties && pIMailMsgProperties) { //Now that we are done... delete mailmsg from system hr = HrDeleteIMailMsg(pIMailMsgProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to delete message hr0x%08X", hr); //message was actually NDR'd/bad mailed correctly hr = S_OK; } } Exit: if (pIMailMsgRecipients) pIMailMsgRecipients->Release(); TraceFunctLeave(); return hr; } //+------------------------------------------------------------ // // Function: MailTransport_Completion_SubmitMessage // // Synopsis: SEO will call this routine after all sinks for // SubmitMessage have been handeled // // Arguments: // pvContext: Context passed into TriggerServerEvent // // Returns: // S_OK: Success // // History: // jstamerj 980609 16:13:40: Created. // //------------------------------------------------------------- HRESULT MailTransport_Completion_SubmitMessage( HRESULT hrStatus, PVOID pvContext) { TraceFunctEnter("MailTransport_Completion_SubmitMessage"); PEVENTPARAMS_SUBMISSION pParams = (PEVENTPARAMS_SUBMISSION) pvContext; CAQSvrInst *paqinst = (CAQSvrInst *) pParams->pCCatMsgQueue; TraceFunctLeave(); return paqinst->SubmissionEventCompletion( hrStatus, pParams); } //+------------------------------------------------------------ // // Function: CAQSvrInst::SubmissionEventCompletion // // Synopsis: Completion routine called when the submission event is // done. // // Arguments: // hrStatus: Status of server event // pParams: Context passed into TriggerServereEvent // // Returns: // Nothing // // History: // jstamerj 980610 12:26:18: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::SubmissionEventCompletion( HRESULT hrStatus, PEVENTPARAMS_SUBMISSION pParams) { TraceFunctEnterEx((LPARAM)pParams->pIMailMsgProperties, "CAQSvrInst::SubmissionEventCompletion"); _ASSERT(pParams); HRESULT hr; DebugTrace((LPARAM)pParams->pIMailMsgProperties, "Status of event completion: %08lx", hrStatus); InterlockedDecrement((PLONG) &m_cCurrentMsgsPendingSubmitEvent); // // Update the message status // hr = SetNextMsgStatus(MP_STATUS_SUBMITTED, pParams->pIMailMsgProperties); if (hr == S_OK) //anything else implies that the message has been handled { // Only trigger the precat event if message was not turfed. TriggerPreCategorizeEvent(pParams->pIMailMsgProperties); } // // Release refernce added in SubmitMessage // Release(); pParams->pIMailMsgProperties->Release(); // // pParams is part of a larger allocation that will be released by // SEO Dispatcher code // TraceFunctLeave(); return S_OK; } //---[ CAQSvrInst::SubmitMessageToCategorizer ]------------------------------------------- // // // Description: // Implements IAdvQueue::SubmitMessageToCategorizer // Parameters: // pIMailMsgProperties... Messaage to queue // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::SubmitMessageToCategorizer( IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) pIMailMsgProperties, "CAQSvrInst::SubmitMessageToCategorizer"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; IMailMsgQueueMgmt *pIMailMsgQueueMgmt = NULL; BOOL fLocked = FALSE; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_SUBMIT_MESSAGE_TO_CAT; m_pISMTPServer->WriteLog( &msgTrackInfo, pIMailMsgProperties, NULL, NULL ); fLocked = TRUE; cIncMsgsInSystem(); InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingCat); TRACE_COUNTERS; hr = m_asyncqPreCatQueue.HrQueueRequest(pIMailMsgProperties, FALSE, cCountMsgsForHandleThrottling(pIMailMsgProperties)); if (FAILED(hr)) { HandleAQFailure(AQ_FAILURE_PRECAT_RETRY, E_FAIL, pIMailMsgProperties); goto Exit; } Exit: if(fLocked) { ShutdownUnlock(); } TraceFunctLeave(); return hr; } //---[ CAQSvrInst::SetNextMsgStatus ]------------------------------------------ // // // Description: // Used by the event glue code to set the next message status. Will turf // or badmail a message if the status indicates that is the requested // action. // Parameters: // IN dwCurrentStatus The current status (according to *current* // place in event pipeline). Valid values are // MP_STATUS_SUBMITTED // MP_STATUS_CATEGORIZED // IN pIMailMsgProperties The message // OUT pdwNewStatus The new status // Returns: // S_OK Success // S_FALSE Success, but message has been handled. // History: // 11/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::SetNextMsgStatus( IN DWORD dwCurrentStatus, IN IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::SetNextMsgStatus"); HRESULT hr = S_OK; DWORD dwActualStatus = 0; DWORD dwNewStatus = 0; BOOL fHandled = FALSE; _ASSERT(pIMailMsgProperties); _ASSERT((MP_STATUS_SUBMITTED == dwCurrentStatus) || (MP_STATUS_CATEGORIZED == dwCurrentStatus)); hr = pIMailMsgProperties->GetDWORD(IMMPID_MP_MESSAGE_STATUS, &dwActualStatus); if (FAILED(hr)) dwActualStatus = dwCurrentStatus; if (MP_STATUS_SUCCESS == dwActualStatus) dwActualStatus = dwCurrentStatus; switch(dwActualStatus) { case MP_STATUS_BAD_MAIL: HandleBadMail(pIMailMsgProperties, TRUE, NULL, E_FAIL, FALSE); InterlockedIncrement((PLONG) &m_cBadmailEvent); //OK... now continue as if message was aborted case MP_STATUS_ABORT_DELIVERY: fHandled = TRUE; HrDeleteIMailMsg(pIMailMsgProperties); break; case MP_STATUS_ABANDON_DELIVERY: //In this case, we will leave the message in the queue directory //until restart & reset the state so it goes through the entire //pipeline. The idea is that someone can write a sink to detect //a non-supported state (like CAT disabled in an Exchange install) //that will abandon delivery of the messages and log an event. //The admin can fix the problem and restart smtpsvc. Once //the service is restarted... the messages are magically submitted //and re-categorized. fHandled = TRUE; pIMailMsgProperties->PutDWORD(IMMPID_MP_MESSAGE_STATUS, MP_STATUS_SUCCESS); break; case MP_STATUS_CATEGORIZED: //Don't change status from categorized to something else dwNewStatus = dwActualStatus; DebugTrace((LPARAM) this, "Message 0x%x has already been categorized", pIMailMsgProperties); break; default: //simply move on to the next expected status dwNewStatus = dwCurrentStatus; } if (!fHandled) { pIMailMsgProperties->PutDWORD(IMMPID_MP_MESSAGE_STATUS, dwNewStatus); //callers will not be able to do anything about a failure to write status hr = S_OK; } else { DecMsgsInSystem(); hr = S_FALSE; } TraceFunctLeave(); return hr; } //---[ CAQSvrInst::fPreCatQueueCompletion ]----------------------------------- // // // Description: // Completion routine for Pre-Categorization queue // Parameters: // pIMailMsgProperties - MailMsg to give to categorization // Returns: // TRUE if successful // FALSE if message needs to be re-requeue // History: // 7/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fPreCatQueueCompletion(IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::fPreCatQueueCompletion"); _ASSERT(CATMSGQ_SIG == m_dwSignature); HRESULT hr = S_OK; HRESULT hrCatCompletion; IUnknown *pIUnknown = NULL; BOOL fRet = TRUE; BOOL fLocked = FALSE; if (!fTryShutdownLock()) { hr = S_OK; //we cannot retry on shutdown goto Exit; } fLocked = TRUE; hr = pIMailMsgProperties->QueryInterface(IID_IUnknown, (PVOID *) &pIUnknown); _ASSERT(SUCCEEDED(hr) && "QueryInterface for IID_IUknown Failed!"); _ASSERT(pIUnknown); InterlockedIncrement((PLONG) &m_cCatMsgCalled); m_asyncqPreCatQueue.IncPendingAsyncCompletions(); hr = CatMsg(m_hCat, pIUnknown,(PFNCAT_COMPLETION)CAQSvrInst::CatCompletion, (LPVOID) this); if (FAILED(hr)) { if(hr == CAT_E_RETRY) { // // Return false so that this message will be re-queued // fRet = FALSE; InterlockedDecrement((PLONG) &m_cCatMsgCalled); m_asyncqPreCatQueue.DecPendingAsyncCompletions(); // // Schedule a time to retry the messages // ScheduleInternalRetry(LI_TYPE_PENDING_CAT); } else { // // Return true since this is not a retryable error // Call CatCompletion to handle the non-retryable error (log an event, etc) // hrCatCompletion = CatCompletion( hr, // hrCatResult (LPVOID) this, // pContext pIUnknown, // pIMsg NULL); // rgpIMsg _ASSERT(SUCCEEDED(hrCatCompletion)); } } Exit: if (pIUnknown) pIUnknown->Release(); if(fLocked) { ShutdownUnlock(); } SleepForPerfAnalysis(g_dwCatQueueSleepMilliseconds); TraceFunctLeave(); return fRet; } //---[ CAQSvrInst::SetConfigInfo ]-------------------------------------------- // // // Description: // Implements IAdvQueueConfig::SetConfigInfo // Parameters: // IN pAQConfigInfo Ptr to config info structure // Returns: // S_OK on success // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::SetConfigInfo(IN AQConfigInfo *pAQConfigInfo) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::SetConfigInfo"); HRESULT hr = S_OK; if (!pAQConfigInfo) { hr = E_INVALIDARG; goto Exit; } //check version of structure if (!pAQConfigInfo->cbVersion) { hr = E_INVALIDARG; goto Exit; } //we must be setting something if (!(pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_ALL)) { hr = E_INVALIDARG; goto Exit; } m_slPrivateData.ExclusiveLock(); //Retry related config data if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY && MEMBER_OK(pAQConfigInfo, dwFirstRetrySeconds)) { m_dwFirstTierRetrySeconds = pAQConfigInfo->dwFirstRetrySeconds; } if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_EXPIRE_DELAY && MEMBER_OK(pAQConfigInfo, dwDelayExpireMinutes)) { m_dwDelayExpireMinutes = pAQConfigInfo->dwDelayExpireMinutes; if (m_dwDelayExpireMinutes == 0) { //Default to g_dwRetriesBeforeDelay* retry interval m_dwDelayExpireMinutes = g_dwRetriesBeforeDelay*m_dwFirstTierRetrySeconds/60; } } if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_EXPIRE_NDR && MEMBER_OK(pAQConfigInfo, dwNDRExpireMinutes)) { m_dwNDRExpireMinutes = pAQConfigInfo->dwNDRExpireMinutes; if (m_dwNDRExpireMinutes == 0) { //Default to g_dwDelayIntervalsBeforeNDR* delay expiration m_dwNDRExpireMinutes = g_dwDelayIntervalsBeforeNDR*m_dwDelayExpireMinutes; } } if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_LOCAL_EXPIRE_DELAY && MEMBER_OK(pAQConfigInfo, dwLocalDelayExpireMinutes)) { DWORD dwOldLocalDelayExpire = m_dwLocalDelayExpireMinutes; m_dwLocalDelayExpireMinutes = pAQConfigInfo->dwLocalDelayExpireMinutes; if (m_dwLocalDelayExpireMinutes == 0) { //Default to g_dwRetriesBeforeDelay* retry interval m_dwLocalDelayExpireMinutes = g_dwRetriesBeforeDelay*m_dwFirstTierRetrySeconds/60; } } if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_LOCAL_EXPIRE_NDR && MEMBER_OK(pAQConfigInfo, dwLocalNDRExpireMinutes)) { m_dwLocalNDRExpireMinutes = pAQConfigInfo->dwLocalNDRExpireMinutes; if (m_dwLocalNDRExpireMinutes == 0) { //Default to g_dwDelayIntervalsBeforeNDR* delay expiration m_dwLocalNDRExpireMinutes = g_dwDelayIntervalsBeforeNDR*m_dwLocalDelayExpireMinutes; } } //Handle default local domain if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_DEFAULT_DOMAIN && MEMBER_OK(pAQConfigInfo, szDefaultLocalDomain)) { hr = HrUpdateRefCountedString(&m_prstrDefaultDomain, pAQConfigInfo->szDefaultLocalDomain); } //Handle Server FQDN if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_SERVER_FQDN && MEMBER_OK(pAQConfigInfo, szServerFQDN)) { hr = HrUpdateRefCountedString(&m_prstrServerFQDN, pAQConfigInfo->szServerFQDN); } //Handle Copy NDR To Address if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_SEND_DSN_TO && MEMBER_OK(pAQConfigInfo, szSendCopyOfNDRToAddress)) { hr = HrUpdateRefCountedString(&m_prstrCopyNDRTo, pAQConfigInfo->szSendCopyOfNDRToAddress); } //Handle BadMail config if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_BADMAIL_DIR && MEMBER_OK(pAQConfigInfo, szBadMailDir)) { hr = HrUpdateRefCountedString(&m_prstrBadMailDir, pAQConfigInfo->szBadMailDir); } //Get DSN options if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_USE_DSN_OPTIONS && MEMBER_OK(pAQConfigInfo, dwDSNOptions)) { m_dwDSNOptions = pAQConfigInfo->dwDSNOptions; } //Get Default DSN Language if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_USE_DSN_LANGUAGE && MEMBER_OK(pAQConfigInfo, dwDSNLanguageID)) { m_dwDSNLanguageID = pAQConfigInfo->dwDSNLanguageID; } m_slPrivateData.ExclusiveUnlock(); m_pConnMgr->UpdateConfigData(pAQConfigInfo); if (INVALID_HANDLE_VALUE != m_hCat) { HRESULT hrTmp = CatChangeConfig(m_hCat, pAQConfigInfo, m_pISMTPServer, (IAdvQueueDomainType *) this); if (SUCCEEDED(hr)) hr = hrTmp; } Exit: TraceFunctLeave(); return hr; } //---[ CAQSvrInst::SetDomainInfo ]------------------------------------------- // // // Description: // Implements IAdvQueueConfig::SetDomainInfo // Parameters: // IN pDomainInfo Per domain config info to store // Returns: // S_OK on success // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::SetDomainInfo(IN DomainInfo *pDomainInfo) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::SetDomainInfo"); HRESULT hr = S_OK; CInternalDomainInfo *pIntDomainInfo = NULL; BOOL fLocked = FALSE; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; pIntDomainInfo = new CInternalDomainInfo(m_dct.dwGetCurrentVersion()); if (!pIntDomainInfo) { hr = E_OUTOFMEMORY; goto Exit; } //Create internal Domain Info struct hr = pIntDomainInfo->HrInit(pDomainInfo); if (FAILED(hr)) goto Exit; hr = m_dct.HrSetInternalDomainInfo(pIntDomainInfo); if (FAILED(hr)) goto Exit; DebugTrace((LPARAM) this, "INFO: Setting domain info flags 0x%08X for domain %s", pDomainInfo->dwDomainInfoFlags, pDomainInfo->szDomainName); Exit: if (fLocked) ShutdownUnlock(); if (pIntDomainInfo) pIntDomainInfo->Release(); TraceFunctLeave(); return hr; } //---[ CAQSvrInst::GetDomainInfo ]------------------------------------------- // // // Description: // Implements IAdvQueue::GetDomainInfo... returns information about a // requested domain. To keep from leaking memory, all calls must be paired // with a call to ReleaseDomainInfo. Will handle wildcard matches // Parameters: // IN cbDomainNameLength Length of domain name string // IN szDomainName Domain Name to look for // IN OUT pDomainInfo Ptr to Domain info structure to fill // OUT ppvDomainContext Ptr to Domain context used to release mem // Returns: // S_OK on success // History: // 7/29/98 - MikeSwa Modified (fixed leak of domain info struct) //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::GetDomainInfo( IN DWORD cbDomainNameLength, IN CHAR szDomainName[], IN OUT DomainInfo *pDomainInfo, OUT DWORD **ppvDomainContext) { HRESULT hr = S_OK; CInternalDomainInfo *pIntDomainInfo = NULL; BOOL fLocked = FALSE; if (!cbDomainNameLength || !szDomainName || !pDomainInfo || !ppvDomainContext) { hr = E_INVALIDARG; goto Exit; } _ASSERT(pDomainInfo->cbVersion >= sizeof(DomainInfo)); *ppvDomainContext = NULL; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; hr = m_dct.HrGetInternalDomainInfo(cbDomainNameLength, szDomainName, &pIntDomainInfo); if (FAILED(hr)) goto Exit; _ASSERT(pIntDomainInfo); //copy domain info struct memcpy(pDomainInfo, &(pIntDomainInfo->m_DomainInfo), sizeof(DomainInfo)); *ppvDomainContext = (DWORD *) pIntDomainInfo; goto Exit; Exit: if (fLocked) ShutdownUnlock(); return hr; } //---[ CAQSvrInst::ReleaseDomainInfo ]--------------------------------------- // // // Description: // Implements IAdvQueueConfig ReleaseDomainInfo... releases data // associated with the DomainInfo struct returned by GetDomainInfo. // Parameters: // IN pvDomainContext Context passed // Returns: // S_OK on success // E_INVALIDARG if pvDomainContext is NULL //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::ReleaseDomainInfo(IN DWORD *pvDomainContext) { HRESULT hr = S_OK; CInternalDomainInfo *pIntDomainInfo = (CInternalDomainInfo *) pvDomainContext; if (!pIntDomainInfo) { hr = E_INVALIDARG; goto Exit; } pIntDomainInfo->Release(); Exit: return hr; } //---[ CAQSvrInst::GetPerfCounters ]--------------------------------------- // // // Description: // Method to retrieve AQ perf counters. // Parameters: // OUT pAQPerfCounters Struct to return counters in. // OUT pCatPerfCouneters Struct to return counters in. (optinal) // Returns: // S_OK on success // E_INVALIDARG if pAQPerfCounters is NULL //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::GetPerfCounters( OUT AQPerfCounters *pAQPerfCounters, OUT PCATPERFBLOCK pCatPerfCounters) { HRESULT hr = S_OK; if (!pAQPerfCounters) return( E_INVALIDARG ); _ASSERT((sizeof(AQPerfCounters) == pAQPerfCounters->cbVersion) && "aqueue/smtpsvc dll version mismatch"); if (sizeof(AQPerfCounters) != pAQPerfCounters->cbVersion) return( E_INVALIDARG ); pAQPerfCounters->cMsgsDeliveredLocal = m_cMsgsDeliveredLocal; pAQPerfCounters->cCurrentMsgsPendingCat = m_cCurrentMsgsPendingCat; pAQPerfCounters->cCurrentMsgsPendingRemoteDelivery = m_cCurrentMsgsPendingDelivery; pAQPerfCounters->cCurrentMsgsPendingLocalDelivery = m_cCurrentMsgsPendingLocal; pAQPerfCounters->cCurrentQueueMsgInstances = m_cCurrentQueueMsgInstances; pAQPerfCounters->cTotalMsgRemoteSendRetries = m_cMsgsAckedRetry; pAQPerfCounters->cTotalMsgLocalRetries = m_cMsgsAckedRetryLocal; pAQPerfCounters->cCurrentMsgsPendingLocalRetry = m_cCurrentMsgsPendingLocalRetry; //DSN counters pAQPerfCounters->cNDRsGenerated = m_cNDRs; pAQPerfCounters->cDelayedDSNsGenerated = m_cDelayedDSNs; pAQPerfCounters->cDeliveredDSNsGenerated = m_cDeliveredDSNs; pAQPerfCounters->cRelayedDSNsGenerated = m_cRelayedDSNs; pAQPerfCounters->cExpandedDSNsGenerated = m_cExpandedDSNs; pAQPerfCounters->cTotalMsgsTURNETRN = m_cTotalMsgsTURNETRNDelivered; //Queue/Link related counters pAQPerfCounters->cCurrentRemoteDestQueues = m_cCurrentRemoteDestQueues; pAQPerfCounters->cCurrentRemoteNextHopLinks = m_cCurrentRemoteNextHops; pAQPerfCounters->cTotalMsgsBadmailNoRecipients = m_cBadmailNoRecipients; pAQPerfCounters->cTotalMsgsBadmailHopCountExceeded = m_cBadmailHopCountExceeded; pAQPerfCounters->cTotalMsgsBadmailFailureGeneral = m_cBadmailFailureGeneral; pAQPerfCounters->cTotalMsgsBadmailBadPickupFile = m_cBadmailBadPickupFile; pAQPerfCounters->cTotalMsgsBadmailEvent = m_cBadmailEvent; pAQPerfCounters->cTotalMsgsBadmailNdrOfDsn = m_cBadmailNdrOfDsn; pAQPerfCounters->cCurrentMsgsPendingRouting = m_cCurrentMsgsPendingRouting; pAQPerfCounters->cTotalDSNFailures = m_cTotalDSNFailures; pAQPerfCounters->cCurrentMsgsInLocalDelivery = m_cCurrentMsgsInLocalDelivery; // // The m_cTotalMsgsSubmitted counter counts the number of times // HrInternalSubmit msg has been called. This does not include the // presubmission queue, so me need to manually add this count in. // pAQPerfCounters->cTotalMsgsSubmitted = m_cTotalMsgsSubmitted + m_cCurrentMsgsPendingSubmit; if (fTryShutdownLock()) { pAQPerfCounters->cCurrentMsgsPendingUnreachableLink = m_dmt.GetCurrentlyUnreachableTotalMsgCount(); ShutdownUnlock(); } //For now, these counters will be calculated by walking the DMT (same //function that determines msgs pending retry). pAQPerfCounters->cCurrentRemoteNextHopLinksEnabled = 0; pAQPerfCounters->cCurrentRemoteNextHopLinksPendingRetry = 0; pAQPerfCounters->cCurrentRemoteNextHopLinksPendingScheduling = 0; pAQPerfCounters->cCurrentRemoteNextHopLinksPendingTURNETRN = 0; pAQPerfCounters->cCurrentRemoteNextHopLinksFrozenByAdmin = 0; //Get Retry remote retry and DSN counters pAQPerfCounters->cCurrentMsgsPendingRemoteRetry = 0; if (fTryShutdownLock()) { hr = m_dmt.HrIterateOverSubDomains(NULL, CalcDMTPerfCountersIteratorFn, pAQPerfCounters); //will not generate transient errors (we expect success or an empty table _ASSERT(SUCCEEDED(hr) || (DOMHASH_E_NO_SUCH_DOMAIN == hr)); if((m_hCat != INVALID_HANDLE_VALUE) && (pCatPerfCounters)) hr = CatGetPerfCounters(m_hCat, pCatPerfCounters); _ASSERT(SUCCEEDED(hr)); ShutdownUnlock(); } //save values in CAQSvrInst, so we can dump them in the debugger m_cCurrentRemoteNextHopsEnabled = pAQPerfCounters->cCurrentRemoteNextHopLinksEnabled; m_cCurrentRemoteNextHopsPendingRetry = pAQPerfCounters->cCurrentRemoteNextHopLinksPendingRetry; m_cCurrentRemoteNextHopsPendingSchedule = pAQPerfCounters->cCurrentRemoteNextHopLinksPendingScheduling; m_cCurrentRemoteNextHopsFrozenByAdmin = pAQPerfCounters->cCurrentRemoteNextHopLinksFrozenByAdmin; return( S_OK ); } //---[ CAQSvrInst::ResetPerfCounters ]--------------------------------------- // // // Description: // Method to reset AQ perf counters to 0. // Parameters: // None // Returns: // S_OK on success //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::ResetPerfCounters() { m_cTotalMsgsQueued = 0; m_cTotalMsgsSubmitted = 0; m_cMsgsAcked = 0; m_cMsgsAckedRetry = 0; m_cMsgsDeliveredLocal = 0; m_cMsgsAckedRetryLocal = 0; m_cTotalMsgsTURNETRNDelivered = 0; //clear DSN counters m_cDelayedDSNs = 0; m_cNDRs = 0; m_cDeliveredDSNs = 0; m_cRelayedDSNs = 0; m_cExpandedDSNs = 0; return( S_OK ); } //---[ CAQSvrInst::StartConfigUpdate() ]--------------------------------------- // // // Description: // Implements IAQConfig::StartConfigUpdate() which is used to signal that // all of the domain information is about to be updated. // Parameters: // - // Returns: // S_OK on success // AQUEUE_E_SHUTDOWN on shutdown // History: // 9/29/98 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::StartConfigUpdate() { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::StartConfigUpdate"); HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; } else { m_dct.StartConfigUpdate(); ShutdownUnlock(); } TraceFunctLeave(); return hr; } //---[ CAQSvrInst::FinishConfigUpdate ]---------------------------------------- // // // Description: // Implements IAQConfig::FinishConfigUpdate() which is used to signal that // signal that all of the domain information has been updated. This will // cause us to walk through the DomainConfigTable and remove any domain // config info that has not been updated (ie - a domain that has been // deleted). // Parameters: // - // Returns: // S_OK on success // AQUEUE_E_SHUTDOWN if called while shutdown is in progress. // History: // 9/29/98 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::FinishConfigUpdate() { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::FinishConfigUpdate"); HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; } else { m_dct.FinishConfigUpdate(); //Reroute everything //$$REVIEW - should we re-route on metabase changes? ResetRoutes(RESET_NEXT_HOPS); //Important configuration data may have changed... kick connmgr m_pConnMgr->KickConnections(); ShutdownUnlock(); } TraceFunctLeave(); return hr; } //+------------------------------------------------------------ // // Function: CAQSvrInst::TriggerPostCategorizeEvent // // Synopsis: Triggers post categorization event // // Arguments: // pIMsg: MailMsg for event or NULL // rgpIMsg: NULL or a null terminated array of mailmsg pointers // // NOTE: pIMsg or rgpIMsg must be NULL, but neither can be null // (exclusive OR) // // Returns: // - // // History: // jstamerj 980616 20:43:08: Created. // 8/25/98 - MikeSwa Modified - removed return code // //------------------------------------------------------------- void CAQSvrInst::TriggerPostCategorizeEvent( IUnknown *pIMsg, IUnknown **rgpIMsg) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::TriggerPostCategorizeEvent"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; IMailMsgProperties *pIMailMsgProperties = NULL; if(pIMsg) { hr = TriggerPostCategorizeEventOneMsg(pIMsg); if (FAILED(hr)) { hrTmp = pIMsg->QueryInterface(IID_IMailMsgProperties, (void **) &pIMailMsgProperties); _ASSERT(SUCCEEDED(hrTmp) && "Could not QI for IMailMsgProperties"); if (FAILED(hrTmp)) LogAQEvent(AQ_FAILURE_POSTCAT_EVENT, NULL, NULL, NULL); else { HandleAQFailure(AQ_FAILURE_POSTCAT_EVENT, hr, pIMailMsgProperties); pIMailMsgProperties->Release(); pIMailMsgProperties = NULL; } DecMsgsInSystem(FALSE, FALSE); hr = S_OK; } } else { _ASSERT(rgpIMsg); IUnknown **ppIMsgCurrent = rgpIMsg; DecMsgsInSystem(FALSE, FALSE); while(SUCCEEDED(hr) && (*ppIMsgCurrent)) { hr = TriggerPostCategorizeEventOneMsg( *ppIMsgCurrent); ppIMsgCurrent++; if (FAILED(hr)) { hrTmp = (*ppIMsgCurrent)->QueryInterface(IID_IMailMsgProperties, (void **) &pIMailMsgProperties); _ASSERT(SUCCEEDED(hrTmp) && "Could not QI for IMailMsgProperties"); if (FAILED(hrTmp)) LogAQEvent(AQ_FAILURE_POSTCAT_EVENT, NULL, NULL, NULL); else { HandleAQFailure(AQ_FAILURE_POSTCAT_EVENT, hr, pIMailMsgProperties); pIMailMsgProperties->Release(); pIMailMsgProperties = NULL; } hr = S_OK; } else { cIncMsgsInSystem(); } } } TraceFunctLeaveEx((LPARAM)this); } //+------------------------------------------------------------ // // Function: CAQSvrInst::TriggerPostCategorizeEventOneMsg // // Synopsis: Triggers ONE server event for one mailmsg // // Arguments: // pIMsg - mailmsg // // Returns: // S_OK: Success // // History: // jstamerj 980616 21:26:30: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::TriggerPostCategorizeEventOneMsg( IUnknown *pIMsg) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::TriggerPostCategorizeEventOneMsg"); HRESULT hr; // // trigger one event // this is an async event // EVENTPARAMS_POSTCATEGORIZE Params; // Setup pParams hr = pIMsg->QueryInterface(IID_IMailMsgProperties, (PVOID *)&(Params.pIMailMsgProperties)); if(FAILED(hr)) { ErrorTrace((LPARAM)this, "QI failed with error %08lx", hr); TraceFunctLeaveEx((LPARAM)this); return hr; } Params.pfnCompletion = MailTransport_Completion_PostCategorization; Params.pCCatMsgQueue = (PVOID) this; // // Addref here, release in completion // AddRef(); // // keep a count of messages in the post-cat event // InterlockedIncrement((LPLONG) &m_cCurrentMsgsPendingPostCatEvent); if(m_pISMTPServer) { hr = TriggerServerEvent( SMTP_MAILTRANSPORT_POSTCATEGORIZE_EVENT, &Params); DebugTrace((LPARAM)this, "TriggerServerEvent returned hr %08lx", hr); } if((m_pISMTPServer == NULL) || (FAILED(hr))) { ErrorTrace((LPARAM)this, "Unable to dispatch server event; calling completion routine directly"); // // Call completion routine directly // TraceFunctLeaveEx((LPARAM)this); return PostCategorizationEventCompletion(S_OK, &Params); } TraceFunctLeaveEx((LPARAM)this); return S_OK; } //+------------------------------------------------------------ // // Function: MailTransport_Completion_PostCategorization // // Synopsis: SEO will call this routine after all sinks for // OnPostCategoriztion have been handeled // // Arguments: // pvContext: Context passed into TriggerServerEvent // // Returns: // S_OK: Success // // History: // jstamerj 980609 16:13:40: Created. // //------------------------------------------------------------- HRESULT MailTransport_Completion_PostCategorization( HRESULT hrStatus, PVOID pvContext) { TraceFunctEnter("MailTransport_Completion_PostCategorization"); PEVENTPARAMS_POSTCATEGORIZE pParams = (PEVENTPARAMS_POSTCATEGORIZE) pvContext; CAQSvrInst *paqinst = (CAQSvrInst *) pParams->pCCatMsgQueue; TraceFunctLeave(); return paqinst->PostCategorizationEventCompletion( hrStatus, pParams); } //+------------------------------------------------------------ // // Function: CAQSvrInst::PostCategorizationEventCompletion // // Synopsis: Called on the completion side of OnPostCategorization // // Arguments: // hrStatus: status of server event // pParams: context structure passed into TriggerServerEvent // // Returns: // S_OK: Success // // History: // jstamerj 980616 21:33:05: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::PostCategorizationEventCompletion( HRESULT hrStatus, PEVENTPARAMS_POSTCATEGORIZE pParams) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::PostCategorizationEventCompletion"); DebugTrace((LPARAM)this, "hrStatus is %08lx", hrStatus); HRESULT hr; // // Decrease count of msgs in post-cat event // InterlockedDecrement((LPLONG) &m_cCurrentMsgsPendingPostCatEvent); hr = SetNextMsgStatus(MP_STATUS_CATEGORIZED, pParams->pIMailMsgProperties); //See if this message has been "handled" if (S_FALSE == hr) { //Message has been "handled"... do not try to route it hr = S_OK; } else { //Increment counters and put msg into the pre-routing queue InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingRouting); hr = m_asyncqPreRoutingQueue.HrQueueRequest(pParams->pIMailMsgProperties, FALSE, cCountMsgsForHandleThrottling(pParams->pIMailMsgProperties)); if (FAILED(hr)) { HandleAQFailure(AQ_FAILURE_PREROUTING_FAILED, hr, pParams->pIMailMsgProperties); ErrorTrace((LPARAM)this, "fRouteAndQueueMsg failed with hr %08lx", hr); DecMsgsInSystem(FALSE, FALSE); //don't passback shutdown errors in completions routines if (AQUEUE_E_SHUTDOWN == hr) hr = S_OK; } } // // Release mailmsg reference added in TriggerPostCategorizerEventOneMsg // pParams->pIMailMsgProperties->Release(); // // Release reference to this object added in // TriggerPostCategorizerEventOneMsg // Release(); TraceFunctLeaveEx((LPARAM)this); return S_OK; //we should always handle failures internally here } //---[ CatCompletion ]--------------------------------------------------------- // // // Description: // Message Categoriztion Completion function // Parameters: // hrCatResult HRESULT of categorization attempt // pContext Context as passed into MsgCat // pIMsg Single categorized IMsg (if not bifurcated) // rgpIMsg NULL terminated array of IMsg's (bifurcated) // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::CatCompletion(HRESULT hrCatResult, PVOID pContext, IUnknown *pIMsg, IUnknown **rgpIMsg) { TraceFunctEnterEx((LPARAM) pIMsg, "CatCompletion"); HRESULT hr = S_OK; CAQSvrInst *paqinst = (CAQSvrInst *) pContext; IMailMsgProperties *pIMailMsg = NULL; IMailMsgQueueMgmt *pIMailMsgQM = NULL; _ASSERT(paqinst); _ASSERT(CATMSGQ_SIG == paqinst->m_dwSignature); //Increment count of times CatCompletion called InterlockedIncrement((PLONG) &(paqinst->m_cCatCompletionCalled)); paqinst->m_asyncqPreCatQueue.DecPendingAsyncCompletions(); //make sure Cat is returning an HRESULT _ASSERT(!hrCatResult || (hrCatResult & 0xFFFF0000)); if (SUCCEEDED(hrCatResult)) { // // Kick off post categorize event // InterlockedDecrement((PLONG) &(paqinst->m_cCurrentMsgsPendingCat)); paqinst->TriggerPostCategorizeEvent(pIMsg, rgpIMsg); } else if (FAILED(hrCatResult) && (CAT_E_RETRY == hrCatResult)) { //MsgCat has some re-tryable error... //stick it back in the queue and retry later DebugTrace((LPARAM) paqinst, "INFO: MsgCat had tmp failure - hr 0x%08X", hr); // // Adjust counters... they we be adjusted correctly per msg in // HandleCatRetryOneMessage // InterlockedDecrement((PLONG) &(paqinst->m_cCurrentMsgsPendingCat)); paqinst->DecMsgsInSystem(FALSE, FALSE); if(pIMsg) { paqinst->HandleCatRetryOneMessage(pIMsg); } else { _ASSERT(rgpIMsg); IUnknown **ppIMsgCurrent = rgpIMsg; while(*ppIMsgCurrent) { paqinst->HandleCatRetryOneMessage(*ppIMsgCurrent); ppIMsgCurrent++; } } } else { _ASSERT(pIMsg && rgpIMsg == NULL && "Message bifurcated inspite of non-retryable cat error"); paqinst->HandleCatFailure(pIMsg, hrCatResult); } // Non retryable error TraceFunctLeaveEx((LPARAM)paqinst); return S_OK; //all errors should be handled internally } //---[ CAQSvrInst::HandleCatRetryOneMessage ]---------------------------------- // // // Description: // Handles cat retry for a single message // Parameters: // pIUnknown IUnknown for the message to retry // Returns: // - // History: // 4/13/2000 - MikeSwa Created // //----------------------------------------------------------------------------- void CAQSvrInst::HandleCatRetryOneMessage(IUnknown *pIUnknown) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HandleCatRetryOneMessage"); IMailMsgProperties *pIMailMsgProperties = NULL; HRESULT hr = S_OK; hr = pIUnknown->QueryInterface(IID_IMailMsgProperties, (void **) &pIMailMsgProperties); _ASSERT(SUCCEEDED(hr) && "QueryInterface for IID_IMailMsgProperties FAILED"); if (FAILED(hr)) goto Exit; // // Check and see if the message is still valid // if (!fShouldRetryMessage(pIMailMsgProperties)) goto Exit; // // Queue it to the pre-cat queue // hr = m_asyncqPreCatQueue.HrQueueRequest(pIMailMsgProperties, TRUE, cCountMsgsForHandleThrottling(pIMailMsgProperties)); if (FAILED(hr)) { HandleAQFailure(AQ_FAILURE_PRECAT_RETRY, hr, pIMailMsgProperties); goto Exit; } // // Adjust counters as appropriate // InterlockedIncrement((PLONG) &m_cCurrentMsgsPendingCat); cIncMsgsInSystem(); // // Kick off cat retry if needed // ScheduleInternalRetry(LI_TYPE_PENDING_CAT); Exit: if (pIMailMsgProperties) pIMailMsgProperties->Release(); TraceFunctLeave(); } //---[ CAQSvrInst::HandleCatFailure ]------------------------------------------ // // // Description: // Handles the details of post cat DSN generation. Will put the // message in the failed queue if DSN generation fails // Parameters: // pIUnknown IUnkown for mailmsg // hrCatResult Error code returned by cat // Returns: // - // History: // 11/11/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void CAQSvrInst::HandleCatFailure(IUnknown *pIUnknown, HRESULT hrCatResult) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HandleCatFailure"); HRESULT hr = S_OK; IMailMsgProperties *pIMailMsgProperties = NULL; BOOL fHasShutdownLock = FALSE; ErrorTrace((LPARAM) this, "ERROR: MsgCat failed, will try to NDR message - hr 0x%08X", hrCatResult); InterlockedDecrement((PLONG) &m_cCurrentMsgsPendingCat); DecMsgsInSystem(FALSE, FALSE); const char *rgszStrings[1] = { NULL }; if(!pIUnknown) goto Exit; //If we are shutting down, this error could be caused by a shutdown being //signaled. If this is the case, we do not want to log an error or //generate an NDR. if (!fTryShutdownLock()) goto Exit; fHasShutdownLock = TRUE; HrTriggerLogEvent( AQUEUE_CAT_FAILED, // Message ID TRAN_CAT_QUEUE_ENGINE, // Category 1, // Word count of substring rgszStrings, // Substring EVENTLOG_WARNING_TYPE, // Type of the message hrCatResult, // error code LOGEVENT_LEVEL_MEDIUM, // Logging level "phatq", // key to this event LOGEVENT_FLAG_PERIODIC, // Logging option 0, // index of format message string in rgszStrings GetModuleHandle(AQ_MODULE_NAME) // module handle to format a message ); hr = pIUnknown->QueryInterface(IID_IMailMsgProperties, (void **) &pIMailMsgProperties); _ASSERT(SUCCEEDED(hr) && "QueryInterface for IID_IMailMsgProperties FAILED"); if (FAILED(hr)) goto Exit; // we ignore errors on this since it is only to help debug // cat failures pIMailMsgProperties->PutDWORD(IMMPID_MP_HR_CAT_STATUS, hrCatResult); hr = HandleFailedMessage(pIMailMsgProperties, TRUE, NULL, MESSAGE_FAILURE_CAT, hrCatResult); Exit: if (pIMailMsgProperties) pIMailMsgProperties->Release(); if (fHasShutdownLock) ShutdownUnlock(); TraceFunctLeave(); } //+------------------------------------------------------------ // // Function: CAQSvrInst::ResetRoutes // // Synopsis: This is a sink callback function; sinks will call this // function when they wish to reset next hop routes or message types. // // Arguments: // dwResetType: Must be either RESET_NEXT_HOPS or RESET_MESSAGE_TYPES // // Returns: // S_OK: Success // E_INVALIDARG: bogus dwResetType // // History: // jstamerj 1998/07/10 19:27:45: Created. // 3/9/99 - MikeSwa Added async reset // //------------------------------------------------------------- STDMETHODIMP CAQSvrInst::ResetRoutes( IN DWORD dwResetType) { TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::ResetRoutes"); HRESULT hr = S_OK; InterlockedIncrement((PLONG) &m_cTotalResetRoutes); if(dwResetType == RESET_NEXT_HOPS) { DebugTrace((LPARAM)this, "ResetNextHops called"); if (1 == InterlockedIncrement((PLONG) &m_cCurrentPendingResetRoutes)) { DebugTrace((LPARAM) this, "Adding ResetRoutes operation to work queue"); AddRef(); //released in completion function hr = HrQueueWorkItem(this, CAQSvrInst::fResetRoutesNextHopCompletion); //Failure will still call completion function, so we should not release } else { DebugTrace((LPARAM) this, "Other ResetRoutes pending... only one pending allowed"); InterlockedDecrement((PLONG) &m_cCurrentPendingResetRoutes); } } else if(dwResetType == RESET_MESSAGE_TYPES) { DebugTrace((LPARAM)this, "ResetMessageTypes called"); //$$TODO: Reset message types } else { ErrorTrace((LPARAM)this, "ResetRoutes called with bogus dwResetType %08lx", dwResetType); hr = E_INVALIDARG; } return hr; } //---[ CAQSvrInst::LogResetRouteEvent ]---------------------------------------- // // // Description: // Log statistics on resetroute // Parameters: // dwObtainLock time spend on obtaining exclusive lock // dwWaitLock time spend on waiting for the lock // dwQueue queue length at the moment // History: // 11/10/2000 haozhang created // //----------------------------------------------------------------------------- void CAQSvrInst::LogResetRouteEvent( DWORD dwObtainLock, DWORD dwWaitLock, DWORD dwQueue) { LPSTR lpstr[3]; char subStrings[3][13]; sprintf (subStrings[0],"%d",dwObtainLock); sprintf (subStrings[1],"%d",dwWaitLock); sprintf (subStrings[2],"%d",dwQueue); lpstr[0] = subStrings[0]; lpstr[1] = subStrings[1]; lpstr[2] = subStrings[2]; HrTriggerLogEvent( AQUEUE_RESETROUTE_DIAGNOSTIC, // Message ID TRAN_CAT_QUEUE_ENGINE, // Category ID 3, // Word count of substring (LPCSTR *) lpstr, // Substring EVENTLOG_INFORMATION_TYPE, // Type of the message 0, // No error code LOGEVENT_LEVEL_MEDIUM, // Debug level NULL, // Key to identify this event LOGEVENT_FLAG_ALWAYS ); } //---[ CAQSvrInst::fResetRoutesNextHopCompletion ]----------------------------- // // // Description: // Completion function that handles async reset routes // Parameters: // pvThis Ptr to CAQSvrInst // dwStatus Status returned by // Returns: // TRUE always // History: // 3/9/99 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fResetRoutesNextHopCompletion(PVOID pvThis, DWORD dwStatus) { TraceFunctEnterEx((LPARAM) pvThis, "CAQSvrInst::fResetRoutesNextHopCompletion"); CAQSvrInst *paqinst = (CAQSvrInst *) pvThis; DWORD cCurrentPendingResetRoutes = 0; DWORD dwPreLock; DWORD dwObtainLock; DWORD dwReleaseLock; DWORD dwQueue; HRESULT hr = S_OK; _ASSERT(paqinst); if (ASYNC_WORK_QUEUE_NORMAL == dwStatus) { if (paqinst && paqinst->fTryShutdownLock()) { DebugTrace((LPARAM) paqinst, "Rerouting domains"); dwPreLock = GetTickCount(); paqinst->m_slPrivateData.ExclusiveLock(); dwObtainLock = GetTickCount(); dwQueue = paqinst->m_cCurrentRemoteDestQueues; //Drop pending reset routes count here. We should do it after //we grab the lock to prevent too many threads from //trying to grab it exclusively. We also need to do it //before we actual update any routing info in case a ResetRoutes //is requested midway through this update. cCurrentPendingResetRoutes = InterlockedDecrement((PLONG) &(paqinst->m_cCurrentPendingResetRoutes)); //Make sure the count hasn't gone negative _ASSERT(cCurrentPendingResetRoutes < 0xFFFFFF00); //With the lock held, call HrBeginRerouteDomains. This function //will flag a reroute in progress and will move all domains to //the currently unreachable queue. hr = paqinst->m_dmt.HrBeginRerouteDomains(); paqinst->m_slPrivateData.ExclusiveUnlock(); dwReleaseLock = GetTickCount(); // If the first part failed, we don't do the second part if(SUCCEEDED(hr)) { //Now, having released the lock, call HrCompleteRerouteDomains. //This function will reroute the contents of the currently //unreachable queue and will then unflag the reroute in progress. paqinst->m_dmt.HrCompleteRerouteDomains(); } //If things have been re-routing to a special link... we should //process them as well. paqinst->m_dmt.ProcessSpecialLinks(paqinst->m_dwDelayExpireMinutes, FALSE); paqinst->ShutdownUnlock(); // // Log Event of ResetRoute // paqinst->LogResetRouteEvent( dwObtainLock - dwPreLock, // time to obtain the exclusive lock dwReleaseLock - dwObtainLock, // time waiting on the lock dwQueue // number of queues ); } } else { if (paqinst) { cCurrentPendingResetRoutes = InterlockedDecrement((PLONG) &(paqinst->m_cCurrentPendingResetRoutes)); //Make sure the count hasn't gone negative _ASSERT(cCurrentPendingResetRoutes < 0xFFFFFF00); } if (ASYNC_WORK_QUEUE_FAILURE & dwStatus) ErrorTrace((LPARAM) paqinst, "ResetRoutes completion failure"); } if (paqinst) paqinst->Release(); TraceFunctLeave(); return TRUE; } //---[ CAQSvrInst::GetDomainInfoFlags ]-------------------------------------- // // // Description: // Determines if a domain is local (has DOMAIN_INFO_LOCAL_MAILBOX set) // Parameters: // IN szDomainName Name of domain to check for // OUT pdwDomainInfoFlags DomainInfo flags for this domain // Returns: // S_OK on success // E_INVALIDARG if szDomainName or pdwDomainInfoFlags is NULL // History: // 7/29/98 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::GetDomainInfoFlags( IN LPSTR szDomainName, OUT DWORD *pdwDomainInfoFlags) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::GetDomainInfoFlags"); BOOL fLocked = FALSE; HRESULT hr = S_OK; DWORD cbDomainName = 0; CInternalDomainInfo *pIntDomainInfo = NULL; ISMTPServerGetAuxDomainInfoFlags *pISMTPServerGetAuxDomainInfoFlags = NULL; DWORD dwSinkDomainFlags = 0; _ASSERT(pdwDomainInfoFlags && "Invalid Param"); _ASSERT(szDomainName && "Invalid Param"); if (!pdwDomainInfoFlags || !szDomainName) { hr = E_INVALIDARG; goto Exit; } if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; cbDomainName = lstrlen(szDomainName); hr = m_dct.HrGetInternalDomainInfo(cbDomainName, szDomainName, &pIntDomainInfo); if (FAILED(hr)) goto Exit; _ASSERT(pIntDomainInfo); // Aux Domain info not found, use the config we got from our own tables *pdwDomainInfoFlags = pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags; // We should have gotten back domain config even if it is only // the default config - now we need to see if we can get more // specific data from an event sink if (!cbDomainName || pIntDomainInfo->m_DomainInfo.szDomainName[0] == '*') { // QI for ISMTPServerGetAuxDomainInfoFlags interface hr = m_pISMTPServer->QueryInterface( IID_ISMTPServerGetAuxDomainInfoFlags, (LPVOID *)&pISMTPServerGetAuxDomainInfoFlags); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to QI for ISMTPServerGetAuxDomainInfoFlags 0x%08X",hr); // Drop this error, this isn't fatal hr = S_OK; goto Exit; } // Check for domain info hr = pISMTPServerGetAuxDomainInfoFlags->HrTriggerGetAuxDomainInfoFlagsEvent( szDomainName, &dwSinkDomainFlags); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Failed calling HrTriggerGetAuxDomainInfoFlags 0x%08X",hr); // Drop this error, this isn't fatal hr = S_OK; goto Exit; } if (dwSinkDomainFlags & DOMAIN_INFO_INVALID) { // Domain info not found from event sink hr = S_OK; goto Exit; } // Ok, we got Aux Domain info, use it *pdwDomainInfoFlags = dwSinkDomainFlags; } Exit: if (pISMTPServerGetAuxDomainInfoFlags) pISMTPServerGetAuxDomainInfoFlags->Release(); if (pIntDomainInfo) pIntDomainInfo->Release(); if (fLocked) ShutdownUnlock(); TraceFunctLeave(); return hr; } //+------------------------------------------------------------ // // Function: CAQSvrInst::GetMessageRouter // // Synopsis: Default functionality of GetMessageRouter // If there is no current IMessageRouter, provide the // default IMessageRouter // // Arguments: // pIMailMsgProperties: MailMsg that needs a router // pICurrentRouter: current sink provided router // ppIMessageRouter: out param for new IMessageRouter // // Returns: // S_OK: Success, provided IMessageRouter // E_NOTIMPL: Didn't provide an IMessageRouter // // History: // jstamerj 1998/07/10 19:33:41: Created. // //------------------------------------------------------------- STDMETHODIMP CAQSvrInst::GetMessageRouter( IN IMailMsgProperties *pIMailMsgProperties, IN IMessageRouter *pICurrentMessageRouter, OUT IMessageRouter **ppIMessageRouter) { _ASSERT(ppIMessageRouter); TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::GetMessageRouter"); if((pICurrentMessageRouter == NULL) && (m_pIMessageRouterDefault)) { // // Return our default IMessageRouter and AddRef for the caller // *ppIMessageRouter = m_pIMessageRouterDefault; m_pIMessageRouterDefault->AddRef(); DebugTrace((LPARAM)this, "Supplying default IMessageRouter"); TraceFunctLeaveEx((LPARAM)this); return S_OK; } else { TraceFunctLeaveEx((LPARAM)this); return E_NOTIMPL; } } //---[ CAQSvrInst::HrTriggerDSNGenerationEvent ]----------------------------- // // // Description: // Triggers DSN Generation event // Parameters: // pdsnparams A CDSNParams that will be used to trigger event // fHasRoutingLock TRUE if routing lock is current held by this thread // Returns: // S_OK on success (and DSN was generated) // S_FALSE on success, but no DSN generated // AQUEUE_E_NOT_INITIALIZED if not initialized correctly // History: // 7/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrTriggerDSNGenerationEvent(CDSNParams *pdsnparams, BOOL fHasRoutingLock) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrTriggerDSNGenerationEvent"); HRESULT hr = S_OK; DWORD cCurrent = 0; CRefCountedString *prstrDefaultDomain = NULL; LPSTR szDefaultDomain = NULL; CRefCountedString *prstrCopyNDRTo = NULL; LPSTR szCopyNDRTo = NULL; CRefCountedString *prstrFQDN = NULL; LPSTR szFQDN = NULL; DWORD cCurrentDSNsGenerated = 0; DWORD cbCurrentSize = 0; FILETIME *pftExpireTime = NULL; FILETIME ftExpireTime; if (!(m_dwInitMask & CMQ_INIT_DSN) || !m_pISMTPServer) { hr = AQUEUE_E_NOT_INITIALIZED; goto Exit; } //Get config string from ref-counted objects if (!fHasRoutingLock) m_slPrivateData.ShareLock(); else m_slPrivateData.AssertIsLocked(); if (m_prstrDefaultDomain) { prstrDefaultDomain = m_prstrDefaultDomain; prstrDefaultDomain->AddRef(); szDefaultDomain = prstrDefaultDomain->szStr(); } else { //we need to have something as our default domain szDefaultDomain = "localhost"; } if (m_prstrCopyNDRTo) { prstrCopyNDRTo = m_prstrCopyNDRTo; prstrCopyNDRTo->AddRef(); szCopyNDRTo = prstrCopyNDRTo->szStr(); } if (m_prstrServerFQDN) { prstrFQDN = m_prstrServerFQDN; prstrFQDN->AddRef(); szFQDN = prstrFQDN->szStr(); } if (!fHasRoutingLock) m_slPrivateData.ShareUnlock(); // // Get the expire time // hr = pdsnparams->pIMailMsgProperties->GetProperty( IMMPID_MP_EXPIRE_NDR, sizeof(FILETIME), &cbCurrentSize, (BYTE *) &ftExpireTime); if (SUCCEEDED(hr)) { _ASSERT(sizeof(FILETIME) == cbCurrentSize); pftExpireTime = &ftExpireTime; } else if (MAILMSG_E_PROPNOTFOUND == hr) { // // Calculate the expire time based on the arrival time // hr = pdsnparams->pIMailMsgProperties->GetProperty( IMMPID_MP_ARRIVAL_FILETIME, sizeof(FILETIME), &cbCurrentSize, (BYTE *) &ftExpireTime); if(SUCCEEDED(hr)) { CalcExpireTimeNDR(ftExpireTime, FALSE, &ftExpireTime); pftExpireTime = &ftExpireTime; } else if(hr == MAILMSG_E_PROPNOTFOUND) hr = S_OK; else goto Exit; } else goto Exit; // // Set the CDSNParam's paqinst pointer so that // CDSNParams::HrSubmitDSN call call back into this CAQSvrInst // object. // pdsnparams->paqinst = this; hr = m_dsnsink.GenerateDSN( this, m_dwServerInstance, m_pISMTPServer, pdsnparams->pIMailMsgProperties, pdsnparams->dwStartDomain, pdsnparams->dwDSNActions, pdsnparams->dwRFC821Status, pdsnparams->hrStatus, szDefaultDomain, szFQDN, (CHAR *) DEFAULT_MTA_TYPE, pdsnparams->szDebugContext, m_dwDSNLanguageID, m_dwDSNOptions, szCopyNDRTo, pftExpireTime, pdsnparams, g_dwMaxDSNSize); if (SUCCEEDED(hr)) { if(pdsnparams->dwDSNTypesGenerated) { // A DSN was generated hr = S_OK; } else { // No DSN was generated hr = S_FALSE; } } else if (AQUEUE_E_NDR_OF_DSN == hr) { hr = S_FALSE; //report as no DSN generated //original message is badmail HandleBadMail(pdsnparams->pIMailMsgProperties, TRUE, NULL, AQUEUE_E_NDR_OF_DSN, fHasRoutingLock); InterlockedIncrement((PLONG) &m_cBadmailNdrOfDsn); } else { //bail out on failure InterlockedIncrement((PLONG) &m_cTotalDSNFailures); // // Check to see if the message has been deleted... store driver // has been gone away. // if (!fShouldRetryMessage(pdsnparams->pIMailMsgProperties, FALSE)) { DebugTrace((LPARAM) this, "Msg no longer valid... abandoning"); hr = S_FALSE; } goto Exit; } Exit: if (prstrDefaultDomain) prstrDefaultDomain->Release(); if (prstrCopyNDRTo) prstrCopyNDRTo->Release(); if (prstrFQDN) prstrFQDN->Release(); TraceFunctLeave(); return hr; } //---[ CAQSvrInst::HrNDRUnresolvedRecipients ]------------------------------- // // // Description: // NDR any unresolved recipients for a given IMailMsgProperties. Also // generates a expanded DSNs // Parameters: // IN pIMailMsgProperties IMailMsgProperties to generate NDR for // IN pIMailMsgRecipients Recipients interface for message // Returns: // S_OK on success and message should continue through transport // S_FALSE on success, but message should not be queued for delivery // History: // 7/21/98 - MikeSwa Created // 10/14/98 - MikeSwa Modified to use common utility functions // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrNDRUnresolvedRecipients( IMailMsgProperties *pIMailMsgProperties, IMailMsgRecipients *pIMailMsgRecipients) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrNDRUnresolvedRecipients"); HRESULT hr = S_OK; HRESULT hrCat = S_OK; //cat HRESULT DWORD cbProp = 0; DWORD iCurrentDomain = 0; DWORD cRecips = 0; _ASSERT(pIMailMsgProperties); hr = pIMailMsgProperties->GetProperty(IMMPID_MP_HR_CAT_STATUS, sizeof(HRESULT), &cbProp, (BYTE *) &hrCat); if (FAILED(hr)) { if (MAILMSG_E_PROPNOTFOUND == hr) //no result... don't generate DSN hr = S_OK; //not really an error goto Exit; } if (CAT_W_SOME_UNDELIVERABLE_MSGS == hrCat) { //There was an error resolving recipients //We need to NDR all recipients with hard errors (like RP_UNRESOLVED) //and expand any recipient marked expanded. CDSNParams dsnparams; dsnparams.dwStartDomain = 0; dsnparams.dwDSNActions = DSN_ACTION_FAILURE | DSN_ACTION_EXPANDED; dsnparams.pIMailMsgProperties = pIMailMsgProperties; dsnparams.hrStatus = CAT_W_SOME_UNDELIVERABLE_MSGS; hr = HrLinkAllDomains(pIMailMsgProperties); if (FAILED(hr)) goto Exit; //Fire DSN Generation event SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); hr = HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) goto Exit; //Check to see how many recipients have been NDRd hr = pIMailMsgRecipients->Count(&cRecips); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: IMailMsgRecipients::Count() FAILED - hr 0x%08X", hr); goto Exit; } //If all recipients have been handled... return S_FALSE if (dsnparams.cRecips == cRecips) { hr = S_FALSE; } else { hr = S_OK; } } else { hr = S_OK; } Exit: TraceFunctLeave(); return hr; } //---[ CAQSvrInst::fPreLocalDeliveryQueueCompletion ]------------------------ // // // Description: // Completion function for PerLocal delivery queue // Parameters: // pmsgref - Msgref to attempt delivery for // Returns: // TRUE If Delivery attempt was handled (delivered or NDR'd) // FALSE If MsgRef needs to be requeued // History: // 7/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CAQSvrInst::fPreLocalDeliveryQueueCompletion(CMsgRef *pmsgref) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::fPreLocalDeliveryQueueCompletion"); HRESULT hr = S_OK; BOOL fMsgHandled = TRUE; BOOL fLocked = FALSE; //TRUE if locked for shutdown DWORD cRecips = 0; DWORD *rgdwRecips= 0; CAQStats aqstat; CLinkMsgQueue *plmq = NULL; BOOL fReleaseLDNotify = FALSE; CAQLocalDeliveryNotify *pLDNotify = NULL; BOOL fUpdateCounters = TRUE; if (NULL == m_pISMTPServer) { ErrorTrace((LPARAM) this, "ERROR: Local Delivery not configured properly"); goto Exit; } if (NULL == pmsgref) { ErrorTrace((LPARAM) this, "ERROR: Local Delivery not configured properly, msgref=NULL"); goto Exit; } if (!fTryShutdownLock()) { goto Exit; } pLDNotify = new CAQLocalDeliveryNotify(this, pmsgref); if (!pLDNotify) { ErrorTrace((LPARAM) this, "ERROR: new CAQLocalDeliveryNotify failed"); fMsgHandled = FALSE; goto Exit; } fReleaseLDNotify = TRUE; fLocked = TRUE; if (pmsgref->fIsMsgFrozen()) { //Message is frozen... requeue message fMsgHandled = FALSE; goto Exit; } hr = m_dmt.HrPrepareForLocalDelivery(pmsgref, FALSE, pLDNotify->pdcntxtGetDeliveryContext(), &cRecips, &rgdwRecips); if (FAILED(hr)) { if ((AQUEUE_E_MESSAGE_HANDLED != hr) && (AQUEUE_E_MESSAGE_PENDING != hr)) { //message will be retried when last reference is released. pmsgref->RetryOnDelete(); ErrorTrace((LPARAM) this, "ERROR: HrPrepareLocalDelivery FAILED - hr 0x%08X", hr); } fMsgHandled = TRUE; hr = S_OK; goto Exit; } //Increase Ref count for message ref (as if it was actually queued) pmsgref->AddRef(); //Send off for local delivery InterlockedIncrement((PLONG) &m_cCurrentMsgsInLocalDelivery); MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_LOCAL_DELIVERY; msgTrackInfo.cRcpts = cRecips; m_pISMTPServer->WriteLog(&msgTrackInfo, pLDNotify->pimsgGetIMsg(), NULL, NULL); // from here on we will end up calling LDCompletion, and they will // release it fReleaseLDNotify = FALSE; m_asyncqPreLocalDeliveryQueue.IncPendingAsyncCompletions(); // we need to hold a reference on pLDNotify to be able to call // into fNotCalledCompletion pLDNotify->AddRef(); fUpdateCounters = FALSE; if (m_pISMTPServerAsync) { hr = m_pISMTPServerAsync->TriggerLocalDeliveryAsync( pLDNotify->pimsgGetIMsg(), cRecips, rgdwRecips, pLDNotify); } else { hr = m_pISMTPServer->TriggerLocalDelivery( pLDNotify->pimsgGetIMsg(), cRecips, rgdwRecips); } // if we get back any error code besides MAILMSG_S_PENDING then // the dispatcher completed sync. We need to see if the // completion function was called, and if not then we need to call // it ourselves. if (hr != MAILMSG_S_PENDING && pLDNotify->fNotCalledCompletion()) { LDCompletion(hr, this, pmsgref, pLDNotify); } // after this point we can't call pLDNotify pLDNotify->Release(); Exit: if (fUpdateCounters && fMsgHandled) { UpdateLDCounters(pmsgref); } if (fReleaseLDNotify) { pLDNotify->Release(); } if (fLocked) ShutdownUnlock(); TraceFunctLeave(); return fMsgHandled; } //---[ CAQSvrInst::LDCompletion ]---------------------------------------------- // // // Description: // Completion function local delivery // Parameters: // hrLDResult - local delivery status // pContext - this // pmsgref - msgref that we were delivering. // History: // 10/30/2000 - AWetmore Created // //----------------------------------------------------------------------------- void CAQSvrInst::LDCompletion(HRESULT hr, PVOID pContext, CMsgRef *pmsgref, CAQLocalDeliveryNotify *pLDNotify) { TraceFunctEnter("CAQSvrInst::LDCompletion"); BOOL fMsgHandled = TRUE; MessageAck *pMsgAck = pLDNotify->pmsgackGetMsgAck(); CAQSvrInst *pThis = (CAQSvrInst *) pContext; InterlockedDecrement((PLONG) &(pThis->m_cCurrentMsgsInLocalDelivery)); pThis->m_asyncqPreLocalDeliveryQueue.DecPendingAsyncCompletions(); if (FAILED(hr)) { //We will need to handle in one of 2 ways: // - set fMsgHandled to FALSE (on STOREDRV_E_RETRY) // - NDR the message (on other errors) if (STOREDRV_E_RETRY == hr) { //try... try again DebugTrace((LPARAM) pmsgref, "INFO: Msg queued for local retry"); fMsgHandled = FALSE; pMsgAck->dwMsgStatus = MESSAGE_STATUS_RETRY; pThis->ScheduleInternalRetry(LI_TYPE_LOCAL_DELIVERY); } else { ErrorTrace((LPARAM) pmsgref, "ERROR: Local delivery failed. - hr 0x%08X", hr); pMsgAck->dwMsgStatus = MESSAGE_STATUS_NDR_ALL; } } else { InterlockedIncrement(&(pThis->m_cMsgsDeliveredLocal)); } pMsgAck->pIMailMsgProperties = pLDNotify->pimsgGetIMsg(); pMsgAck->pvMsgContext = (DWORD *) pLDNotify->pdcntxtGetDeliveryContext(); pMsgAck->dwMsgStatus |= MESSAGE_STATUS_LOCAL_DELIVERY; // // Make sure we should retry the message. We want to do this before we // ACK the message so that we do not reopen the P1 stream if we *are* // retrying it. // if (!fMsgHandled) fMsgHandled = !pmsgref->fShouldRetry(); hr = pThis->HrAckMsg(pMsgAck, TRUE); if (FAILED(hr)) { ErrorTrace((LPARAM) pThis, "ERROR: Local MsgAck failed - hr 0x%08X", hr); goto Exit; } Exit: if (fMsgHandled) //we aren't retrying message { pThis->UpdateLDCounters(pmsgref); } else { // retry this message pThis->HandleLocalRetry(pLDNotify->pmsgrefGetMsgRef()); } // clean up after ourselves pLDNotify->Release(); SleepForPerfAnalysis(g_dwLocalQueueSleepMilliseconds); TraceFunctLeave(); } //---[ CAQSvrInst::UpdateLDCounters ]------------------------------------------ // // // Description: // Update the local delivery queue counters // Parameters: // pmsgref - msgref that we were delivering. // History: // 05/11/2001 - AWetmore Created // //----------------------------------------------------------------------------- void CAQSvrInst::UpdateLDCounters(CMsgRef *pmsgref) { TraceFunctEnter("CAQSvrInst::UpdateLDCounters"); CAQStats aqstat; CLinkMsgQueue *plmq = NULL; HRESULT hr; InterlockedDecrement((PLONG) &(m_cCurrentMsgsPendingLocal)); // // Update stats for the local link // pmsgref->GetStatsForMsg(&aqstat); plmq = m_dmt.plmqGetLocalLink(); if (plmq) { hr = plmq->HrNotify(&aqstat, FALSE); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "HrNotify failed... local stats innaccurate 0x%08X", hr); hr = S_OK; } plmq->Release(); plmq = NULL; } TraceFunctLeave(); } //---[ CAQSvrInst::HandleLocalRetry ]------------------------------------------ // // // Description: // Handles LD retry for a single message // Parameters: // pIUnknown IUnknown for the message to retry // Returns: // - // History: // 10/30/2000 - AWetmore Created // //----------------------------------------------------------------------------- void CAQSvrInst::HandleLocalRetry(CMsgRef *pmsgref) { TraceFunctEnter("CAQSvrInst::HandleLocalRetry"); IMailMsgProperties *pIMailMsgProperties = pmsgref->pimsgGetIMsg(); HRESULT hr = S_OK; if (pIMailMsgProperties == NULL) { _ASSERT(pIMailMsgProperties && "pimsgGetIMsg() failed!!"); hr = E_POINTER; goto Exit; } // // Check and see if the message is still valid // if (!pmsgref->fShouldRetry()) goto Exit; // // Queue it to the local delivery queue // hr = m_asyncqPreLocalDeliveryQueue.HrQueueRequest(pmsgref, TRUE); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Enqueue to local delivery queue failed, 0x%08X", hr); pmsgref->RetryOnDelete(); goto Exit; } Exit: if (pIMailMsgProperties) pIMailMsgProperties->Release(); TraceFunctLeave(); } //---[ CAQSvrInst::HrSetSubmissionTimeIfNecessary ]---------------------------- // // // Description: // Sets the submission time on the message if it is not already set. // // Parameters: // IN pIMailMsgProperties message to stamp // Returns: // S_OK on success // History: // 8/13/98 - MikeSwa Created // 10/9/98 - MikeSwa - Changed behavior to that any pre-existing // properties will be maintained. // 5/16/2001 - dbraun changed to only set submission time // (was SetMessageExpiry) // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::HrSetSubmissionTimeIfNecessary(IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HrSetSubmissionTimeIfNecessary"); HRESULT hr = S_OK; DWORD dwTimeContext = 0; DWORD cbProp = 0; FILETIME ftSubmitTime; _ASSERT(pIMailMsgProperties); //Set arrival time hr = pIMailMsgProperties->GetProperty(IMMPID_MP_ARRIVAL_FILETIME, sizeof(FILETIME), &cbProp, (BYTE *) &ftSubmitTime); if (MAILMSG_E_PROPNOTFOUND == hr) { //Prop not set... we can set it m_qtTime.GetExpireTime(0, &ftSubmitTime, &dwTimeContext); hr = pIMailMsgProperties->PutProperty(IMMPID_MP_ARRIVAL_FILETIME, sizeof(FILETIME), (BYTE *) &ftSubmitTime); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to write arrival time to msg"); goto Exit; } } else if (FAILED(hr)) { goto Exit; } Exit: TraceFunctLeave(); return hr; } //---[ CAQSvrInst::CalcExpireTimeNDR ]----------------------------------------- // // // Description: // Calculates the message's NDR expire time // // Parameters: // IN ftSubmission time the message was submitted // IN fLocal Bool TRUE if we want local time, // otherwise returns remote // OUT pftExpire Used to return expire time // Returns: // - // History: // 5/16/2001 - dbraun created // //----------------------------------------------------------------------------- void CAQSvrInst::CalcExpireTimeNDR(FILETIME ftSubmission, BOOL fLocal, FILETIME *pftExpire) { if (fLocal) m_qtTime.GetExpireTime(ftSubmission, m_dwLocalNDRExpireMinutes, pftExpire); else m_qtTime.GetExpireTime(ftSubmission, m_dwNDRExpireMinutes, pftExpire); } //---[ CAQSvrInst::CalcExpireTimeDelay ]--------------------------------------- // // // Description: // Calculates the message's delay expire time // // Parameters: // IN ftSubmission time the message was submitted // IN fLocal Bool TRUE if we want local time, // otherwise returns remote // OUT pftExpire Used to return expire time // Returns: // - // History: // 5/16/2001 - dbraun created // //----------------------------------------------------------------------------- void CAQSvrInst::CalcExpireTimeDelay(FILETIME ftSubmission, BOOL fLocal, FILETIME *pftExpire) { if (fLocal) m_qtTime.GetExpireTime(ftSubmission, m_dwLocalDelayExpireMinutes, pftExpire); else m_qtTime.GetExpireTime(ftSubmission, m_dwDelayExpireMinutes, pftExpire); } //---[ CAQSvrInst::AsyncQueueRetry ]----------------------------------------- // // // Description: // Restarts an async queue after a failure. // Parameters: // dwQueueID Tells which queue to kick // PRELOCAL_QUEUE_ID Retries pre-local queue // PRECAT_QUEUE_ID Retries pre-cat queue // PREROUTING_QUEUE_ID Retries pre-routing queue // PRESUBMIT_QUEUE_ID Retries pre-submit queue // Returns: // - // History: // 8/17/98 - MikeSwa Created // 3/3/2000 - MikeSwa Modified to add presubmit queue // //----------------------------------------------------------------------------- void CAQSvrInst::AsyncQueueRetry(DWORD dwQueueID) { _ASSERT(CATMSGQ_SIG == m_dwSignature); if (fTryShutdownLock()) { if (PRELOCAL_QUEUE_ID == dwQueueID) { InterlockedDecrement((PLONG) &m_cLocalRetriesPending); m_asyncqPreLocalDeliveryQueue.StartRetry(); } else if (PRECAT_QUEUE_ID == dwQueueID) { InterlockedDecrement((PLONG) &m_cCatRetriesPending); m_asyncqPreCatQueue.StartRetry(); } else if (PREROUTING_QUEUE_ID == dwQueueID) { InterlockedDecrement((PLONG) &m_cRoutingRetriesPending); m_asyncqPreRoutingQueue.StartRetry(); } else if (PRESUBMIT_QUEUE_ID == dwQueueID) { InterlockedDecrement((PLONG) &m_cSubmitRetriesPending); m_asyncqPreSubmissionQueue.StartRetry(); } else { _ASSERT(0 && "Invalid Queue ID"); } ShutdownUnlock(); } } //---[ HrCreateBadMailPropertyFile ]------------------------------------------ // // // Description: // Creates a property stream for the given message. The property // stream file is given name the .BDP extension. // Parameters: // szDestFileBase The filename of the actual badmail file // pIMailMsgProperties The original message that is being badmailed // (may be NULL if it is a pickup dir file) // Returns: // S_OK on success // E_POINTER if szDestFileBase is NULL // History: // 8/17/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT HrCreateBadMailPropertyFile(LPSTR szDestFileBase, IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) NULL, "HrCreateBadMailPropertyFile"); HRESULT hr = S_OK; CHAR szOldExt[] = "123"; CHAR szNewExt[] = "BDP"; LPSTR szBadMailFileNameExt = NULL; DWORD cbBadMailFileName = 0; BOOL fShouldRestoreExtension = FALSE; CFilePropertyStream fstrm; IMailMsgBind *pIMailMsgBind = NULL; IMailMsgPropertyStream *pIMailMsgPropertyStream = NULL; _ASSERT(szDestFileBase); if (!szDestFileBase) { hr = E_POINTER; ErrorTrace((LPARAM) NULL, "Error NULL badmail filename passed in"); goto Exit; } if (!pIMailMsgProperties) //no-op goto Exit; hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgBind, (void **) &pIMailMsgBind); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to QI for IMailMsgBind - 0x%08X", hr); goto Exit; } _ASSERT(pIMailMsgBind); //Create the filename & file cbBadMailFileName = strlen(szDestFileBase); _ASSERT(cbBadMailFileName > 4); //must at least have . extenstion szBadMailFileNameExt = szDestFileBase + cbBadMailFileName-3; //szBadMailFileNameExt now points to the first character of the 3 char ext _ASSERT('.' == *(szBadMailFileNameExt-1)); _ASSERT(sizeof(szNewExt) == sizeof(szOldExt)); memcpy(szOldExt, szBadMailFileNameExt, sizeof(szOldExt)); memcpy(szBadMailFileNameExt, szNewExt, sizeof(szNewExt)); fShouldRestoreExtension = TRUE; hr = fstrm.HrInitialize(szDestFileBase); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to create badmail property stream - 0x%08X", hr); goto Exit; } hr = fstrm.QueryInterface(IID_IMailMsgPropertyStream, (void **) &pIMailMsgPropertyStream); _ASSERT(SUCCEEDED(hr)); //we control this totally if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to QI for IID_IMailMsgPropertyStream - 0x%08X", hr); goto Exit; } hr = pIMailMsgBind->GetProperties(pIMailMsgPropertyStream, MAILMSG_GETPROPS_COMPLETE, NULL); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "GetProperties failed with 0x%08X", hr); goto Exit; } Exit: if (fShouldRestoreExtension) { _ASSERT(szBadMailFileNameExt); memcpy(szBadMailFileNameExt, szOldExt, sizeof(szOldExt)); } if (pIMailMsgBind) pIMailMsgBind->Release(); if (pIMailMsgPropertyStream) pIMailMsgPropertyStream->Release(); TraceFunctLeave(); return hr; } //---[ HrCreateBadMailReasonFile ]--------------------------------------------- // // // Description: // Creates a file in the badmail directory that expains why the given // message was badmailed, and dump the sender and recipient as well. Uses // the extension BMR (BadMailReason) to differentiate from the content. // Parameters: // szDestFileBase The filename of the actual badmail file // hrReason The reason the badmail is being created // pIMailMsgProperties The original message that is being badmailed // (may be NULL if it is a pickup dir file) // Returns: // S_OK on success // E_POINTER if szDestFileBase is NULL // History: // 8/16/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT HrCreateBadMailReasonFile(IN LPSTR szDestFileBase, IN HRESULT hrReason, IN IMailMsgProperties *pIMailMsgProperties) { TraceFunctEnterEx((LPARAM) NULL, "HrCreateBadMailReasonFile"); HRESULT hr = S_OK; HRESULT hrErrorLogged = hrReason; WCHAR wszBadmailReason[1000] = L""; //Localized hrReason string WCHAR wszReasonBuffer[2000] = L""; WCHAR wszErrorCode[] = L"0x12345678 "; WCHAR wszErrorCodeMessage[200] = L""; CHAR szPropBuffer[1000] = ""; DWORD dwErr = 0; BOOL fWriteBadmailReason = FALSE; BOOL fShouldRestoreExtension = FALSE; DWORD cReasonBuffer = 0; LPSTR szBadMailFileNameExt = NULL; CHAR szOldExt[] = "123"; CHAR szNewExt[] = "BDR"; DWORD cbBadMailFileName = 0; HANDLE hBadMailFile = NULL; DWORD cbBytesWritten = 0; DWORD dwFacility = 0; LPWSTR rgwszArgList[32]; const WCHAR wcszBlankLine[] = L"\r\n"; IMailMsgRecipients *pIMailMsgRecipients = NULL; DWORD cRecips = 0; DWORD iCurrentRecip = 0; _ASSERT(szDestFileBase); if (!szDestFileBase) { ErrorTrace((LPARAM) NULL, "Invalid destination file for badmail"); hr = E_POINTER; goto Exit; } if (!g_hAQInstance) { _ASSERT(g_hAQInstance && "This should always be set in DLL main"); ErrorTrace((LPARAM) NULL, "Error, g_hAQInstance is NULL"); hr = E_FAIL; goto Exit; } //Create the filename & file cbBadMailFileName = strlen(szDestFileBase); _ASSERT(cbBadMailFileName > 4); //must at least have . extenstion szBadMailFileNameExt = szDestFileBase + cbBadMailFileName-3; //szBadMailFileNameExt now points to the first character of the 3 char ext _ASSERT('.' == *(szBadMailFileNameExt-1)); _ASSERT(sizeof(szNewExt) == sizeof(szOldExt)); memcpy(szOldExt, szBadMailFileNameExt, sizeof(szOldExt)); memcpy(szBadMailFileNameExt, szNewExt, sizeof(szNewExt)); fShouldRestoreExtension = TRUE; hBadMailFile = CreateFile(szDestFileBase, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (INVALID_HANDLE_VALUE ==hBadMailFile ) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Unable to create badmail reason file - err 0x%08X - file %s", hr, szDestFileBase); goto Exit; } //Figure out the reason for the failure if (SUCCEEDED(hrErrorLogged)) { //someone is being lazy about setting the error reason _ASSERT(0 && "No badmail reason given"); ErrorTrace((LPARAM) NULL, "Non-failing badmail HRESULT given 0x%08X", hrErrorLogged); //Substitute a generic error so we don't have something obnoxious like //"The operation completed succesfully" appear in the badmail file hrErrorLogged = E_FAIL; } //Write the error code in "0x00000000" format wsprintfW(wszErrorCode, L"0x%08X", hrErrorLogged); dwFacility = ((0x0FFF0000 & hrErrorLogged) >> 16); //If it is not ours... then "un-HRESULT" it if (dwFacility != FACILITY_ITF) hrErrorLogged &= 0x0000FFFF; dwErr = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, g_hAQInstance, hrErrorLogged, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wszBadmailReason, sizeof(wszBadmailReason)/sizeof(WCHAR), NULL); if (!dwErr) { //We should fall back on a numeric error that we were given ErrorTrace((LPARAM) NULL, "Error: unable to format badmail message 0x%08X, error is %d", hrErrorLogged, GetLastError()); wcscpy(wszBadmailReason, wszErrorCode); } else { //Get rid of trailing newline cReasonBuffer = wcslen(wszBadmailReason); cReasonBuffer--; while(iswspace(wszBadmailReason[cReasonBuffer])) { wszBadmailReason[cReasonBuffer] = '\0'; cReasonBuffer--; } cReasonBuffer = 0; } ErrorTrace((LPARAM) NULL, "Generating badmail because: %S", wszBadmailReason); rgwszArgList[0] = wszBadmailReason; rgwszArgList[1] = NULL; dwErr = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY, g_hAQInstance, PHATQ_BADMAIL_REASON, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wszReasonBuffer, sizeof(wszReasonBuffer)/sizeof(WCHAR), (va_list *) rgwszArgList); if (!dwErr) { ErrorTrace((LPARAM) NULL, "Error: unable to format PHATQ_BADMAIL_REASON, error is %d", GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError()); wcscpy(wszReasonBuffer, wszBadmailReason); } cReasonBuffer = wcslen(wszReasonBuffer); if (!WriteFile(hBadMailFile, (PVOID) wszReasonBuffer, cReasonBuffer*sizeof(WCHAR), &cbBytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Error writing to badmail reason file - erro 0x%08X - file %s", hr, szDestFileBase); goto Exit; } //Write the actual error code in 0x00000000 form so tools can parse it out rgwszArgList[0] = wszErrorCode; rgwszArgList[1] = NULL; wcscpy(wszReasonBuffer, wcszBlankLine); dwErr = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY, g_hAQInstance, PHATQ_BADMAIL_ERROR_CODE, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wszReasonBuffer+(sizeof(wcszBlankLine)-1)/sizeof(WCHAR), (sizeof(wszReasonBuffer)-sizeof(wcszBlankLine))/sizeof(WCHAR), (va_list *) rgwszArgList); wcscat(wszReasonBuffer, wcszBlankLine); cReasonBuffer = wcslen(wszReasonBuffer); if (!WriteFile(hBadMailFile, (PVOID) wszReasonBuffer, cReasonBuffer*sizeof(WCHAR), &cbBytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Error writing to badmail reason file - erro 0x%08X - file %s", hr, szDestFileBase); goto Exit; } //All the rest requries access to an actual message... if we don't //have one, bail if (!pIMailMsgProperties) goto Exit; //Write Sender of message hr = pIMailMsgProperties->GetStringA(IMMPID_MP_SENDER_ADDRESS_SMTP, sizeof(szPropBuffer), szPropBuffer); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "ERROR: Unable to get sender of IMailMsg 0x%08X", pIMailMsgProperties); hr = S_OK; //just don't display sender } else { rgwszArgList[0] = (LPWSTR) szPropBuffer; rgwszArgList[1] = NULL; wcscpy(wszReasonBuffer, wcszBlankLine); dwErr = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY, g_hAQInstance, PHATQ_BADMAIL_SENDER, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wszReasonBuffer+(sizeof(wcszBlankLine)-1)/sizeof(WCHAR), (sizeof(wszReasonBuffer)-sizeof(wcszBlankLine))/sizeof(WCHAR), (va_list *) rgwszArgList); wcscat(wszReasonBuffer, wcszBlankLine); cReasonBuffer = wcslen(wszReasonBuffer); if (!WriteFile(hBadMailFile, (PVOID) wszReasonBuffer, cReasonBuffer*sizeof(WCHAR), &cbBytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Error writing to badmail reason file - erro 0x%08X - file %s", hr, szDestFileBase); goto Exit; } } //Write Message recipients hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgRecipients, (PVOID *) &pIMailMsgRecipients); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to query interface for recip interface - 0x%08X", hr); goto Exit; } hr = pIMailMsgRecipients->Count(&cRecips); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to get recipient count - 0x%08X", hr); goto Exit; } //If we don't have any recipients, bail if (!cRecips) goto Exit; //Write the localized text wcscpy(wszReasonBuffer, wcszBlankLine); dwErr = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, g_hAQInstance, PHATQ_BADMAIL_RECIPIENTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), wszReasonBuffer+(sizeof(wcszBlankLine)-1)/sizeof(WCHAR), (sizeof(wszReasonBuffer)-sizeof(wcszBlankLine))/sizeof(WCHAR), NULL); cReasonBuffer = wcslen(wszReasonBuffer); if (!WriteFile(hBadMailFile, (PVOID) wszReasonBuffer, cReasonBuffer*sizeof(WCHAR), &cbBytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Error writing to badmail reason file - erro 0x%08X - file %s", hr, szDestFileBase); goto Exit; } //Loop over SMTP recips and dump them in the file for (iCurrentRecip = 0; iCurrentRecip < cRecips; iCurrentRecip++) { hr = pIMailMsgRecipients->GetStringA(iCurrentRecip, IMMPID_RP_ADDRESS_SMTP, sizeof(szPropBuffer), szPropBuffer); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Unable to get SMTP address for recip %d - 0x%08X", iCurrentRecip, hr); hr = S_OK; continue; } cReasonBuffer = wsprintfW(wszReasonBuffer, L"\t%S%s", szPropBuffer, wcszBlankLine); if (!WriteFile(hBadMailFile, (PVOID) wszReasonBuffer, cReasonBuffer*sizeof(WCHAR), &cbBytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "Error writing to badmail reason file - error 0x%08X - file %s", hr, szDestFileBase); goto Exit; } } Exit: if (fShouldRestoreExtension) { _ASSERT(szBadMailFileNameExt); memcpy(szBadMailFileNameExt, szOldExt, sizeof(szOldExt)); } if (pIMailMsgRecipients) pIMailMsgRecipients->Release(); if (hBadMailFile != INVALID_HANDLE_VALUE) _VERIFY(CloseHandle(hBadMailFile)); TraceFunctLeave(); return hr; } //---[ CAQSvrInst::HandleBadMail ]--------------------------------------------- // // // Description: // Handles mail that needs to be placed in the badmail directory (or // equivalent). // Parameters: // IN pIMailMsgProperties that needs to be badmail'd // IN fUseIMailMsgPropeties -- use IMAilMsgProps if set else use szFilename // IN szFileName Name of badmail file (if no msg can be supplied) // IN hrReason - HRESULT (defined in aqerr) that describes reason // Eventually, we may log this information to the badmail // file (or recipient) // IN fHasRoutingLock - TRUE if this thread holds routing lock // Returns: // - // History: // 10/8/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CAQSvrInst::HandleBadMail(IN IMailMsgProperties *pIMailMsgProperties, IN BOOL fUseIMailMsgProperties, IN LPSTR szOriginalFileName, IN HRESULT hrReason, IN BOOL fHasRoutingLock) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::HandleBadMail"); HRESULT hr = S_OK; LPSTR szFullPathName = NULL; LPSTR szFileName = NULL; BOOL fDataLocked = FALSE; BOOL fDone = TRUE; HANDLE hFile = INVALID_HANDLE_VALUE; PFIO_CONTEXT pFIOContext = NULL; FILETIME ftCurrent; CRefCountedString *prstrBadMailDir = NULL; MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_BADMAIL; m_pISMTPServer->WriteLog( &msgTrackInfo, pIMailMsgProperties, NULL, NULL ); InterlockedIncrement((PLONG) &m_cTotalMsgsBadmailed); if (!fHasRoutingLock) { m_slPrivateData.ShareLock(); fDataLocked = TRUE; } else { m_slPrivateData.AssertIsLocked(); } if (m_prstrBadMailDir) { prstrBadMailDir = m_prstrBadMailDir; prstrBadMailDir->AddRef(); } else { LogAQEvent(AQUEUE_E_NO_BADMAIL_DIR, NULL, pIMailMsgProperties, NULL); goto Exit; } if (fDataLocked) { m_slPrivateData.ShareUnlock(); fDataLocked = FALSE; } szFullPathName = (LPSTR) pvMalloc(sizeof(CHAR) * (UNIQUEUE_FILENAME_BUFFER_SIZE + prstrBadMailDir->cbStrlen())); if (!szFullPathName) { LogAQEvent(AQUEUE_E_BADMAIL, NULL, pIMailMsgProperties, NULL); goto Exit; } memcpy(szFullPathName, prstrBadMailDir->szStr(), prstrBadMailDir->cbStrlen()); if (szFullPathName[prstrBadMailDir->cbStrlen()-1] != '\\') { _ASSERT(0 && "Malformed badmail config"); LogAQEvent(AQUEUE_E_NO_BADMAIL_DIR, NULL, pIMailMsgProperties, NULL); goto Exit; } szFileName = szFullPathName + prstrBadMailDir->cbStrlen(); //If we have a msg use it if (pIMailMsgProperties && fUseIMailMsgProperties) { //Loop while trying to generate a unique file name do { fDone = TRUE; GetExpireTime(0, &ftCurrent, NULL); GetUniqueFileName(&ftCurrent, szFileName, "BAD"); //Create file and write MsgContent to it hFile = CreateFile( szFullPathName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (INVALID_HANDLE_VALUE == hFile) { if (ERROR_ALREADY_EXISTS == GetLastError()) { //Try a new file name fDone = FALSE; continue; } //Other we are hosed... log an event LogAQEvent(AQUEUE_E_BADMAIL, NULL, pIMailMsgProperties, NULL); goto Exit; } _ASSERT(hFile); //should return INVALID_HANDLE_VALUE on failure } while (!fDone); if (hFile != INVALID_HANDLE_VALUE) pFIOContext = AssociateFile(hFile); if (!pFIOContext || FAILED(pIMailMsgProperties->CopyContentToFile(pFIOContext, NULL))) { //Copy failed log event LogAQEvent(AQUEUE_E_BADMAIL, NULL, pIMailMsgProperties, NULL); } } else if (szOriginalFileName) { //Otherwise (no msg)... just do a movefile _ASSERT(szFullPathName[prstrBadMailDir->cbStrlen()-1] == '\\'); szFullPathName[prstrBadMailDir->cbStrlen()-1] = '\0'; if (!MoveFileEx(szOriginalFileName, szFullPathName, MOVEFILE_COPY_ALLOWED)) { //MoveFile failed... try renaming with a unique file name szFullPathName[prstrBadMailDir->cbStrlen()-1] = '\\'; GetExpireTime(0, &ftCurrent, NULL); GetUniqueFileName(&ftCurrent, szFileName, "BAD"); if (rename(szOriginalFileName, szFullPathName)) LogAQEvent(AQUEUE_E_BADMAIL, NULL, NULL, szOriginalFileName); } } hr = HrCreateBadMailReasonFile(szFullPathName, hrReason, pIMailMsgProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to make badmail reason file - hr 0x%08X", hr); } hr = HrCreateBadMailPropertyFile(szFullPathName, pIMailMsgProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to make badmail property file - hr 0x%08X", hr); } Exit: if (fDataLocked) m_slPrivateData.ShareUnlock(); if (prstrBadMailDir) prstrBadMailDir->Release(); if (szFullPathName) FreePv(szFullPathName); if (NULL != pFIOContext) ReleaseContext(pFIOContext); TraceFunctLeave(); } //---[ HandleAQFailure ]-------------------------------------------------------- // // // Description: // Function to handle AQ failures that would result in loss of data // or messages if unhandled. Meant to be a substitute for // _ASSERT(SUCCEEDED(hr)). // // Note: Msgs are still Turfed for M2 // Parameters: // eaqfFailureSituation Enum that describes the failure situation // as well as what the context is. // hr HRSULT that triggered failure condition // pIMailMsgProperties MailMsgProperties // Returns: // - // History: // 8/25/98 - MikeSwa Created // 10/8/98 - MikeSwa Moved to CAQSvrInst // //----------------------------------------------------------------------------- void CAQSvrInst::HandleAQFailure(eAQFailure eaqfFailureSituation, HRESULT hr, IMailMsgProperties *pIMailMsgProperties) { _ASSERT(eaqfFailureSituation < AQ_FAILURE_NUM_SITUATIONS); InterlockedIncrement((PLONG) &g_cTotalAQFailures); InterlockedIncrement((PLONG) &(g_rgcAQFailures[eaqfFailureSituation])); BOOL fCanRetry = fShouldRetryMessage(pIMailMsgProperties); MSG_TRACK_INFO msgTrackInfo; ZeroMemory( &msgTrackInfo, sizeof( msgTrackInfo ) ); msgTrackInfo.dwEventId = MTE_AQ_FAILURE; m_pISMTPServer->WriteLog( &msgTrackInfo, pIMailMsgProperties, NULL, NULL ); switch(eaqfFailureSituation) { case(AQ_FAILURE_CANNOT_NDR_UNRESOLVED_RECIPS): LogAQEvent(AQUEUE_E_DSN_FAILURE, NULL, pIMailMsgProperties, NULL); //drop through to default case default: // // Throw the message in the last-ditch retry queue if the following are true: // - We had a failure // - We are not shutting down // - We can retry the message (e.g., it has not been deleted) // if (FAILED(hr) && (AQUEUE_E_SHUTDOWN != hr) && fCanRetry) { InterlockedIncrement((PLONG) &m_cCurrentResourceFailedMsgsPendingRetry); m_fmq.HandleFailedMailMsg(pIMailMsgProperties); } } } //---[ CAQSvrInst::LogAQEvent ]------------------------------------------------ // // // Description: // General event logging mechanism for AQ // Parameters: // hrEventReason AQUEUE HRESULT describing event // pmsgref CMsgRef of msg for event (can be NULL) // pIMailMsgProperties pIMailMsgProperties for event (can be NULL) // szFileName Filename if no msgs provided (can be NULL) // Returns: // - // History: // 10/9/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CAQSvrInst::LogAQEvent(HRESULT hrEventReason, CMsgRef *pmsgref, IMailMsgProperties *pIMailMsgProperties, LPSTR szFileName) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::LogAQEvent"); switch (hrEventReason) { //$$TODO - Add actual event callouts here case (S_OK): //Added to remove switch compile warnings default: ErrorTrace((LPARAM) this, "EVENT: Generic AQueue event - 0x%08X", hrEventReason); } TraceFunctLeave(); } //+------------------------------------------------------------ // // Function: CAQSvrInst::TriggerPreCategorizeEvent // // Synopsis: Fire the pre-cat server event // // Arguments: // pIMailMsgProperties: the IMailMsgProperties interface of the mailmsg // // Returns: NOTHING // // History: // jstamerj 1998/11/24 20:07:58: Created. // //------------------------------------------------------------- VOID CAQSvrInst::TriggerPreCategorizeEvent( IN IMailMsgProperties *pIMailMsgProperties) { HRESULT hr; EVENTPARAMS_PRECATEGORIZE Params; TraceFunctEnterEx((LPARAM)pIMailMsgProperties, "CAQSvrInst::TriggerPreCategorizeEvent"); _ASSERT(pIMailMsgProperties); Params.pfnCompletion = MailTransport_Completion_PreCategorization; Params.pCCatMsgQueue = (PVOID) this; Params.pIMailMsgProperties = pIMailMsgProperties; // // Addref here, release in completion // pIMailMsgProperties->AddRef(); AddRef(); // // keep a count of messages in the pre-cat event // InterlockedIncrement((LPLONG) &m_cCurrentMsgsPendingPreCatEvent); if(m_pISMTPServer) { hr = TriggerServerEvent( SMTP_MAILTRANSPORT_PRECATEGORIZE_EVENT, &Params); DebugTrace((LPARAM)this, "TriggerServerEvent returned hr %08lx", hr); } if((m_pISMTPServer == NULL) || (FAILED(hr))) { ErrorTrace((LPARAM)this, "Unable to dispatch server event; calling completion routine directly"); DebugTrace((LPARAM)this, "hr is %08lx", hr); // // Call completion routine directly // _VERIFY(SUCCEEDED(PreCatEventCompletion(S_OK, &Params))); } TraceFunctLeaveEx((LPARAM)this); } //+------------------------------------------------------------ // // Function: CAQSvrInst::PreCatEventCompletion // // Synopsis: Called by SEO upon completipon of the precat event // // Arguments: // pIMailMsgProperties: the IMailMsgProperties interface of the mailmsg // // Returns: // S_OK: Success // // History: // jstamerj 1998/11/24 20:17:44: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::PreCatEventCompletion( IN HRESULT hrStatus, IN PEVENTPARAMS_PRECATEGORIZE pParams) { HRESULT hr; _ASSERT(pParams); _ASSERT(pParams->pIMailMsgProperties); TraceFunctEnterEx((LPARAM)pParams->pIMailMsgProperties, "CAQSvrInst::PreCatEventCompletion"); DebugTrace((LPARAM)pParams->pIMailMsgProperties, "hrStatus is %08lx", hrStatus); // // Decrease count of msgs in pre-cat event // InterlockedDecrement((LPLONG) &m_cCurrentMsgsPendingPreCatEvent); // // Update the message status and check for abort/badmail // hr = SetNextMsgStatus(MP_STATUS_SUBMITTED, pParams->pIMailMsgProperties); if (hr == S_OK) //anything else implies that the message has been handled { //Only submit to categorizer if things message was not turfed. hr = SubmitMessageToCategorizer(pParams->pIMailMsgProperties); if(FAILED(hr)) { _ASSERT((hr == AQUEUE_E_SHUTDOWN) && "SubmitMessageToCategorizer failed."); ErrorTrace((LPARAM)pParams->pIMailMsgProperties, "SubmitMessageToCategorizer returned hr %08lx", hr); } } // // Release references added in TriggerPreCategorizeEvent // pParams->pIMailMsgProperties->Release(); Release(); TraceFunctLeaveEx((LPARAM)pParams->pIMailMsgProperties); return S_OK; } //+------------------------------------------------------------ // // Function: MailTransport_Completion_PreCategorization // // Synopsis: SEO will call this routine after all sinks for // OnPreCategoriztion have been handeled // // Arguments: // pvContext: Context passed into TriggerServerEvent // // Returns: // S_OK: Success // // History: // jstamerj 1998/11/24 20:26:51: Created // //------------------------------------------------------------- HRESULT MailTransport_Completion_PreCategorization( HRESULT hrStatus, PVOID pvContext) { TraceFunctEnter("MailTransport_Completion_PreCategorization"); PEVENTPARAMS_PRECATEGORIZE pParams = (PEVENTPARAMS_PRECATEGORIZE) pvContext; CAQSvrInst *paqinst = (CAQSvrInst *) pParams->pCCatMsgQueue; TraceFunctLeave(); return paqinst->PreCatEventCompletion( hrStatus, pParams); } //---[ CAQSvrInst::SetCallbackTime ]------------------------------------------- // // // Description: // Set a callback time based on a number of minutes. // Parameters: // IN pCallbackFn Ptr to a callback function // IN pvContext Context pass to callback function // IN dwCallbackMinutes Minutes to wait before calling callback // function. // Returns: // // History: // 12/29/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::SetCallbackTime(IN PSRVFN pCallbackFn, IN PVOID pvContext, IN DWORD dwCallbackMinutes) { HRESULT hr = S_OK; if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } _ASSERT(m_pConnMgr); if (m_pConnMgr) hr = m_pConnMgr->SetCallbackTime(pCallbackFn, pvContext, dwCallbackMinutes); ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::SetCallbackTime ]------------------------------------------- // // // Description: // Set a callback time based on a filetime. // Parameters: // // Returns: // // History: // 12/29/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CAQSvrInst::SetCallbackTime(IN PSRVFN pCallbackFn, IN PVOID pvContext, IN FILETIME *pft) { HRESULT hr = S_OK; DWORD dwCallbackMinutes = 0; DWORD dwTimeContext = 0; FILETIME ftCurrentTime; LARGE_INTEGER *pLargeIntCurrentTime = (LARGE_INTEGER *) &ftCurrentTime; LARGE_INTEGER *pLargeIntCallbackTime = (LARGE_INTEGER *) pft; _ASSERT(pCallbackFn); _ASSERT(pvContext); _ASSERT(pft); if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } if (!fInPast(pft, &dwTimeContext)) { //Get current time using previous context (so current time is the same) GetExpireTime(0, &ftCurrentTime, &dwTimeContext); //the current time must be less than the callback time _ASSERT(pLargeIntCurrentTime->QuadPart < pLargeIntCallbackTime->QuadPart); pLargeIntCurrentTime->QuadPart = pLargeIntCallbackTime->QuadPart - pLargeIntCurrentTime->QuadPart; pLargeIntCurrentTime->QuadPart /= (LONGLONG) 600000000; //If the callback time is > 2 billion minutes... I'd //like to know about it in debug builds _ASSERT(!pLargeIntCurrentTime->HighPart); dwCallbackMinutes = pLargeIntCurrentTime->LowPart; //The only current application is for deferred delivery... I would like //to see the internal test situations that result in a deferred delivery _ASSERT(dwCallbackMinutes < (60*24*7)); // // If we have rounded down to 0 minutes, we should call back in 1 // otherwise we can end up in a tight loop. If the call merely wants // another thread, they should use the AsyncWorkQueue // if (!dwCallbackMinutes) dwCallbackMinutes = 1; } else { //If in past... callback as soon as possible, but don't use this thread, in //case there are locking complications (CShareLockNH is non-reentrant). dwCallbackMinutes = 1; } _ASSERT(m_pConnMgr); if (m_pConnMgr) hr = m_pConnMgr->SetCallbackTime(pCallbackFn, pvContext, dwCallbackMinutes); ShutdownUnlock(); Exit: return hr; } //---[ CAQSvrInst::SetLinkState ]---------------------------------------------- // // // Description: // Implements IMailTransportRouterSetLinkState::SetLinkState // Parameters: // IN szLinkDomainName The Domain Name of the link (next hop) // IN guidRouterGUID The GUID ID of the router // IN dwScheduleID The schedule ID link // IN szConnectorName The connector name given by the router // IN dwFlagsToSet Link State Flags to set // IN dwFlagsToUnset Link State Flags to unset // IN pftNextScheduledConnection Next scheduled connection time. // Returns: // S_OK on success // E_INVALIDARG if szLinkDomainName is NULL // AQUEUE_E_SHUTDOWN if shutting down. // History: // 1/9/99 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CAQSvrInst::SetLinkState( IN LPSTR szLinkDomainName, IN GUID guidRouterGUID, IN DWORD dwScheduleID, IN LPSTR szConnectorName, IN DWORD dwSetLinkState, IN DWORD dwUnsetLinkState, IN FILETIME *pftNextScheduledConnection, IN IMessageRouter *pMessageRouter) { HRESULT hr = S_OK; BOOL fLocked = FALSE; DWORD cbLinkDomainName = 0; CDomainEntry *pdentry = NULL; CLinkMsgQueue *plmq = NULL; CAQScheduleID aqsched(guidRouterGUID, dwScheduleID); BOOL fRemoveOwnedSchedule = TRUE; if (!szLinkDomainName) { hr = E_INVALIDARG; goto Exit; } if (!fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; cbLinkDomainName = lstrlen(szLinkDomainName); // see if they want to create a new link if (dwSetLinkState & LINK_STATE_CREATE_IF_NECESSARY) { // creating a link requires a pmessagerouter if (pMessageRouter == NULL) { hr = E_POINTER; } else { LinkFlags lf; if (dwSetLinkState & LINK_STATE_TYPE_INTERNAL_SMTP) { lf = eLinkFlagsInternalSMTPLinkInfo; } else { _ASSERT(dwSetLinkState & LINK_STATE_TYPE_EXTERNAL_SMTP); lf = eLinkFlagsExternalSMTPLinkInfo; } dwSetLinkState &= ~(LINK_STATE_TYPE_INTERNAL_SMTP | LINK_STATE_TYPE_EXTERNAL_SMTP); // get the link, and create it if it doesn't exist and they want to // have a new link created hr = m_dmt.HrGetOrCreateLink(szLinkDomainName, cbLinkDomainName, dwScheduleID, szConnectorName, pMessageRouter, TRUE, lf, &plmq, &fRemoveOwnedSchedule); } } else { cbLinkDomainName = lstrlen(szLinkDomainName); hr = HrGetDomainEntry(cbLinkDomainName, szLinkDomainName, &pdentry); if (SUCCEEDED(hr)) hr = pdentry->HrGetLinkMsgQueue(&aqsched, &plmq); } if (FAILED(hr)) goto Exit; // this bit is only used above, so remove it dwSetLinkState &= ~LINK_STATE_CREATE_IF_NECESSARY; dwUnsetLinkState &= ~LINK_STATE_CREATE_IF_NECESSARY; _ASSERT(plmq); // // If this operation is dis-allowing scheduled connections... we should // record when the next scheduled attempt will be. We should also do // this before we modify the link state, so that the queue admin does // not display a scheduled queue without a next connection time. // if (pftNextScheduledConnection && (pftNextScheduledConnection->dwLowDateTime || pftNextScheduledConnection->dwHighDateTime)) { plmq->SetNextScheduledConnection(pftNextScheduledConnection); } //filter out the reserved bits for this "public" API plmq->dwModifyLinkState(~LINK_STATE_RESERVED & dwSetLinkState, ~LINK_STATE_RESERVED & dwUnsetLinkState); // schedule a callback if one was requested if (pftNextScheduledConnection->dwLowDateTime != 0 || pftNextScheduledConnection->dwHighDateTime != 0) { //callback with next attempt plmq->AddRef(); //Addref self as context hr = SetCallbackTime( CLinkMsgQueue::ScheduledCallback, plmq, pftNextScheduledConnection); if (FAILED(hr)) plmq->Release(); //callback will not happen... release context } Exit: if (fLocked) ShutdownUnlock(); if (pdentry) pdentry->Release(); if (plmq) plmq->Release(); // // If we have not passed ownership of the shedule ID to a link, // then we are responsible for releasing it. // if (fRemoveOwnedSchedule) { IMessageRouterLinkStateNotification *pILinkStateNotify = NULL; HRESULT hrLinkStateNotify = pMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); _ASSERT( SUCCEEDED( hrLinkStateNotify)); FILETIME ftNotUsed = {0,0}; DWORD dwSetNotUsed = LINK_STATE_NO_ACTION; DWORD dwUnsetNotUsed = LINK_STATE_NO_ACTION; hrLinkStateNotify = pILinkStateNotify->LinkStateNotify( szLinkDomainName, guidRouterGUID, dwScheduleID, szConnectorName, LINK_STATE_LINK_NO_LONGER_USED, 0, //consecutive failures &ftNotUsed, &dwSetNotUsed, &dwUnsetNotUsed); _ASSERT( SUCCEEDED( hrLinkStateNotify)); if ( NULL != pILinkStateNotify) { pILinkStateNotify->Release(); } } return hr; } //---[ CAQSvrInst::prstrGetDefaultDomain ]------------------------------------- // // // Description: // Returns the ref-counted string for the default domain // Parameters: // - // Returns: // See above // History: // 2/23/99 - MikeSwa Created // //----------------------------------------------------------------------------- CRefCountedString *CAQSvrInst::prstrGetDefaultDomain() { CRefCountedString *prstrDefaultDomain = NULL; m_slPrivateData.ShareLock(); if (m_prstrDefaultDomain) m_prstrDefaultDomain->AddRef(); prstrDefaultDomain = m_prstrDefaultDomain; m_slPrivateData.ShareUnlock(); return prstrDefaultDomain; } //+------------------------------------------------------------ // // Function: ScheduleInternalRetry // // Synopsis: Schedule categorizer retry if necessary // // Arguments: None // // Returns: Nothing // // History: // jstamerj 2000/06/08 17:31:30: Created. // 1/16/2001 - MikeSwa Modified to handle all internal retries // //------------------------------------------------------------- VOID CAQSvrInst::ScheduleInternalRetry(DWORD dwLinkType) { TraceFunctEnterEx((LPARAM) this, "CAQSvrInst::ScheduleInternalRetry"); DWORD *pcRetriesPending = NULL; PSRVFN pCallbackFn = NULL; DWORD cCallbackMinutes = 0; FILETIME ftCallback = {0,0}; BOOL fHasLock = FALSE; CQueueAdminRetryNotify *pqapiret = NULL; // // First try to get the shutdown lock before accessing // anything that may go away during deinitialization (like m_pConnMgr) // if (!fTryShutdownLock()) goto Exit; fHasLock = TRUE; switch (dwLinkType) { case LI_TYPE_PENDING_ROUTING: pcRetriesPending = &m_cRoutingRetriesPending; pCallbackFn = RoutingRetry; cCallbackMinutes = g_cRoutingRetryMinutes; pqapiret = (CQueueAdminRetryNotify *) m_dmt.pmmaqGetPreRouting(); break; case LI_TYPE_LOCAL_DELIVERY: pcRetriesPending = &m_cLocalRetriesPending; pCallbackFn = LocalDeliveryRetry; cCallbackMinutes = g_cLocalRetryMinutes; pqapiret = (CQueueAdminRetryNotify *) m_dmt.plmqGetLocalLink(); break; case LI_TYPE_PENDING_CAT: pcRetriesPending = &m_cCatRetriesPending; pCallbackFn = CatRetry; cCallbackMinutes = g_cCatRetryMinutes; pqapiret = (CQueueAdminRetryNotify *) m_dmt.pmmaqGetPreCategorized(); break; case LI_TYPE_PENDING_SUBMIT: pcRetriesPending = &m_cSubmitRetriesPending; pCallbackFn = SubmitRetry; cCallbackMinutes = g_cSubmissionRetryMinutes; pqapiret = (CQueueAdminRetryNotify *) m_dmt.pmmaqGetPreSubmission(); break; default: // // Someone has called with a bogus callback // _ASSERT(0 && "Unspecified callback for internal retry"); ErrorTrace((LPARAM) this, "Unspecified callback for internal retry 0x%08X", dwLinkType); goto Exit; } _ASSERT(pcRetriesPending); if (pcRetriesPending && !*pcRetriesPending) { _ASSERT(pCallbackFn); _ASSERT(cCallbackMinutes); // // Say that we are requesting a callback // InterlockedIncrement((PLONG) pcRetriesPending); m_pConnMgr->SetCallbackTime(pCallbackFn, this, cCallbackMinutes); // // Get the expire time... so we can update our retry time // m_qtTime.GetExpireTime(cCallbackMinutes, &ftCallback, NULL); // // Update retry time // if (pqapiret) pqapiret->SetNextRetry(&ftCallback); } Exit: if (pqapiret) pqapiret->Release(); if (fHasLock) ShutdownUnlock(); TraceFunctLeave(); } // CAQSvrInst::ScheduleInternalRetry //+------------------------------------------------------------ // // Function: CAQSvrInst::HrAllocBoundMessage // // Synopsis: // Allocates a bound message // // Arguments: // ppMsg: Out param for Allocated mailmsg // phContent: Out param for content handle. Handle is managed by mailmsg // // Returns: // S_OK: Success // E_FAIL: No ISMTPServer is available // error from SMTP // // History: // jstamerj 2001/05/11 15:39:16: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::HrAllocBoundMessage( OUT IMailMsgProperties **ppMsg, OUT PFIO_CONTEXT *phContent) { HRESULT hr = S_OK; ISMTPServerInternal *pISMTPInternal = NULL; TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::HrAllocBoundMessage"); if(m_pISMTPServer == NULL) { hr = E_FAIL; goto CLEANUP; } hr = m_pISMTPServer->QueryInterface( IID_ISMTPServerInternal, (LPVOID *) &pISMTPInternal); if(FAILED(hr)) goto CLEANUP; hr = pISMTPInternal->AllocBoundMessage( ppMsg, phContent); CLEANUP: if(pISMTPInternal) pISMTPInternal->Release(); DebugTrace((LPARAM)this, "returning %08lx", hr); TraceFunctLeaveEx((LPARAM)this); return hr; } // CAQSvrInst::HrAllocBoundMessage //+------------------------------------------------------------ // // Function: CAQSvrInst::HrSubmitDSN // // Synopsis: Acceps a message submitted by a DSN sink // // Arguments: // pIMsgOrig: Original message (for which a DSN is being generated) // dwDSNAction: Indicates the type of DSN // cRecipsDSNd: Number of recipients in the DSN. // pDSNMsg: The DSN mailmsg object // // Returns: // S_OK: Success // // History: // jstamerj 2000/12/08 13:42:17: Created. // //------------------------------------------------------------- HRESULT CAQSvrInst::HrSubmitDSN( CDSNParams *pdsnparams, DWORD dwDSNAction, DWORD cRecipsDSNd, IMailMsgProperties *pDSNMsg) { HRESULT hr = S_OK; DWORD cCurrent = 0; TraceFunctEnterEx((LPARAM)this, "CAQSvrInst::HrSubmitDSN"); DebugTrace((LPARAM)this, "dwDSNAction: %08lx", dwDSNAction); DebugTrace((LPARAM)this, "cRecipsDSNd: %ld", cRecipsDSNd); if(pDSNMsg == NULL) { hr = E_INVALIDARG; goto CLEANUP; } if ((DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL) & dwDSNAction) { MSG_TRACK_INFO msgTrackInfo; cCurrent = InterlockedIncrement((PLONG) &m_cNDRs); DebugTrace((LPARAM) this, "INFO: NDR Generated - total %d", cCurrent); ZeroMemory(&msgTrackInfo, sizeof(MSG_TRACK_INFO)); msgTrackInfo.dwEventId = MTE_NDR_ALL; msgTrackInfo.pszPartnerName = "aqueue"; msgTrackInfo.dwRcptReportStatus = MP_STATUS_ABORT_DELIVERY; m_pISMTPServer->WriteLog(&msgTrackInfo, pdsnparams->pIMailMsgProperties, NULL, NULL); } if (DSN_ACTION_DELAYED & dwDSNAction) { cCurrent = InterlockedIncrement((PLONG) &m_cDelayedDSNs); DebugTrace((LPARAM) this, "INFO: Delayed DSN Generated - total %d", cCurrent); } if (DSN_ACTION_RELAYED & dwDSNAction) { cCurrent = InterlockedIncrement((PLONG) &m_cRelayedDSNs); DebugTrace((LPARAM) this, "INFO: Relayed DSN Generated - total %d", cCurrent); } if (DSN_ACTION_DELIVERED & dwDSNAction) { cCurrent = InterlockedIncrement((PLONG) &m_cDeliveredDSNs); DebugTrace((LPARAM) this, "INFO: Delivery DSN Generated - total %d", cCurrent); } if (DSN_ACTION_EXPANDED & dwDSNAction) { cCurrent = InterlockedIncrement((PLONG) &m_cExpandedDSNs); DebugTrace((LPARAM) this, "INFO: Expanded DSN Generated - total %d", cCurrent); } //Queue request to post DSN generation queue hr = m_asyncqPostDSNQueue.HrQueueRequest( pDSNMsg, FALSE, cCountMsgsForHandleThrottling(pDSNMsg)); if (SUCCEEDED(hr)) hr = S_OK; pdsnparams->dwDSNTypesGenerated |= dwDSNAction; pdsnparams->cRecips += cRecipsDSNd; CLEANUP: DebugTrace((LPARAM)this, "returning %08lx", hr); TraceFunctLeaveEx((LPARAM)this); return hr; } // CAQSvrInst::HrSubmitDSN