/*++ 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 #include "cdns.h" #include 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(); }