Windows2003-3790/inetsrv/iis/svcs/smtp/server/smtpdns.cxx
2020-09-30 16:53:55 +02:00

522 lines
18 KiB
C++

/*++
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;
}