//----------------------------------------------------------------------------- // // // File: // smtpconn.cpp // Description: // Implementation of CSMTPConn // Author: Mike Swafford (MikeSwa) // // History: // // Copyright (C) 1998 Microsoft Corporation // //----------------------------------------------------------------------------- #include "aqprecmp.h" #include "SMTPConn.h" #include "connmgr.h" #include "domcfg.h" CPool CSMTPConn::s_SMTPConnPool; //---[ CSMTPConn::CSMTPConn() ]------------------------------------------------ // // // Description: // CSMTPConn constructor // Parameters: // IN pConnMgr Ptr to instance connection manager // IN plmq Ptr to link for this connection // IN cMaxMessagesPerConnection Max messages to send per connection // 0 implies unlimited // Returns: // - // //----------------------------------------------------------------------------- CSMTPConn::CSMTPConn(CConnMgr *pConnMgr, CLinkMsgQueue *plmq, DWORD cMaxMessagesPerConnection) { _ASSERT(pConnMgr); _ASSERT(plmq); m_dwSignature = SMTP_CONNECTION_SIG; m_pConnMgr = pConnMgr; m_pIntDomainInfo = NULL; m_plmq = plmq; m_cFailedMsgs = 0; m_cTriedMsgs = 0; m_cMaxMessagesPerConnection = cMaxMessagesPerConnection; m_dwConnectionStatus = CONNECTION_STATUS_OK; m_szDomainName = NULL; m_cbDomainName = 0; m_liConnections.Flink = NULL; m_liConnections.Blink = NULL; m_cAcks = 0; m_dwTickCountOfLastAck = 0; ZeroMemory(m_szConnectedIPAddress, sizeof(m_szConnectedIPAddress)); if (plmq) { plmq->AddRef(); } } //---[ CSMTPConn::~CSMTPConn() ]----------------------------------------------- // // // Description: // CSMTPConn default destructor // Parameters: // - // Returns: // - // //----------------------------------------------------------------------------- CSMTPConn::~CSMTPConn() { HRESULT hrConnectionStatus = S_OK; BOOL fForceCheckForDSNGeneration = FALSE; _ASSERT(m_cAcks == m_cTriedMsgs); if (m_plmq != NULL) { _ASSERT(m_pConnMgr); m_pConnMgr->ReleaseConnection(this, &fForceCheckForDSNGeneration); switch(m_dwConnectionStatus) { case CONNECTION_STATUS_OK: hrConnectionStatus = S_OK; break; case CONNECTION_STATUS_FAILED: hrConnectionStatus = AQUEUE_E_HOST_NOT_RESPONDING; break; case CONNECTION_STATUS_DROPPED: hrConnectionStatus = AQUEUE_E_CONNECTION_DROPPED; break; case CONNECTION_STATUS_FAILED_LOOPBACK: hrConnectionStatus = AQUEUE_E_LOOPBACK_DETECTED; break; case CONNECTION_STATUS_FAILED_NDR_UNDELIVERED: hrConnectionStatus = AQUEUE_E_SMTP_GENERIC_ERROR; break; default: _ASSERT(0 && "Undefined Connection Status"); hrConnectionStatus = S_OK; } m_plmq->SetLastConnectionFailure(hrConnectionStatus); m_plmq->RemoveConnection(this, fForceCheckForDSNGeneration); m_plmq->Release(); //We should kick the connection manager, because if we were generating //DSNs, no connection could be made m_pConnMgr->KickConnections(); } if (m_pIntDomainInfo) m_pIntDomainInfo->Release(); } //---[ CSMTPConn::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: // 11/27/2001 - MikeSwa copied from CAQSvrInst // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::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_ISMTPConnection == riid) { *ppvObj = static_cast(this); } else if (IID_IConnectionPropertyManagement == riid) { *ppvObj = static_cast(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; } static_cast(*ppvObj)->AddRef(); Exit: return hr; } //---[ CSMTPConn::GetNextMessage ]--------------------------------------------- // // // Description: // Implementation of ISMTPConnection::GetNextMsg. // Gets the next message queued for this connection and determines which // recipients should be delivered for this connection. // Parameters: // OUT ppimsg New IMsg top be delivered // OUT pdwMsgContext A 32-bit Context that needs to be provided in the // message ack. // OUT pcIndexes The number of index in prgdwRecipIndex // OUT prgdwRecipIndex Recipient indexes that the caller is responsible // for attempting delivery to. // Returns: // // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::GetNextMessage( OUT IMailMsgProperties **ppIMailMsgProperties, OUT DWORD ** ppvMsgContext, OUT DWORD * pcIndexes, OUT DWORD ** prgdwRecipIndex) { TraceFunctEnterEx((LPARAM) this, "CSMTPConn::GetNextMessage"); HRESULT hr = S_OK; //We get the next message only if we are under the batch limit if(m_cMaxMessagesPerConnection && (m_cTriedMsgs >= m_cMaxMessagesPerConnection) && (!m_pIntDomainInfo || !((DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY) & m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags))) { //SMTP does not check - but we may need a specific error for this case hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; } if (m_pConnMgr && m_pConnMgr->fConnectionsStoppedByAdmin()) { //Admin has requested that all outbound connections stop hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; } hr = m_plmq->HrGetNextMsg(&m_dcntxtCurrentDeliveryContext, ppIMailMsgProperties, pcIndexes, prgdwRecipIndex); if (FAILED(hr)) goto Exit; //this will automagically catch the queue empty case... //If the Link has no more messages it will return AQUEUE_E_QUEUE_EMPTY, which //should cause the caller to Release() and query GetNextConnection again. *ppvMsgContext = (DWORD *) &m_dcntxtCurrentDeliveryContext; //increment the messages served InterlockedIncrement((PLONG)&m_cTriedMsgs); Exit: if (!m_cTriedMsgs) DebugTrace((LPARAM) this, "GetNextMessage called, but no messages tried for this connection"); //rewrite error for SMTPSVC if (AQUEUE_E_QUEUE_EMPTY == hr) hr = HRESULT_FROM_WIN32(ERROR_EMPTY); TraceFunctLeave(); return hr; } //---[ CSMTPConn::AckMessage ]------------------------------------------------- // // // Description: // Acknowledges the delivery of a message (success/error codes are put in // the envelope by the transport). // // Implements ISMTPConnection::AckMessage(); // Parameters: // IN pIMsg IMsg to acknowledge // IN dwMsgContext Context that was returned by GetNextMessage // IN eMsgStatus Status of message // Returns: // S_OK on success // E_INVALIDARG if dwMsgContext is invalid // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::AckMessage(/*[in]*/ MessageAck *pMsgAck) { HRESULT hr = S_OK; DWORD dwTickCount = GetTickCount(); _ASSERT(m_plmq); _ASSERT(pMsgAck); if (!(pMsgAck->dwMsgStatus & MESSAGE_STATUS_ALL_DELIVERED)) { m_cFailedMsgs++; } InterlockedIncrement((PLONG)&m_cAcks); _ASSERT(m_cAcks == m_cTriedMsgs); hr = m_plmq->HrAckMsg(pMsgAck); m_dwTickCountOfLastAck = dwTickCount; //Set after assert so we can compare return hr; } //---[ CSMTPConn::GetSMTPDomain ]---------------------------------------------- // // // Description: // Returns the SMTPDomain of the link associated with this connections. // // $$REVIEW: // This method does not allocate new memory for this string, but instead // relies on the good intentions of the SMTP stack (or test driver) to // not overwrite this memory. If we ever expose this interface externally, // then we should revert to allocating memory and doing a buffer copy // // Implements ISMTPConnection::GetSMTPDomain // Parameters: // IN OUT pDomainInfo Ptr to DomainInfo struct supplied by caller // and filled in here // Returns: // S_OK on success // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::GetDomainInfo(IN OUT DomainInfo *pDomainInfo) { HRESULT hr = S_OK; _ASSERT(pDomainInfo->cbVersion >= sizeof(DomainInfo)); _ASSERT(pDomainInfo); if (NULL == m_plmq) { hr = AQUEUE_E_LINK_INVALID; goto Exit; } if (!m_pIntDomainInfo) { //Try to get domain info hr = m_plmq->HrGetDomainInfo(&m_cbDomainName, &m_szDomainName, &m_pIntDomainInfo); if (FAILED(hr)) { m_pIntDomainInfo = NULL; _ASSERT(AQUEUE_E_INVALID_DOMAIN != hr); goto Exit; } } _ASSERT(m_pIntDomainInfo); _ASSERT(m_cbDomainName); _ASSERT(m_szDomainName); // Is it OK to send client side commands on this connection // If not, we reset those domain info flags so SMTp cannot see them if(!m_plmq->fCanSendCmd()) { m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags &= ~(DOMAIN_INFO_SEND_TURN | DOMAIN_INFO_SEND_ETRN); } // If SMTP doesn't have the DOMAIN_INFO_TURN_ON_EMPTY then it is the older, // broken SMTP and we shouldn't allow TURN on empty to work. if ((m_plmq->cGetTotalMsgCount() == 0) && !(m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_TURN_ON_EMPTY)) { m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags &= ~DOMAIN_INFO_SEND_TURN; } //copy everything but size memcpy(&(pDomainInfo->dwDomainInfoFlags), &(m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags), sizeof(DomainInfo) - sizeof(DWORD)); //make sure our assumptions about the struct of DomainInfo are valid _ASSERT(1 == ((DWORD *) &(pDomainInfo->dwDomainInfoFlags)) - ((DWORD *) pDomainInfo)); //we've filled pDomainInfo with the info for our Domain if (pDomainInfo->szDomainName[0] == '*') { //we matched a wildcard domain... substitute our domain name pDomainInfo->cbDomainNameLength = m_cbDomainName; pDomainInfo->szDomainName = m_szDomainName; } else { //if it wasn't a wildcard match... strings should match! _ASSERT(0 == _stricmp(m_szDomainName, pDomainInfo->szDomainName)); } Exit: return hr; } //---[ CSMTPConn::SetDiagnosticInfo ]------------------------------------------ // // // Description: // Sets the extra diagnostic information for this connection. // Parameters: // IN hrDiagnosticError Error code... if SUCCESS we thow away // the rest of the information // IN szDiagnosticVerb String pointing to the protocol // verb that caused the failure. // IN szDiagnosticResponse String that contains the remote // servers response. // Returns: // S_OK always // History: // 2/18/99 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::SetDiagnosticInfo( IN HRESULT hrDiagnosticError, IN LPCSTR szDiagnosticVerb, IN LPCSTR szDiagnosticResponse) { TraceFunctEnterEx((LPARAM) this, "CSMTPConn::SetDiagnosticInfo"); if (m_plmq && FAILED(hrDiagnosticError)) { m_plmq->SetDiagnosticInfo(hrDiagnosticError, szDiagnosticVerb, szDiagnosticResponse); } TraceFunctLeave(); return S_OK; //always return S_OK } //---[ CSMTPConn::CopyQueuePropertiesToSession ]------------------------------- // // // Description: // Copies the set of propties that queuing owns into the SMTP session // object. In some cases, these properties are required for security // reasons (ie - a sink wants to know who we think we are connecting to // instead of who the remote side says they are). // Parameters: // IN IUnknown SMTP Session object to copy properties to // NOTE: We need to resist the urge to AddRef and keep this around // later use. AddRefs on this object are actually ignored as the // lifetime is controlled by either the SMTP connection object // or the stack. // // Returns: // S_OK always // History: // 11/27/2001 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::CopyQueuePropertiesToSession(IN IUnknown *pISession) { TraceFunctEnterEx((LPARAM) this, "SMTPConn::CopyQueuePropertiesToSession"); HRESULT hr = S_OK; IMailMsgPropertyBag *pISessionProperties = NULL; LPSTR szConnectorName = NULL; if (!pISession) { ErrorTrace((LPARAM) this, "NULL ISession - bailing"); hr = E_POINTER; goto Exit; } // // Get the property bag object // hr = pISession->QueryInterface(IID_IMailMsgPropertyBag, (PVOID *) &pISessionProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "QI for IID_IMailMsgPropertyBag failed 0x%08X", hr); pISessionProperties = NULL; goto Exit; } // // Copy the next hop name into the session property bag // hr = pISessionProperties->PutStringA(ISESSION_PID_OUT_ROUTE_ADDRESS, m_szDomainName); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to write ISESSION_PID_OUT_ROUTE_ADDRESS hr - 0x%08X", hr); } if (m_plmq) szConnectorName = m_plmq->szGetConnectorName(); if (szConnectorName) { hr = pISessionProperties->PutStringA(ISESSION_PID_OUT_CONNECTOR_NAME, szConnectorName); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to write ISESSION_PID_OUT_CONNECTOR_NAME 0x%08X", hr); } } else { DebugTrace((LPARAM) this, "szConnectorName is NULL... not writing to ISession"); } Exit: if (pISessionProperties) pISessionProperties->Release(); TraceFunctLeave(); return S_OK; } //---[ CSMTPConn::CopySessionPropertiesToQueue ]------------------------------- // // // Description: // Copies the set of propties that the protocol owns into the queue // object. In some cases, these properties are required for diagnostic // reasons (ie - an admin wants to know which IP address we connected to). // Parameters: // IN IUnknown SMTP Session object to copy properties to // NOTE: We need to resist the urge to AddRef and keep this around // later use. AddRefs on this object are actually ignored as the // lifetime is controlled by either the SMTP connection object // or the stack. // // Returns: // S_OK always // History: // 11/27/2001 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CSMTPConn::CopySessionPropertiesToQueue(IN IUnknown *pISession) { TraceFunctEnterEx((LPARAM) this, "SMTPConn::CopySessionPropertiesToQueue"); HRESULT hr = S_OK; IMailMsgPropertyBag *pISessionProperties = NULL; if (!pISession) { ErrorTrace((LPARAM) this, "NULL ISession - bailing"); hr = E_POINTER; goto Exit; } // // Get the property bag object // hr = pISession->QueryInterface(IID_IMailMsgPropertyBag, (PVOID *) &pISessionProperties); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "QI for IID_IMailMsgPropertyBag failed 0x%08X", hr); pISessionProperties = NULL; goto Exit; } hr = pISessionProperties->GetStringA(ISESSION_PID_REMOTE_IP_ADDRESS, sizeof(m_szConnectedIPAddress)-1, (CHAR *) &m_szConnectedIPAddress); if (FAILED(hr)) { DebugTrace((LPARAM) this, "Unable to read ISESSION_PID_REMOTE_IP_ADDRESS - 0x%08X", hr); m_szConnectedIPAddress[0] = '\0'; } Exit: if (pISessionProperties) pISessionProperties->Release(); TraceFunctLeave(); return S_OK; }