Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1503 lines
40 KiB

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
send.c
Abstract:
Domain Name System (DNS) Library
Send response routines.
Author:
Jim Gilroy (jamesg) October, 1996
Revision History:
--*/
#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include "remoteq.hxx"
#include "dnsreci.h"
#include <dnsapi.h>
#include "cdns.h"
#include <time.h>
extern CTcpRegIpList g_TcpRegIpList;
WORD gwTransactionId = 1;
VOID
DnsCompletion(
PVOID pvContext,
DWORD cbWritten,
DWORD dwCompletionStatus,
OVERLAPPED * lpo
)
{
BOOL WasProcessed = TRUE;
CAsyncDns *pCC = (CAsyncDns *) pvContext;
_ASSERT(pCC);
_ASSERT(pCC->IsValid());
//
// if we could not process a command, or we were
// told to destroy this object, close the connection.
//
WasProcessed = pCC->ProcessClient(cbWritten, dwCompletionStatus, lpo);
}
void DeleteDnsRec(PSMTPDNS_RECS pDnsRec)
{
DWORD Loop = 0;
PLIST_ENTRY pEntry = NULL;
PMXIPLIST_ENTRY pQEntry = NULL;
if(pDnsRec == NULL)
{
return;
}
while (pDnsRec->DnsArray[Loop] != NULL)
{
if(pDnsRec->DnsArray[Loop]->DnsName[0])
{
while(!IsListEmpty(&pDnsRec->DnsArray[Loop]->IpListHead))
{
pEntry = RemoveHeadList (&pDnsRec->DnsArray[Loop]->IpListHead);
pQEntry = CONTAINING_RECORD( pEntry, MXIPLIST_ENTRY, ListEntry);
delete pQEntry;
}
delete pDnsRec->DnsArray[Loop];
}
Loop++;
}
if(pDnsRec)
{
delete pDnsRec;
pDnsRec = NULL;
}
}
CAsyncDns::CAsyncDns(void)
{
m_signature = DNS_CONNECTION_SIGNATURE_VALID; // signature on object for sanity check
m_cPendingIoCount = 0;
m_cThreadCount = 0;
m_cbReceived = 0;
m_BytesToRead = 0;
m_dwIpServer = 0;
m_dwFlags = 0;
m_Index = 0;
m_LocalPref = 256;
m_fUdp = TRUE;
m_fUsingMx = TRUE;
m_FirstRead = TRUE;
//
// By default we fail (AQUEUE_E_DNS_FAILURE) is the generic failure code. On success
// this is set to ERROR_SUCCESS. We may also set this to a more specific error code
// at the point of failure.
//
m_dwDiagnostic = AQUEUE_E_DNS_FAILURE;
m_pMsgRecv = NULL;
m_pMsgRecvBuf = NULL;
m_pMsgSend = NULL;
m_pMsgSendBuf = NULL;
m_cbSendBufSize = 0;
m_ppRecord = NULL;
m_ppResponseRecords = NULL;
m_pAtqContext = NULL;
m_FQDNToDrop[0] = '\0';
m_HostName [0] = '\0';
m_SeenLocal = FALSE;
ZeroMemory (m_Weight, sizeof(m_Weight));
ZeroMemory (m_Prefer, sizeof(m_Prefer));
}
CAsyncDns::~CAsyncDns(void)
{
PATQ_CONTEXT pAtqContext = NULL;
//_ASSERT(m_cThreadCount == 0);
if(m_pMsgSend)
{
delete [] m_pMsgSendBuf;
m_pMsgSend = NULL;
m_pMsgSendBuf = NULL;
}
if(m_pMsgRecv)
{
delete [] m_pMsgRecvBuf;
m_pMsgRecv = NULL;
m_pMsgRecvBuf = NULL;
}
//release the context from Atq
pAtqContext = (PATQ_CONTEXT)InterlockedExchangePointer( (PVOID *)&m_pAtqContext, NULL);
if ( pAtqContext != NULL )
{
AtqFreeContext( pAtqContext, TRUE );
}
DeleteDnsRec(m_AuxList);
m_signature = DNS_CONNECTION_SIGNATURE_FREE; // signature on object for sanity check
}
BOOL CAsyncDns::ReadFile(
IN LPVOID pBuffer,
IN DWORD cbSize /* = MAX_READ_BUFF_SIZE */
)
{
BOOL fRet = TRUE;
_ASSERT(pBuffer != NULL);
_ASSERT(cbSize > 0);
ZeroMemory(&m_ReadOverlapped, sizeof(m_ReadOverlapped));
m_ReadOverlapped.LastIoState = DNS_READIO;
IncPendingIoCount();
fRet = AtqReadFile(m_pAtqContext, // Atq context
pBuffer, // Buffer
cbSize, // BytesToRead
(OVERLAPPED *)&m_ReadOverlapped) ;
if(!fRet)
{
DisconnectClient();
DecPendingIoCount();
}
return fRet;
}
BOOL CAsyncDns::WriteFile(
IN LPVOID pBuffer,
IN DWORD cbSize /* = MAX_READ_BUFF_SIZE */
)
{
BOOL fRet = TRUE;
_ASSERT(pBuffer != NULL);
_ASSERT(cbSize > 0);
ZeroMemory(&m_WriteOverlapped, sizeof(m_WriteOverlapped));
m_WriteOverlapped.LastIoState = DNS_WRITEIO;
IncPendingIoCount();
fRet = AtqWriteFile(m_pAtqContext, // Atq context
pBuffer, // Buffer
cbSize, // BytesToRead
(OVERLAPPED *) &m_WriteOverlapped) ;
if(!fRet)
{
DisconnectClient();
DecPendingIoCount();
}
return fRet;
}
DNS_STATUS
CAsyncDns::SendPacket(void)
{
return 0;
}
//
// Public send routines
//
DNS_STATUS
CAsyncDns::Dns_Send(
)
/*++
Routine Description:
Send a DNS packet.
This is the generic send routine used for ANY send of a DNS message.
It assumes nothing about the message type, but does assume:
- pCurrent points at byte following end of desired data
- RR count bytes are in HOST byte order
Arguments:
pMsg - message info for message to send
Return Value:
TRUE if successful.
FALSE on send error.
--*/
{
INT err = 0;
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::Dns_Send");
DebugTrace((LPARAM) this, "Sending DNS request for %s", m_HostName);
fRet = WriteFile(m_pMsgSendBuf, (DWORD) m_cbSendBufSize);
if(!fRet)
{
err = GetLastError();
}
return( (DNS_STATUS)err );
} // Dns_Send
//-----------------------------------------------------------------------------------
// Description:
// Kicks off an async query to DNS.
//
// Arguments:
// IN pszQuestionName - Name to query for.
//
// IN wQuestionType - Record type to query for.
//
// IN dwFlags - DNS configuration flags for SMTP. Currently these dictate
// what transport is used to talk to DNS (TCP/UDP). They are:
//
// DNS_FLAGS_NONE - Use UDP initially. If that fails, or if the
// reply is truncated requery using TCP.
//
// DNS_FLAGS_TCP_ONLY - Use TCP only.
//
// DNS_FLAGS_UDP_ONLY - Use UDP only.
//
// IN MyFQDN - FQDN of this machine (for MX record sorting)
//
// IN fUdp - Should UDP or TCP be used for this query? When dwFlags is
// DNS_FLAGS_NONE the initial query is UDP, and the retry query, if the
// response was truncated, is TCP. Depending on whether we're retrying
// this flag should be set appropriately by the caller.
//
// Returns:
// ERROR_SUCCESS if an async query was pended
// Win32 error if an error occurred and an async query was not pended. All
// errors from this function are retryable (as opposed NDR'ing the message)
// so the message is re-queued if an error occurred.
//-----------------------------------------------------------------------------------
DNS_STATUS
CAsyncDns::Dns_QueryLib(
IN DNS_NAME pszQuestionName,
IN WORD wQuestionType,
IN DWORD dwFlags,
IN char *MyFQDN,
IN BOOL fUdp)
{
DNS_STATUS status = ERROR_NOT_ENOUGH_MEMORY;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::Dns_QueryLib");
m_dwFlags = dwFlags;
m_fUdp = fUdp;
m_AuxList = new SMTPDNS_RECS;
if(m_AuxList == NULL)
{
TraceFunctLeaveEx((LPARAM) this);
return (DNS_STATUS) ERROR_NOT_ENOUGH_MEMORY;
}
ZeroMemory(m_AuxList, sizeof(SMTPDNS_RECS));
lstrcpyn(m_FQDNToDrop, MyFQDN, sizeof(m_FQDNToDrop));
lstrcpyn(m_HostName, pszQuestionName, sizeof(m_HostName));
MultiByteToWideChar( CP_ACP, 0, pszQuestionName, -1, m_wszHostName, MAX_PATH );
//
// build send packet
//
m_pMsgSendBuf = new BYTE[DNS_TCP_DEFAULT_PACKET_LENGTH ];
if( NULL == m_pMsgSendBuf )
{
TraceFunctLeaveEx((LPARAM) this);
return (DNS_STATUS) ERROR_NOT_ENOUGH_MEMORY;
}
DWORD dwBufSize = DNS_TCP_DEFAULT_PACKET_LENGTH ;
if( !m_fUdp )
{
m_pMsgSend = (PDNS_MESSAGE_BUFFER)(m_pMsgSendBuf+2);
dwBufSize -= 2;
}
else
{
m_pMsgSend = (PDNS_MESSAGE_BUFFER)(m_pMsgSendBuf);
}
if( !DnsWriteQuestionToBuffer_UTF8 ( m_pMsgSend,
&dwBufSize,
pszQuestionName,
wQuestionType,
gwTransactionId++,
!( dwFlags & DNS_QUERY_NO_RECURSION ) ) )
{
ErrorTrace((LPARAM) this, "Unable to create DNS query for %s", pszQuestionName);
TraceFunctLeaveEx((LPARAM) this);
return ERROR_NOT_ENOUGH_MEMORY;
}
m_cbSendBufSize = (WORD) dwBufSize;
if( !m_fUdp )
{
*((u_short*)m_pMsgSendBuf) = htons((WORD)dwBufSize );
m_cbSendBufSize += 2;
}
if (m_pMsgSend)
{
status = DnsSendRecord();
}
else
{
status = ERROR_INVALID_NAME;
}
TraceFunctLeaveEx((LPARAM) this);
return status;
}
void CAsyncDns::DisconnectClient(void)
{
SOCKET hSocket;
hSocket = (SOCKET)InterlockedExchangePointer( (PVOID *)&m_DnsSocket, (PVOID) INVALID_SOCKET );
if ( hSocket != INVALID_SOCKET )
{
if ( QueryAtqContext() != NULL )
{
AtqCloseSocket(QueryAtqContext() , TRUE);
}
}
}
//
// TCP routines
//
DNS_STATUS
CAsyncDns::Dns_OpenTcpConnectionAndSend()
/*++
Routine Description:
Connect via TCP or UDP to a DNS server. The server list is held
in a global variable read from the registry.
Arguments:
None
Return Value:
ERROR_SUCCESS on success
Win32 error on failure
--*/
{
INT err = 0;
DWORD dwErrServList = ERROR_SUCCESS;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::Dns_OpenTcpConnectionAndSend");
//
// setup a TCP socket
// - INADDR_ANY -- let stack select source IP
//
if(!m_fUdp)
{
m_DnsSocket = Dns_CreateSocket(SOCK_STREAM);
BOOL fRet = FALSE;
int err;
//Alway enable linger so sockets that connect to the server.
//This will send a hard close to the server which will cause
//the servers TCP/IP socket table to be flushed very early.
//We should see very few, if any, sockets in the TIME_WAIT
//state
struct linger Linger;
Linger.l_onoff = 1;
Linger.l_linger = 0;
err = setsockopt(m_DnsSocket, SOL_SOCKET, SO_LINGER, (const char FAR *)&Linger, sizeof(Linger));
}
else
{
m_DnsSocket = Dns_CreateSocket(SOCK_DGRAM);
}
if ( m_DnsSocket == INVALID_SOCKET )
{
err = WSAGetLastError();
if ( !err )
{
err = WSAENOTSOCK;
}
ErrorTrace((LPARAM) this, "Received error %d opening a socket to DNS server", err);
return( err );
}
m_RemoteAddress.sin_family = AF_INET;
m_RemoteAddress.sin_port = DNS_PORT_NET_ORDER;
//
// Get a DNS server from the set of servers for this machine and connect
// to it. The g_TcpRegIpList has logic to keep track of the state of DNS
// servers (UP or DOWN) and logic to retry DOWN DNS servers.
//
dwErrServList = g_TcpRegIpList.GetIp(&m_dwIpServer);
while(ERROR_SUCCESS == dwErrServList)
{
m_RemoteAddress.sin_addr.s_addr = m_dwIpServer;
err = connect(m_DnsSocket, (struct sockaddr *) &m_RemoteAddress, sizeof(SOCKADDR_IN));
if ( !err )
{
break;
}
else
{
MarkDown(m_dwIpServer, err, m_fUdp);
dwErrServList = g_TcpRegIpList.GetIp(&m_dwIpServer);
continue;
}
}
if(DNS_ERROR_NO_DNS_SERVERS == dwErrServList || ERROR_RETRY == dwErrServList)
{
//
// Log Event and set diagnostic: No DNS servers available.
//
err = DNS_ERROR_NO_DNS_SERVERS;
m_dwDiagnostic = AQUEUE_E_NO_DNS_SERVERS;
SmtpLogEventSimple(SMTP_NO_DNS_SERVERS, DNS_ERROR_NO_DNS_SERVERS);
ErrorTrace((LPARAM) this, "No DNS servers. Error - %d", dwErrServList);
return err;
}
_ASSERT(ERROR_SUCCESS == dwErrServList);
//
// We have a connection to DNS
//
if(ERROR_SUCCESS == err)
{
//
// NOTE: We've set the timeout to a hardcoded value of 1 minute. This might
// seem excessively large for DNS, however, since the resolution of the ATQ
// timer is 1 minute that's the minimum anyway. Might as well make it apparent.
//
// Re-associate the handle to the ATQ
// Call ATQ to associate the handle
if (!AtqAddAsyncHandle(
&m_pAtqContext,
NULL,
(LPVOID) this,
DnsCompletion,
60, // Timeout == 60 seconds
(HANDLE) m_DnsSocket))
{
return GetLastError();
}
//
// send desired packet
//
err = Dns_Send();
}
else
{
if(m_DnsSocket != INVALID_SOCKET)
{
closesocket(m_DnsSocket);
m_DnsSocket = INVALID_SOCKET;
}
}
return( (DNS_STATUS)err );
} // Dns_OpenTcpConnectionAndSend
//------------------------------------------------------------------------------
// Description:
// Failed to connect to dwIpServer. Mark the server as DOWN, log
// an event and write traces. This is a simple wrapper for
// CTcpRegIpList::MarkDown.
// Arguments:
// DWORD dwIpServer - IP of server to which we failed to connect
// DWORD dwErr - Win32 error if any
// BOOL fUdp - Which transport was being used when the failure occurred.
//------------------------------------------------------------------------------
void CAsyncDns::MarkDown(DWORD dwIpServer, DWORD dwErr, BOOL fUdp)
{
const CHAR *pszServerIp = NULL;
const CHAR *pszProtocol = NULL;
const CHAR *apszSubStrings[2];
in_addr inAddrIpServer;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::MarkDown");
CopyMemory(&inAddrIpServer, &dwIpServer, sizeof(DWORD));
pszServerIp = inet_ntoa(inAddrIpServer);
if(NULL != pszServerIp)
{
pszProtocol = fUdp ? "UDP" : "TCP";
apszSubStrings[0] = pszServerIp;
apszSubStrings[1] = pszProtocol;
SmtpLogEvent(SMTP_DNS_SERVER_DOWN, 2, apszSubStrings, dwErr);
}
ErrorTrace((LPARAM) this, "Received error %d connecting to DNS server %d.%d.%d.%d over %s",
dwErr, ((PBYTE)&dwIpServer)[0], ((PBYTE)&dwIpServer)[1],
((PBYTE)&dwIpServer)[2], ((PBYTE)&dwIpServer)[3],
fUdp ? "UDP" : "TCP");
g_TcpRegIpList.MarkDown(dwIpServer);
TraceFunctLeaveEx((LPARAM)this);
}
BOOL CAsyncDns::ProcessReadIO(IN DWORD InputBufferLen,
IN DWORD dwCompletionStatus,
IN OUT OVERLAPPED * lpo)
{
BOOL fRet = TRUE;
DWORD DataSize = 0;
DNS_STATUS DnsStatus = 0;
TraceFunctEnterEx((LPARAM) this, "BOOL CAsyncDns::ProcessReadIO");
//add up the number of bytes we received thus far
m_cbReceived += InputBufferLen;
//
// read atleast 2 bytes
//
if(!m_fUdp && m_FirstRead && ( m_cbReceived < 2 ) )
{
fRet = ReadFile(&m_pMsgRecvBuf[m_cbReceived],DNS_TCP_DEFAULT_PACKET_LENGTH-1 );
return fRet;
}
//
// get the size of the message
//
if(!m_fUdp && m_FirstRead && (m_cbReceived >= 2))
{
DataSize = ntohs(*(u_short *)m_pMsgRecvBuf);
//
// add 2 bytes for the field which specifies the length of data
//
m_BytesToRead = DataSize + 2;
m_FirstRead = FALSE;
}
//
// pend another read if we have n't read enough
//
if(!m_fUdp && (m_cbReceived < m_BytesToRead))
{
DWORD cbMoreToRead = m_BytesToRead - m_cbReceived;
fRet = ReadFile(&m_pMsgRecvBuf[m_cbReceived], cbMoreToRead);
}
else
{
if( !m_fUdp )
{
//
// message length is 2 bytes less to take care of the msg length
// field.
//
//m_pMsgRecv->MessageLength = (WORD) m_cbReceived - 2;
m_pMsgRecv = (PDNS_MESSAGE_BUFFER)(m_pMsgRecvBuf+2);
}
else
{
//m_pMsgRecv->MessageLength = (WORD) m_cbReceived;
m_pMsgRecv = (PDNS_MESSAGE_BUFFER)m_pMsgRecvBuf;
}
SWAP_COUNT_BYTES(&m_pMsgRecv->MessageHead);
//
// We queried over UDP and the reply from DNS was truncated because the response
// was longer than the UDP packet size. We requery DNS using TCP unless SMTP is
// configured to use UDP only. RetryAsyncDnsQuery sets the members of this CAsyncDns
// object appropriately depending on whether if fails or succeeds. After calling
// RetryAsyncDnsQuery, this object must be deleted.
//
if(m_fUdp && !(m_dwFlags & DNS_FLAGS_UDP_ONLY) && m_pMsgRecv->MessageHead.Truncation)
{
_ASSERT(!(m_dwFlags & DNS_FLAGS_TCP_ONLY) && "Shouldn't have truncated reply over TCP");
DebugTrace((LPARAM) this, "Truncated reply - reissuing query using TCP");
RetryAsyncDnsQuery(FALSE); // FALSE == Do not use UDP
goto Exit;
}
DnsStatus = DnsParseMessage( m_pMsgRecv,
(WORD)( m_fUdp ? ( m_cbReceived ) : ( m_cbReceived - 2 )),
&m_ppRecord);
//
// End of resolve: HandleCompleted data examines the DnsStatus and results, and sets up
// member variables of CAsyncSmtpDns to either NDR messages, connect to the remote host
// or ack this queue for retry when the object is deleted.
//
HandleCompletedData(DnsStatus);
}
Exit:
TraceFunctLeaveEx((LPARAM) this);
return fRet;
}
BOOL CAsyncDns::ProcessClient (IN DWORD InputBufferLen,
IN DWORD dwCompletionStatus,
IN OUT OVERLAPPED * lpo)
{
BOOL RetStatus = FALSE;
DWORD dwIp = 0;
BOOL fRetryQuery = FALSE;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::ProcessClient()");
IncThreadCount();
//if lpo == NULL, then we timed out. Send an appropriate message
//then close the connection
if( (lpo == NULL) && (dwCompletionStatus == ERROR_SEM_TIMEOUT))
{
fRetryQuery = TRUE;
//
// fake a pending IO as we'll dec the overall count in the
// exit processing of this routine needs to happen before
// DisconnectClient else completing threads could tear us down
//
IncPendingIoCount();
DebugTrace( (LPARAM)this, "Async DNS client timed out");
DisconnectClient();
}
else if((InputBufferLen == 0) || (dwCompletionStatus != NO_ERROR))
{
fRetryQuery = TRUE;
DebugTrace((LPARAM) this, "CAsyncDns::ProcessClient: InputBufferLen = %d dwCompletionStatus = %d - Closing connection", InputBufferLen, dwCompletionStatus);
DisconnectClient();
}
else if (lpo == (OVERLAPPED *) &m_ReadOverlapped)
{
//A client based async IO completed
RetStatus = ProcessReadIO(InputBufferLen, dwCompletionStatus, lpo);
}
else if(lpo == (OVERLAPPED *) &m_WriteOverlapped)
{
RetStatus = ReadFile(m_pMsgRecvBuf, DNS_TCP_DEFAULT_PACKET_LENGTH);
if(!RetStatus)
{
ErrorTrace((LPARAM) this, "ReadFile failed");
fRetryQuery = TRUE;
}
}
if(fRetryQuery)
{
MarkDown(m_dwIpServer, dwCompletionStatus, m_fUdp);
if(g_TcpRegIpList.GetIp(&dwIp) != ERROR_SUCCESS)
{
m_dwDiagnostic = AQUEUE_E_NO_DNS_SERVERS;
SmtpLogEventSimple(SMTP_NO_DNS_SERVERS, DNS_ERROR_NO_DNS_SERVERS);
ErrorTrace((LPARAM) this, "No DNS servers");
} else {
RetryAsyncDnsQuery(m_fUdp); // This sets m_dwDiagnostic
}
}
DebugTrace((LPARAM)this,"ASYNC DNS - Pending IOs: %d", m_cPendingIoCount);
// Do NOT Touch the member variables past this POINT!
// This object may be deleted!
//
// decrement the overall pending IO count for this session
// tracing and ASSERTs if we're going down.
//
DecThreadCount();
if (DecPendingIoCount() == 0)
{
DisconnectClient();
DebugTrace((LPARAM)this,"ASYNC DNS - Pending IOs: %d", m_cPendingIoCount);
DebugTrace((LPARAM)this,"ASYNC DNS - Thread count: %d", m_cThreadCount);
delete this;
}
return TRUE;
}
int MxRand(char * host)
{
int hfunc = 0;
unsigned int seed = 0;;
seed = rand() & 0xffff;
hfunc = seed;
while (*host != '\0')
{
int c = *host++;
if (isascii((UCHAR)c) && isupper((UCHAR)c))
c = tolower(c);
hfunc = ((hfunc << 1) ^ c) % 2003;
}
hfunc &= 0xff;
return hfunc;
}
BOOL CAsyncDns::CheckList(void)
{
MXIPLIST_ENTRY * pEntry = NULL;
struct hostent *hp = NULL;
BOOL fRet = TRUE;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::CheckList");
if(m_Index == 0)
{
DebugTrace((LPARAM) this, "m_Index == 0 in CheckList");
m_fUsingMx = FALSE;
m_cbReceived = 0;
m_BytesToRead = 0;
m_FirstRead = TRUE;
DeleteDnsRec(m_AuxList);
m_AuxList = new SMTPDNS_RECS;
if(m_AuxList == NULL)
{
ErrorTrace((LPARAM) this, "m_AuxList = new SMTPDNS_RECS failed");
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
ZeroMemory(m_AuxList, sizeof(SMTPDNS_RECS));
m_AuxList->NumRecords = 1;
m_AuxList->DnsArray[0] = new MX_NAMES;
if(m_AuxList->DnsArray[0] == NULL)
{
ErrorTrace((LPARAM) this, "m_AuxList->DnsArray[0] = new MX_NAMES failed");
TraceFunctLeaveEx((LPARAM)this);
return FALSE;
}
m_AuxList->DnsArray[0]->NumEntries = 0;
InitializeListHead(&m_AuxList->DnsArray[0]->IpListHead);
lstrcpyn(m_AuxList->DnsArray[0]->DnsName, m_HostName, sizeof(m_AuxList->DnsArray[m_Index]->DnsName));
hp = gethostbyname (m_HostName);
if(hp != NULL)
{
for (DWORD Loop = 0; (hp->h_addr_list[Loop] != NULL); Loop++)
{
pEntry = new MXIPLIST_ENTRY;
if(pEntry != NULL)
{
m_AuxList->DnsArray[0]->NumEntries++;
CopyMemory(&pEntry->IpAddress, hp->h_addr_list[Loop], 4);
InsertTailList(&m_AuxList->DnsArray[0]->IpListHead, &pEntry->ListEntry);
}
else
{
fRet = FALSE;
ErrorTrace((LPARAM) this, "pEntry = new MXIPLIST_ENTRY failed in CheckList");
break;
}
}
}
else
{
fRet = FALSE;
}
}
TraceFunctLeaveEx((LPARAM)this);
return fRet;
}
BOOL CAsyncDns::SortMxList(void)
{
BOOL fRet = TRUE;
/* sort the records */
for (DWORD i = 0; i < m_Index; i++)
{
for (DWORD j = i + 1; j < m_Index; j++)
{
if (m_Prefer[i] > m_Prefer[j] ||
(m_Prefer[i] == m_Prefer[j] && m_Weight[i] > m_Weight[j]))
{
DWORD temp;
MX_NAMES *temp1;
temp = m_Prefer[i];
m_Prefer[i] = m_Prefer[j];
m_Prefer[j] = temp;
temp1 = m_AuxList->DnsArray[i];
m_AuxList->DnsArray[i] = m_AuxList->DnsArray[j];
m_AuxList->DnsArray[j] = temp1;
temp = m_Weight[i];
m_Weight[i] = m_Weight[j];
m_Weight[j] = temp;
}
}
if (m_SeenLocal && m_Prefer[i] >= m_LocalPref)
{
/* truncate higher preference part of list */
m_Index = i;
}
}
m_AuxList->NumRecords = m_Index;
if(!CheckList())
{
DeleteDnsRec(m_AuxList);
m_AuxList = NULL;
fRet = FALSE;
}
return fRet;
}
void CAsyncDns::ProcessMxRecord(PDNS_RECORD pnewRR)
{
DWORD Len = 0;
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::ProcessMxRecord");
if(m_Index >= SMTP_MAX_DNS_ENTRIES)
{
DebugTrace((LPARAM) this, "SMTP_MAX_DNS_ENTRIES reached for %s", m_HostName);
TraceFunctLeaveEx((LPARAM)this);
return;
}
if((pnewRR->wType == DNS_TYPE_MX) && pnewRR->Data.MX.nameExchange)
{
Len = lstrlen(pnewRR->Data.MX.nameExchange);
if(pnewRR->Data.MX.nameExchange[Len - 1] == '.')
{
pnewRR->Data.MX.nameExchange[Len - 1] = '\0';
}
DebugTrace((LPARAM) this, "Received MX rec %s with priority %d for %s", pnewRR->Data.MX.nameExchange, pnewRR->Data.MX.wPreference, m_HostName);
if(lstrcmpi(pnewRR->Data.MX.nameExchange, m_FQDNToDrop))
{
m_AuxList->DnsArray[m_Index] = new MX_NAMES;
if(m_AuxList->DnsArray[m_Index])
{
m_AuxList->DnsArray[m_Index]->NumEntries = 0;;
InitializeListHead(&m_AuxList->DnsArray[m_Index]->IpListHead);
lstrcpyn(m_AuxList->DnsArray[m_Index]->DnsName,pnewRR->Data.MX.nameExchange, sizeof(m_AuxList->DnsArray[m_Index]->DnsName));
m_Weight[m_Index] = MxRand (m_AuxList->DnsArray[m_Index]->DnsName);
m_Prefer[m_Index] = pnewRR->Data.MX.wPreference;
m_Index++;
}
else
{
DebugTrace((LPARAM) this, "Out of memory allocating MX_NAMES for %s", m_HostName);
}
}
else
{
if (!m_SeenLocal || pnewRR->Data.MX.wPreference < m_LocalPref)
m_LocalPref = pnewRR->Data.MX.wPreference;
m_SeenLocal = TRUE;
}
}
else if(pnewRR->wType == DNS_TYPE_A)
{
MXIPLIST_ENTRY * pEntry = NULL;
for(DWORD i = 0; i < m_Index; i++)
{
if(lstrcmpi(pnewRR->nameOwner, m_AuxList->DnsArray[i]->DnsName) == 0)
{
pEntry = new MXIPLIST_ENTRY;
if(pEntry != NULL)
{
m_AuxList->DnsArray[i]->NumEntries++;;
pEntry->IpAddress = pnewRR->Data.A.ipAddress;
InsertTailList(&m_AuxList->DnsArray[i]->IpListHead, &pEntry->ListEntry);
}
break;
}
}
}
TraceFunctLeaveEx((LPARAM)this);
}
void CAsyncDns::ProcessARecord(PDNS_RECORD pnewRR)
{
MXIPLIST_ENTRY * pEntry = NULL;
if(pnewRR->wType == DNS_TYPE_A)
{
pEntry = new MXIPLIST_ENTRY;
if(pEntry != NULL)
{
pEntry->IpAddress = pnewRR->Data.A.ipAddress;
InsertTailList(&m_AuxList->DnsArray[0]->IpListHead, &pEntry->ListEntry);
}
}
}
DNS_STATUS CAsyncDns::DnsParseMessage(
IN PDNS_MESSAGE_BUFFER pMsg,
IN WORD wMessageLength,
OUT PDNS_RECORD * ppRecord)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncDns::DnsParseMessage");
PDNS_RECORD pTmp = NULL;
m_SeenLocal = FALSE;
m_LocalPref = 256;
DNS_STATUS status = DnsExtractRecordsFromMessage_UTF8( pMsg, wMessageLength, ppRecord );
//
// Due to Raid #122555 m_fUsingMx is always TRUE in this function
// - hence we will always go a GetHostByName() if there is no MX
// record. It would be better Perf if we did a A record lookup.
//
DebugTrace((LPARAM) this, "Parsed DNS record for %s. status = 0x%08x", m_HostName, status);
switch(status)
{
case ERROR_SUCCESS:
//
// Got the DNS record we want.
//
DebugTrace((LPARAM) this, "Success: DNS record parsed");
pTmp = *ppRecord;
while( pTmp )
{
if( m_fUsingMx )
{
ProcessMxRecord( pTmp );
}
else
{
ProcessARecord( pTmp );
}
pTmp = pTmp->pNext;
}
if(m_fUsingMx)
{
//
// SortMxList sorts the MX records by preference and calls
// gethostbyname() to resolve A records for Mail Exchangers
// if needed (when the A records are not returned in the
// supplementary info).
//
if(SortMxList())
{
status = ERROR_SUCCESS;
DebugTrace((LPARAM) this, "SortMxList() succeeded.");
}
else
{
status = ERROR_RETRY;
ErrorTrace((LPARAM) this, "SortMxList() failed. Message will stay queued.");
}
}
break;
case DNS_ERROR_RCODE_NAME_ERROR:
// Fall through to using gethostbyname()
case DNS_INFO_NO_RECORDS:
// Non authoritative host not found.
// Fall through to using gethostbyname()
default:
DebugTrace((LPARAM) this, "Error in query: status = 0x%08x.", status);
//
// Use gethostbyname to resolve the hostname:
// One issue with our approach is that sometimes we will NDR the message
// on non-permanent errors, "like WINS server down", when gethostbyname
// fails. However, there's no way around it --- gethostbyname doesn't
// report errors in a reliable manner, so it's not possible to distinguish
// between permanent and temporary errors.
//
if (!CheckList ()) {
if(status == DNS_ERROR_RCODE_NAME_ERROR) {
ErrorTrace((LPARAM) this, "Authoritative error");
status = ERROR_NOT_FOUND;
} else {
ErrorTrace((LPARAM) this, "Retryable error");
status = ERROR_RETRY;
}
} else {
DebugTrace ((LPARAM) this, "Successfully resolved using gethostbyname");
status = ERROR_SUCCESS;
}
break;
}
DnsRecordListFree( *ppRecord, TRUE );
return( status );
}
DNS_STATUS
CAsyncDns::DnsSendRecord()
/*++
Routine Description:
Send message, receive response.
Arguments:
aipDnsServers -- specific DNS servers to query;
OPTIONAL, if specified overrides normal list associated with machine
Return Value:
ERROR_SUCCESS if successful.
Error code on failure.
--*/
{
DNS_STATUS status = 0;
m_pMsgRecvBuf = (BYTE*) new BYTE[DNS_TCP_DEFAULT_PACKET_LENGTH];
if(m_pMsgRecvBuf == NULL)
{
return( DNS_ERROR_NO_MEMORY );
}
status = Dns_OpenTcpConnectionAndSend();
return( status );
}
SOCKET
CAsyncDns::Dns_CreateSocket(
IN INT SockType
)
/*++
Routine Description:
Create socket.
Arguments:
SockType -- SOCK_DGRAM or SOCK_STREAM
Return Value:
socket if successful.
Otherwise INVALID_SOCKET.
--*/
{
SOCKET s;
//
// create socket
//
s = socket( AF_INET, SockType, 0 );
if ( s == INVALID_SOCKET )
{
return INVALID_SOCKET;
}
return s;
}
//-----------------------------------------------------------------------------
// Description:
// Constructor and Destructor for class to maintain a list of IP addresses
// (for DNS servers) and their state (UP or DOWN). The IP addresses are
// held in an IP_ARRAY, and the user must set m_DeleteFunc to deallocate
// the memory.
//-----------------------------------------------------------------------------
CTcpRegIpList::CTcpRegIpList()
{
m_IpListPtr = NULL;
//
// Shortcut to quickly figure out how many servers are down. This keeps track
// of how many servers are marked up currently. Used in ResetServersIfNeeded
// primarily to avoid checking the state of all servers in the usual case when
// all servers are up.
//
m_cUpServers = 0;
m_prgdwFailureTick = NULL;
m_prgfServerUp = NULL;
m_dwSig = TCP_REG_LIST_SIGNATURE;
}
CTcpRegIpList::~CTcpRegIpList()
{
_ASSERT(m_DeleteFunc);
if(m_DeleteFunc && m_IpListPtr)
m_DeleteFunc(m_IpListPtr);
if(m_prgdwFailureTick)
delete [] m_prgdwFailureTick;
if(m_prgfServerUp)
delete [] m_prgfServerUp;
m_IpListPtr = NULL;
m_prgdwFailureTick = NULL;
m_prgfServerUp = NULL;
}
//-----------------------------------------------------------------------------
// Description:
// Initializes or updates the IP address list. If this fails due to out
// of memory, there's precious little we can do. So we don't return anything
// and just delete the server IP list.
// Arguments:
// IpPtr - Ptr to IP_ARRAY of servers, this can be NULL in which case
// we assume that there are no servers. On shutdown, the SMTP code
// calls this with NULL.
//-----------------------------------------------------------------------------
void CTcpRegIpList::Update(PIP_ARRAY IpPtr)
{
BOOL fFatalError = FALSE;
TraceFunctEnterEx((LPARAM) this, "CTcpRegIpList::Update");
m_sl.ExclusiveLock();
_ASSERT(m_DeleteFunc);
if(m_IpListPtr && m_DeleteFunc)
m_DeleteFunc(m_IpListPtr);
if(m_prgdwFailureTick) {
delete [] m_prgdwFailureTick;
m_prgdwFailureTick = NULL;
}
if(m_prgfServerUp) {
delete [] m_prgfServerUp;
m_prgfServerUp = NULL;
}
// Note: this can be NULL
m_IpListPtr = IpPtr;
if(IpPtr == NULL) {
m_cUpServers = 0;
goto Exit;
}
m_cUpServers = IpPtr->cAddrCount;
m_prgdwFailureTick = new DWORD[m_cUpServers];
m_prgfServerUp = new BOOL[m_cUpServers];
if(!m_prgdwFailureTick || !m_prgfServerUp) {
ErrorTrace((LPARAM) this, "Failed to read DNS server list - out of memory");
fFatalError = TRUE;
goto Exit;
}
for(int i = 0; i < m_cUpServers; i++) {
m_prgdwFailureTick[i] = 0;
m_prgfServerUp[i] = TRUE;
}
Exit:
if(fFatalError) {
if(m_prgfServerUp) {
delete [] m_prgfServerUp;
m_prgfServerUp = NULL;
}
if(m_prgdwFailureTick) {
delete [] m_prgdwFailureTick;
m_prgdwFailureTick = NULL;
}
if(m_IpListPtr && m_DeleteFunc) {
m_DeleteFunc(m_IpListPtr);
m_IpListPtr = NULL;
}
m_cUpServers = 0;
}
m_sl.ExclusiveUnlock();
TraceFunctLeaveEx((LPARAM) this);
}
//-----------------------------------------------------------------------------
// Description:
// Return the IP address of a server known to be UP. This function also
// checks to see if any servers currently marked DOWN should be reset to
// the UP state again (based on a retry interval).
// Arguments:
// DWORD *pdwIpServer - Sets the DWORD pointed to, to the IP address of
// a server in the UP state.
// Returns:
// ERROR_SUCCESS - If a DNS server in the UP state was found
// ERROR_RETRY - If all DNS servers are currently down
// DNS_ERROR_NO_DNS_SERVERS - If no DNS servers are configured
//-----------------------------------------------------------------------------
DWORD CTcpRegIpList::GetIp(DWORD *pdwIpServer)
{
DWORD dwErr = ERROR_SUCCESS;
int iServer = 0;
_ASSERT(pdwIpServer != NULL);
*pdwIpServer = INADDR_NONE;
// Check if any servers were down and bring them up if needed
ResetServersIfNeeded();
m_sl.ShareLock();
if(m_IpListPtr == NULL || m_IpListPtr->cAddrCount == 0) {
dwErr = DNS_ERROR_NO_DNS_SERVERS;
goto Exit;
}
if(m_cUpServers == 0) {
dwErr = ERROR_RETRY;
goto Exit;
}
for(iServer = 0; iServer < (int)m_IpListPtr->cAddrCount; iServer++) {
if(m_prgfServerUp[iServer])
break;
}
if(m_prgfServerUp[iServer])
*pdwIpServer = m_IpListPtr->aipAddrs[iServer];
else
dwErr = ERROR_RETRY;
Exit:
m_sl.ShareUnlock();
return dwErr;
}
//-----------------------------------------------------------------------------
// Description:
// Marks a server in the list as down and sets the next retry time for
// that server. The next retry time is calculated modulo MAX_TICK_COUNT.
// Arguments:
// dwIp -- IP address of server to mark as DOWN
//-----------------------------------------------------------------------------
void CTcpRegIpList::MarkDown(DWORD dwIp)
{
int iServer = 0;
m_sl.ExclusiveLock();
if(m_IpListPtr == NULL || m_IpListPtr->cAddrCount == 0 || m_cUpServers == 0)
goto Exit;
// Find the server to mark as down among all the UP servers
for(iServer = 0; iServer < (int)m_IpListPtr->cAddrCount; iServer++) {
if(m_IpListPtr->aipAddrs[iServer] == dwIp)
break;
}
if(iServer < (int)m_IpListPtr->cAddrCount && m_prgfServerUp[iServer]) {
m_prgfServerUp[iServer] = FALSE;
_ASSERT(m_cUpServers > 0);
m_cUpServers--;
m_prgdwFailureTick[iServer] = GetTickCount();
}
Exit:
m_sl.ExclusiveUnlock();
return;
}
//-----------------------------------------------------------------------------
// Description:
// Checks if any servers are DOWN, and if the retry time has expired for
// those servers. If so those servers will be brought up.
//-----------------------------------------------------------------------------
void CTcpRegIpList::ResetServersIfNeeded()
{
int iServer = 0;
DWORD dwElapsedTicks = 0;
DWORD dwCurrentTick = 0;
//
// Quick check - if all servers are up (usual case) or there are no configured
// servers, there's nothing for us to do.
//
m_sl.ShareLock();
if(m_IpListPtr == NULL || m_IpListPtr->cAddrCount == 0 || m_cUpServers == m_IpListPtr->cAddrCount) {
m_sl.ShareUnlock();
return;
}
m_sl.ShareUnlock();
// Some servers are down... figure out which need to be brought up
m_sl.ExclusiveLock();
// Re-check that no one modified the list while we didn't have the sharelock
if(m_IpListPtr == NULL || m_IpListPtr->cAddrCount == 0 || m_cUpServers == m_IpListPtr->cAddrCount) {
m_sl.ExclusiveUnlock();
return;
}
dwCurrentTick = GetTickCount();
for(iServer = 0; iServer < (int)m_IpListPtr->cAddrCount; iServer++) {
if(m_prgfServerUp[iServer])
continue;
//
// Note: This also takes care of the special case where dwCurrentTick occurs
// after the wraparound and m_prgdwFailureTick occurs before the wraparound.
// This is because, in that case, the elapsed time is:
//
// time since wraparound + time before wraparound that failure occurred - 1
// (-1 is because it's 0 time to transition from MAX_TICK_VALUE to 0)
//
// = dwCurrentTick + (MAX_TICK_VALUE - m_prgdwFailureTick[iServer]) - 1
//
// Since MAX_TICK_VALUE == -1
//
// = dwCurrentTick + (-1 - m_prgdwFailureTick[iServer]) - 1
// = dwCurrentTick - m_prgdwFailureTick[iServer]
//
dwElapsedTicks = dwCurrentTick - m_prgdwFailureTick[iServer];
#define TICKS_TILL_RETRY 10 * 60 * 1000 // 10 minutes
if(dwElapsedTicks > TICKS_TILL_RETRY) {
m_prgfServerUp[iServer] = TRUE;
m_prgdwFailureTick[iServer] = 0;
m_cUpServers++;
_ASSERT(m_cUpServers <= (int)m_IpListPtr->cAddrCount);
}
}
m_sl.ExclusiveUnlock();
}