|
|
/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
asynccon.cxx
Abstract:
Author:
--*/
#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include "remoteq.hxx"
#include <asynccon.hxx>
#include <dnsreci.h>
#include <cdns.h>
#include "smtpdns.hxx"
#include "asyncmx.hxx"
#include "smtpmsg.h"
#include <tran_evntlog.h>
CDnsLogger *g_pDnsLogger = NULL; extern DWORD g_DnsErrorsBeforeFailover; extern DWORD g_DnsConnectsInProbation;
extern BOOL QueueCallBackFunction(PVOID ThisPtr, BOOLEAN fTimedOut); extern void DeleteDnsRec(PSMTPDNS_RECS pDnsRec); extern PSMTPDNS_RECS GetDnsRecordsFromLiteral(const char * HostName);
CPool CAsyncSmtpDns::Pool(SMTP_ASYNCMX_SIGNATURE);
CAsyncSmtpDns::CAsyncSmtpDns( SMTP_SERVER_INSTANCE * pServiceInstance, ISMTPConnection *pSmtpConnection, RETRYPARAMS *pRetryParams, char *MyFQDN) : CAsyncMxDns(MyFQDN) { TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::CAsyncSmtpDns"); DebugTrace((LPARAM) this, "Creating CAsyncSmtpDns object = 0x%08x", this);
m_Signature = SMTP_ASYNCMX_SIGNATURE; m_DomainOptions = 0; m_fConnectToSmartHost = FALSE; m_pServiceInstance = pServiceInstance; m_pISMTPConnection = pSmtpConnection; m_pDNS_RESOLVER_RECORD = NULL; m_fInitCalled = FALSE; m_pszSSLVerificationName = NULL;
//
// By default we fail in a retryable fashion. This is the generic failure code. If the
// query succeeds this should be set to ERROR_SUCCESS. This may also set this to a more
// specific error code at the point of failure.
//
m_dwDiagnostic = AQUEUE_E_HOST_NOT_FOUND;
//
// pRetryParams encapsulates parameters for a failed message if this DNS query
// is on an SMTP 4xx error failover path
//
if(!pRetryParams) ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams)); else { DebugTrace((LPARAM) this, "Failover mailmsg"); CopyMemory(&m_RetryParams, pRetryParams, sizeof(m_RetryParams)); }
pServiceInstance->InsertAsyncDnsObject(this); }
BOOL CAsyncSmtpDns::Init (LPSTR pszSSLVerificationName) { BOOL fRet = FALSE;
TraceFunctEnterEx ((LPARAM) this, "CAsyncSmtpDns::Init");
m_fInitCalled = TRUE;
if (pszSSLVerificationName) { m_pszSSLVerificationName = new char [lstrlen(pszSSLVerificationName) + 1]; if (!m_pszSSLVerificationName) goto Exit;
lstrcpy (m_pszSSLVerificationName, pszSSLVerificationName); }
fRet = TRUE; Exit: TraceFunctLeaveEx ((LPARAM) this); return fRet; }
BOOL CAsyncSmtpDns::IsShuttingDown() { return m_pServiceInstance->IsShuttingDown(); }
BOOL CAsyncSmtpDns::IsAddressMine(DWORD dwIp) { return m_pServiceInstance->IsAddressMine(dwIp, 25); }
CAsyncSmtpDns::~CAsyncSmtpDns() { DWORD dwAck = 0; MessageAck MsgAck;
TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::~CAsyncSmtpDns");
DebugTrace((LPARAM) this, "Destructing CAsyncSmtpDns object = 0x%08x", this);
_ASSERT (m_fInitCalled && "Init not called on CAsyncSmtpDns");
if(m_fMxLoopBack) m_dwDiagnostic = AQUEUE_E_LOOPBACK_DETECTED;
DeleteDnsRec(m_AuxList);
//
// If we did not succeed, we need to ack the connection here (m_dwDiagnostic holds
// the error code to use). On the other hand, if we succeeded, then HandleCompletedData
// must have kicked off an async connection to the server SMTP, and the ISMTPConnection
// will be acked by the "async connect" code -- we don't need to do anything. The
// m_pISMTPConnection may also be legally set to NULL (see HandleCompletedData).
//
if(m_dwDiagnostic != ERROR_SUCCESS && m_pISMTPConnection) { if(m_RetryParams.m_pIMsg) { DebugTrace((LPARAM) this, "Acking connection on MX failover");
MsgAck.dwMsgStatus = MESSAGE_STATUS_RETRY_ALL; MsgAck.pvMsgContext = (PDWORD) m_RetryParams.m_pAdvQContext; MsgAck.pIMailMsgProperties = (IMailMsgProperties *) m_RetryParams.m_pIMsg; m_pISMTPConnection->AckMessage(&MsgAck); MsgAck.pIMailMsgProperties->Release(); ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
m_pISMTPConnection->AckConnection(CONNECTION_STATUS_DROPPED); m_pISMTPConnection->SetDiagnosticInfo(AQUEUE_E_SMTP_PROTOCOL_ERROR, NULL, NULL); m_pISMTPConnection->Release(); m_pISMTPConnection = NULL; } else { if(AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND == m_dwDiagnostic) dwAck = CONNECTION_STATUS_FAILED_NDR_UNDELIVERED; else if(AQUEUE_E_LOOPBACK_DETECTED == m_dwDiagnostic) dwAck = CONNECTION_STATUS_FAILED_LOOPBACK; else dwAck = CONNECTION_STATUS_FAILED;
DebugTrace((LPARAM) this, "Connection status: %d, Failure: %d", dwAck, m_dwDiagnostic); m_pISMTPConnection->AckConnection(dwAck); m_pISMTPConnection->SetDiagnosticInfo(m_dwDiagnostic, NULL, NULL); m_pISMTPConnection->Release(); m_pISMTPConnection = NULL; } }
if(m_pDNS_RESOLVER_RECORD != NULL) { DebugTrace((LPARAM) this, "Deleting DNS_RESOLVER_RECORD in Async SMTP obj"); delete m_pDNS_RESOLVER_RECORD; m_pDNS_RESOLVER_RECORD = NULL; } DBG_CODE(else DebugTrace((LPARAM) this, "No DNS_RESOLVER_RECORD set for Async SMTP obj"));
if(m_pszSSLVerificationName) delete [] m_pszSSLVerificationName;
m_pServiceInstance->RemoveAsyncDnsObject(this); TraceFunctLeaveEx((LPARAM) this); }
//-----------------------------------------------------------------------------
// Description:
// HandleCompletedData is called when the DNS resolve is finished. It
// does the final processing after DNS is finished, and sets the
// m_dwDiagnostic flag appropriately. It does one of three things based
// on the DnsStatus and m_AuxList:
//
// (1) If the resolve was successful, it kicks off a connection to the
// server and set the m_dwDiagnostic to ERROR_SUCCESS.
// (2) If the resolve failed authoritatively, it set the m_dwDiagnostic
// to NDR the messages (after checking for a smarthost) ==
// AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND.
// (3) If the resolve failed (from dwDnsStatus and m_AuxList) or if
// something fails during HandleCompletedData, the m_dwDiagnostic is
// not modified (it remains initialized to AQUEUE_E_DNS_FAILURE, the
// default error code).
//
// m_dwDiagnostic is examined in ~CAsyncSmtpDns.
// Arguments:
// DNS_STATUS dwDnsStatus - Status code from DnsParseMessage
// Returns:
// Nothing.
//-----------------------------------------------------------------------------
void CAsyncSmtpDns::HandleCompletedData(DNS_STATUS dwDnsStatus) { BOOL fRet = FALSE; CAsyncMx * pAsyncCon = NULL; MXPARAMS Params; DWORD dwPostDnsSmartHost = INADDR_NONE;
// +3 for enclosing [] and NULL termination
CHAR szPostDnsSmartHost[IP_ADDRESS_STRING_LENGTH + 3];
TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::HandleCompletedData");
//
// The DNS lookup failed authoritatively. The messages will be NDR'ed unless there
// is a smarthost configured. If there is a smarthost, we will kick off a resolve
// for it.
//
if(ERROR_NOT_FOUND == dwDnsStatus) { if(m_fConnectToSmartHost) { char szSmartHost[MAX_PATH+1];
m_pServiceInstance->GetSmartHost(szSmartHost); ((REMOTE_QUEUE *)m_pServiceInstance->QueryRemoteQObj())->StartAsyncConnect(szSmartHost, m_pISMTPConnection, m_DomainOptions, FALSE);
//Do not release this ISMTPConnection object! We passed it on to
//StartAsyncConnect so that it can try to associate this object with
//a connection with the smart host. We set it to null here so that we
//will not release it or ack it in the destructor of this object.
m_pISMTPConnection = NULL; m_dwDiagnostic = ERROR_SUCCESS; TraceFunctLeaveEx((LPARAM) this); return; } else { //No smart host, messages will be NDR'ed. Return value is meaningless.
m_dwDiagnostic = AQUEUE_E_AUTHORITATIVE_HOST_NOT_FOUND; TraceFunctLeaveEx((LPARAM) this); return; } }
//Successful DNS lookup.
if(dwDnsStatus == ERROR_SUCCESS && m_AuxList) { //
// The post-DNS smart host is useful for testing DNS. It allows us
// to exercise the DNS resolution codepath and yet send to a smarthost.
// If a smarthost is specified, we will allocate a new TempList struct
// and fill it in with the IP address.
//
if(m_pServiceInstance->GetPostDnsSmartHost( szPostDnsSmartHost, sizeof(szPostDnsSmartHost)))
{ DeleteDnsRec(m_AuxList);
// Note: Literal IP must be enclosed in brackets: []
m_AuxList = GetDnsRecordsFromLiteral(szPostDnsSmartHost); if(!m_AuxList) { ErrorTrace((LPARAM) this, "Can't convert %s to IP", szPostDnsSmartHost); TraceFunctLeaveEx((LPARAM) this); return; } }
if(m_RetryParams.m_pIMsg) { m_AuxList->pMailMsgObj = m_RetryParams.m_pIMsg; m_AuxList->pAdvQContext = m_RetryParams.m_pAdvQContext; m_AuxList->pRcptIdxList = m_RetryParams.m_pRcptIdxList; m_AuxList->dwNumRcpts = m_RetryParams.m_dwNumRcpts; }
CrashOnInvalidSMTPConn(m_pISMTPConnection);
Params.HostName = ""; Params.PortNum = m_pServiceInstance->GetRemoteSmtpPort(); Params.TimeOut = INFINITE; Params.CallBack = QueueCallBackFunction; Params.pISMTPConnection = m_pISMTPConnection; Params.pInstance = m_pServiceInstance; Params.pDnsRec = m_AuxList; Params.pDNS_RESOLVER_RECORD = m_pDNS_RESOLVER_RECORD;
pAsyncCon = new CAsyncMx (&Params); if(pAsyncCon != NULL) { // Abdicate responsibility for deleting outbound params
m_pDNS_RESOLVER_RECORD = NULL; m_AuxList = NULL; if(m_RetryParams.m_pIMsg) ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams));
// Outbound SSL: Set name against which server cert. should matched
fRet = pAsyncCon->Init(m_pszSSLVerificationName); if(!fRet) { delete pAsyncCon; goto Exit; } if(!m_fConnectToSmartHost) { pAsyncCon->SetTriedOnFailHost(); }
pAsyncCon->SetDomainOptions(m_DomainOptions);
fRet = pAsyncCon->InitializeAsyncConnect(); if(!fRet) { delete pAsyncCon; } else { // We should not Ack this in the destructor
m_pISMTPConnection = NULL; m_dwDiagnostic = ERROR_SUCCESS; } } else { DeleteDnsRec(m_AuxList); } } Exit: TraceFunctLeaveEx((LPARAM) this); return; }
//------------------------------------------------------------------------------
// Description:
// Simple wrapper function for DnsQueryAsync. This is a virtual function
// called from CAsyncDns but implemented in CAsyncSmtpDns. In order to retry
// a DNS query we need all the parameters of the old query. These are members
// of CAsyncSmtpDns. Thus the virtual function based implementation.
//
// Arguments:
// BOOL fUdp -- Use UDP as transport for retry query?
//
// Returns:
// TRUE on success. In this situation the ISMTPConnection ack (and release of
// pDNS_RESOLVER_RECORD) is handled by the new CAsyncSmtpDns object created
// by DnsQueryAsync. The diagnostic code of this object is cleared.
//
// FALSE on error. In this case, the cleanup for ISMTPConnection and
// pDNS_RESOLVER_RECORD must be done by "this" CAsyncSmtpDns. The
// diagnostic code is not touched.
//------------------------------------------------------------------------------
BOOL CAsyncSmtpDns::RetryAsyncDnsQuery(BOOL fUdp) { TraceFunctEnterEx((LPARAM) this, "CAsyncSmtpDns::RetryAsyncDnsQuery"); BOOL fRet = FALSE; RETRYPARAMS *pRetryParams = NULL;
//
// If all DNS servers are down, we shouldn't call DnsQueryAsync. This is
// because DnsQueryAsync guarantees that if nothing is up, it will try to
// query one of the down DNS servers (so that *something* happens). This
// is fine when the resolve is happening for the first time (after getting
// a remote-queue from AQueue), but on the retry code-path, this will cause
// us to loop. That is why we return immediately if there are no DNS servers.
//
if(GetDnsList()->GetUpServerCount() == 0) { // How to ack the connection
m_dwDiagnostic = AQUEUE_E_NO_DNS_SERVERS; return FALSE; }
//
// If we do not have a connection object, then the requery attempt
// is doomed to fail. This can happen when we disconnect and
// ATQ calls our completion function with ERROR_OPERATION_ABORTED
// If we don't have a connection object, there is no way to
// ack the connection or get messages to send.
//
if (!m_pISMTPConnection) { DebugTrace((LPARAM) this, "RetryAsyncDnsQuery called without connection object - aborting"); //should be cleared by same code path
_ASSERT(!m_pDNS_RESOLVER_RECORD); fRet = FALSE; //there is nothing to Ack.
goto Exit; }
// Pass in failover message params if any
if(m_RetryParams.m_pIMsg) pRetryParams = &m_RetryParams;
fRet = DnsQueryAsync( m_pServiceInstance, m_HostName, m_FQDNToDrop, m_pISMTPConnection, m_dwFlags, m_DomainOptions, m_fConnectToSmartHost, m_pDNS_RESOLVER_RECORD, m_pszSSLVerificationName, pRetryParams, fUdp);
if(fRet) { m_pDNS_RESOLVER_RECORD = NULL; m_pISMTPConnection = NULL; m_dwDiagnostic = ERROR_SUCCESS; ZeroMemory((PVOID) &m_RetryParams, sizeof(m_RetryParams)); }
Exit: TraceFunctLeave(); return fRet; }
DWORD CTcpRegIpList::ConnectsAllowedInProbation() { return g_DnsConnectsInProbation; }
DWORD CTcpRegIpList::ErrorsBeforeFailover() { return g_DnsErrorsBeforeFailover; }
void CTcpRegIpList::LogServerDown( DWORD dwServerIp, BOOL fUdp, DWORD dwErr, DWORD cUpServers) { const CHAR *pszProtocol = NULL; LPSTR pszServerIp = NULL; in_addr inAddrIpServer; const CHAR *apszSubStrings[2]; CHAR szEventKey[32]; int nBytes = 0;
TraceFunctEnterEx((LPARAM)this, "CTcpRegIpList::LogServerDown"); // Log event informing that DNS server is out
CopyMemory(&inAddrIpServer, &dwServerIp, sizeof(DWORD)); pszServerIp = inet_ntoa(inAddrIpServer);
if(!pszServerIp) { ErrorTrace((LPARAM)this, "Unable to allocate pszServerIp"); return; }
pszProtocol = fUdp ? "UDP" : "TCP"; apszSubStrings[0] = pszServerIp; apszSubStrings[1] = pszProtocol;
//
// Generate a unique key for this event. If a given server goes down
// we will end up logging this warning every 10 minutes (the retry
// time for a down server). This will spam the log. Therefore, we
// call TriggerLogEvent with the PERIODIC flag. This causes all events
// with the same szEventKey to be logged only once per hour. The
// following key is unique for a server-IP/tranport combination.
//
nBytes = _snprintf(szEventKey, sizeof(szEventKey), "DNS %08x %1x", inAddrIpServer, fUdp ? 1 : 0);
// Guard against future errors. Currently nBytes is guaranteed to be 14
if(nBytes < 0) { szEventKey[(sizeof(szEventKey)) - 1] = '\0'; _ASSERT(0 && "szEventKey buffer too small"); }
g_EventLog.LogEvent( SMTP_DNS_SERVER_DOWN, // Message ID
2, // # of substrings
apszSubStrings, // Substrings
EVENTLOG_WARNING_TYPE, // Type of event
dwErr, // Error code
LOGEVENT_LEVEL_MEDIUM, // Logging level
szEventKey, // Key to this event
LOGEVENT_FLAG_PERIODIC, // Logging option
(-1), // Substring index for dwErr (unused)
GetModuleHandle("SMTPSVC")); // Module
ErrorTrace((LPARAM) this, "Received error %d connecting to DNS server %s over %s", dwErr, pszServerIp, pszProtocol);
if(cUpServers == 0) {
// Log this only once an hour
g_EventLog.LogEvent( SMTP_NO_DNS_SERVERS, // Message ID
0, // # of substrings
NULL, // Type of event
EVENTLOG_ERROR_TYPE, // Type of event
DNS_ERROR_NO_DNS_SERVERS, // Error code
LOGEVENT_LEVEL_MEDIUM, // Logging level
"DNS No Servers", // Key to this event
LOGEVENT_FLAG_PERIODIC, // Logging option
(-1), // Substring index for dwErr (unused)
GetModuleHandle("SMTPSVC")); // Module
ErrorTrace((LPARAM) this, "All DNS servers are down"); }
TraceFunctLeaveEx((LPARAM)this); return; }
|