359 lines
12 KiB
C++
359 lines
12 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
//
|
|
// 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;
|
|
|
|
if (plmq)
|
|
{
|
|
plmq->AddRef();
|
|
}
|
|
}
|
|
|
|
//---[ CSMTPConn::~CSMTPConn() ]-----------------------------------------------
|
|
//
|
|
//
|
|
// Description:
|
|
// CSMTPConn default destructor
|
|
// Parameters:
|
|
// -
|
|
// Returns:
|
|
// -
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
CSMTPConn::~CSMTPConn()
|
|
{
|
|
HRESULT hrConnectionStatus = S_OK;
|
|
BOOL fHardErrorForceNDR = FALSE;
|
|
_ASSERT(m_cAcks == m_cTriedMsgs);
|
|
if (m_plmq != NULL)
|
|
{
|
|
_ASSERT(m_pConnMgr);
|
|
m_pConnMgr->ReleaseConnection(this, &fHardErrorForceNDR);
|
|
|
|
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_NDR_ALL;
|
|
break;
|
|
default:
|
|
_ASSERT(0 && "Undefined Connection Status");
|
|
hrConnectionStatus = S_OK;
|
|
}
|
|
|
|
m_plmq->SetLastConnectionFailure(hrConnectionStatus);
|
|
m_plmq->RemoveConnection(this, fHardErrorForceNDR);
|
|
|
|
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::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
|
|
}
|