#include "dnsincs.h" #include extern void DeleteDnsRec(PSMTPDNS_RECS pDnsRec); CAsyncMxDns::CAsyncMxDns(char *MyFQDN) { lstrcpyn(m_FQDNToDrop, MyFQDN, sizeof(m_FQDNToDrop)); m_fUsingMx = TRUE; m_Index = 0; m_LocalPref = 256; m_SeenLocal = FALSE; m_AuxList = NULL; m_fMxLoopBack = FALSE; ZeroMemory (m_Weight, sizeof(m_Weight)); ZeroMemory (m_Prefer, sizeof(m_Prefer)); } //----------------------------------------------------------------------------- // Description: // Given a pDnsRec (array of host IP pairs) and an index into it, this // tries to resolve the host at the Index position. It is assumed that // the caller (GetMissingIpAddresses) has checked that the host at that // index lacks an IP address. // Arguments: // IN PSMTPDNS_RECS pDnsRec --- Array of (host, IP) pairs. // IN DWORD Index --- Index of host in pDnsRec to set IP for. // Returns: // TRUE --- Success IP was filled in for host. // FALSE --- Either the host was not resolved from DNS or an error // occurred (like "out of memory"). //----------------------------------------------------------------------------- BOOL CAsyncMxDns::GetIpFromDns(PSMTPDNS_RECS pDnsRec, DWORD Index) { MXIPLIST_ENTRY * pEntry = NULL; BOOL fReturn = FALSE; DWORD dwStatus = ERROR_SUCCESS; DWORD rgdwIpAddresses[SMTP_MAX_DNS_ENTRIES]; DWORD cIpAddresses = SMTP_MAX_DNS_ENTRIES; PIP_ARRAY pipDnsList = NULL; TraceFunctEnterEx((LPARAM) this, "CAsyncMxDns::GetIpFromDns"); fReturn = GetDnsIpArrayCopy(&pipDnsList); if(!fReturn) { ErrorTrace((LPARAM) this, "Unable to get DNS server list copy"); TraceFunctLeaveEx((LPARAM) this); return FALSE; } dwStatus = ResolveHost( pDnsRec->DnsArray[Index]->DnsName, pipDnsList, DNS_QUERY_STANDARD, rgdwIpAddresses, &cIpAddresses); if(dwStatus == ERROR_SUCCESS) { fReturn = TRUE; for (DWORD Loop = 0; !IsShuttingDown() && Loop < cIpAddresses; Loop++) { pEntry = new MXIPLIST_ENTRY; if(pEntry != NULL) { pDnsRec->DnsArray[Index]->NumEntries++; CopyMemory(&pEntry->IpAddress, &rgdwIpAddresses[Loop], 4); InsertTailList(&pDnsRec->DnsArray[Index]->IpListHead, &pEntry->ListEntry); } else { fReturn = FALSE; ErrorTrace((LPARAM) this, "Not enough memory"); SetLastError(ERROR_NOT_ENOUGH_MEMORY); break; } } } else { ErrorTrace((LPARAM) this, "gethostbyname failed on %s", pDnsRec->DnsArray[Index]->DnsName); SetLastError(ERROR_NO_MORE_ITEMS); } ReleaseDnsIpArray(pipDnsList); TraceFunctLeaveEx((LPARAM) this); return fReturn; } //----------------------------------------------------------------------------- // Description: // This runs through the list of hosts (MX hosts, or if no MX records were // returned, the single target host) and verifies that they all have been // resolved to IP addresses. If any have been found that do not have IP // addresses, it will call GetIpFromDns to resolve it. // Arguments: // IN PSMTPDNS_RECS pDnsRec -- Object containing Host-IP pairs. Hosts // without and IP are filled in. // Returns: // TRUE -- Success, all hosts have IP addresses. // FALSE -- Unable to resolve all hosts to IP addresses, or some internal // error occurred (like "out of memory" or "shutdown in progress". //----------------------------------------------------------------------------- BOOL CAsyncMxDns::GetMissingIpAddresses(PSMTPDNS_RECS pDnsRec) { DWORD Count = 0; DWORD Error = 0; BOOL fSucceededOnce = FALSE; if(pDnsRec == NULL) { return FALSE; } while(!IsShuttingDown() && pDnsRec->DnsArray[Count] != NULL) { if(IsListEmpty(&pDnsRec->DnsArray[Count]->IpListHead)) { SetLastError(NO_ERROR); if(!GetIpFromDns(pDnsRec, Count)) { Error = GetLastError(); if(Error != ERROR_NO_MORE_ITEMS) { return FALSE; } } else { fSucceededOnce = TRUE; } } else { fSucceededOnce = TRUE; } Count++; } return ( fSucceededOnce ); } 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 CAsyncMxDns::CheckList(void) { MXIPLIST_ENTRY * pEntry = NULL; BOOL fRet = TRUE; DWORD dwStatus = ERROR_SUCCESS; DWORD rgdwIpAddresses[SMTP_MAX_DNS_ENTRIES]; DWORD cIpAddresses = SMTP_MAX_DNS_ENTRIES; PIP_ARRAY pipDnsList = NULL; TraceFunctEnterEx((LPARAM) this, "CAsyncDns::CheckList"); if(m_Index == 0) { DebugTrace((LPARAM) this, "m_Index == 0 in CheckList"); m_fUsingMx = FALSE; 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)); fRet = GetDnsIpArrayCopy(&pipDnsList); if(!fRet) { ErrorTrace((LPARAM) this, "Unable to get DNS server list copy"); TraceFunctLeaveEx((LPARAM) this); return FALSE; } dwStatus = ResolveHost( m_HostName, pipDnsList, DNS_QUERY_STANDARD, rgdwIpAddresses, &cIpAddresses); if(dwStatus == ERROR_SUCCESS && cIpAddresses) { for (DWORD Loop = 0; Loop < cIpAddresses; Loop++) { pEntry = new MXIPLIST_ENTRY; if(pEntry != NULL) { m_AuxList->DnsArray[0]->NumEntries++; CopyMemory(&pEntry->IpAddress, &rgdwIpAddresses[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; } ReleaseDnsIpArray(pipDnsList); } TraceFunctLeaveEx((LPARAM)this); return fRet; } BOOL CAsyncMxDns::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 CAsyncMxDns::ProcessMxRecord(PDNS_RECORD pnewRR) { DWORD Len = 0; TraceFunctEnterEx((LPARAM) this, "CAsyncDns::ProcessMxRecord"); // // Leave room for NULL-termination of array // if(m_Index >= SMTP_MAX_DNS_ENTRIES-1) { 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 CAsyncMxDns::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); } } } //----------------------------------------------------------------------------- // Description: // Checks to see if any of the IP addresses returned by DNS belong to this // machine. This is a common configuration when there are backup mail // spoolers. To avoid mail looping, we should delete all MX records that // are less preferred than the record containing the local IP address. // // Arguments: // None. // Returns: // TRUE if no loopback // FALSE if loopback detected //----------------------------------------------------------------------------- BOOL CAsyncMxDns::CheckMxLoopback() { ULONG i = 0; ULONG cLocalIndex = 0; BOOL fSeenLocal = TRUE; DWORD dwIpAddress = INADDR_NONE; DWORD dwLocalPref = 256; PLIST_ENTRY pListHead = NULL; PLIST_ENTRY pListTail = NULL; PLIST_ENTRY pListCurrent = NULL; PMXIPLIST_ENTRY pMxIpListEntry = NULL; TraceFunctEnterEx((LPARAM)this, "CAsyncMxDns::CheckMxLoopback"); if(!m_AuxList) { TraceFunctLeaveEx((LPARAM)this); return TRUE; } // // m_AuxList is a sorted list of MX records. Scan through it searching // for an MX record with a local-IP address. cLocalIndex is set to the // index, within m_AuxList, of this record. // while(m_AuxList->DnsArray[cLocalIndex] != NULL) { pListTail = &(m_AuxList->DnsArray[cLocalIndex]->IpListHead); pListHead = m_AuxList->DnsArray[cLocalIndex]->IpListHead.Flink; pListCurrent = pListHead; while(pListCurrent != pListTail) { pMxIpListEntry = CONTAINING_RECORD(pListCurrent, MXIPLIST_ENTRY, ListEntry); dwIpAddress = pMxIpListEntry->IpAddress; if(IsAddressMine(dwIpAddress)) { DNS_PRINTF_MSG("Local host's IP is one of the target IPs.\n"); DNS_PRINTF_MSG("Discarding all equally or less-preferred IP addresses.\n"); DebugTrace((LPARAM)this, "Local record found in MX list, name=%s, pref=%d, ip=%08x", m_AuxList->DnsArray[cLocalIndex]->DnsName, m_Prefer[cLocalIndex], dwIpAddress); // All records with preference > m_Prefer[cLocalIndex] should be deleted. Since // m_AuxList is sorted by preference, we need to delete everthing with index > // cLocalIndex. However since there may be some records with preference == local- // preference, which occur before cLocalIndex, we walk backwards till we find // the first record with preference = m_Prefer[cLocalIndex]. dwLocalPref = m_Prefer[cLocalIndex]; while(cLocalIndex > 0 && dwLocalPref == m_Prefer[cLocalIndex]) cLocalIndex--; if(dwLocalPref != m_Prefer[cLocalIndex]) cLocalIndex++; fSeenLocal = TRUE; // All records > cLocalIndex are even less preferred than this one, // (since m_AuxList already sorted) and will be deleted. goto END_SEARCH; } pListCurrent = pListCurrent->Flink; } cLocalIndex++; } END_SEARCH: // // If a local-IP address was found, delete all less-preferred records // if(fSeenLocal) { DebugTrace((LPARAM)this, "Deleting all MX records with lower preference than %d", m_Prefer[cLocalIndex]); for(i = cLocalIndex; m_AuxList->DnsArray[i] != NULL; i++) { if(!m_AuxList->DnsArray[i]->DnsName[0]) continue; while(!IsListEmpty(&(m_AuxList->DnsArray[i]->IpListHead))) { pListCurrent = RemoveHeadList(&(m_AuxList->DnsArray[i]->IpListHead)); pMxIpListEntry = CONTAINING_RECORD(pListCurrent, MXIPLIST_ENTRY, ListEntry); delete pMxIpListEntry; } delete m_AuxList->DnsArray[i]; m_AuxList->DnsArray[i] = NULL; } m_AuxList->NumRecords = cLocalIndex; // No records left if(m_AuxList->NumRecords == 0) { DNS_PRINTF_ERR("DNS configuration error (loopback), messages will be NDRed.\n"); DNS_PRINTF_ERR("Local host's IP address is the most preferred MX record.\n"); ErrorTrace((LPARAM)this, "Possible misconfiguration: most preferred MX record is loopback"); _ASSERT(m_AuxList->pMailMsgObj == NULL); delete m_AuxList; m_AuxList = NULL; return FALSE; } } TraceFunctLeaveEx((LPARAM)this); return TRUE; } void CAsyncMxDns::DnsProcessReply( DWORD status, PDNS_RECORD pRecordList) { TraceFunctEnterEx((LPARAM) this, "CAsyncDns::DnsParseMessage"); PDNS_RECORD pTmp = NULL; m_SeenLocal = FALSE; m_LocalPref = 256; m_AuxList = new SMTPDNS_RECS; if(!m_AuxList) { return; } ZeroMemory(m_AuxList, sizeof(SMTPDNS_RECS)); // // 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. // DNS_PRINTF_MSG("Processing MX/A records in reply.\n"); DebugTrace((LPARAM) this, "Success: DNS record parsed"); pTmp = pRecordList; 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). // DNS_PRINTF_MSG("Sorting MX records by priority.\n"); 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) { DNS_PRINTF_ERR("Host does not exist in DNS. Messages will be NDRed.\n"); ErrorTrace((LPARAM) this, "Authoritative error"); status = ERROR_NOT_FOUND; } else { DNS_PRINTF_ERR("Host could not be resolved. Messages will be retried later.\n"); ErrorTrace((LPARAM) this, "Retryable error"); status = ERROR_RETRY; } } else { DebugTrace ((LPARAM) this, "Successfully resolved using gethostbyname"); status = ERROR_SUCCESS; } break; } // // Make a last ditch effort to fill in the IP addresses for any hosts // that are still unresolved. // if(m_AuxList && status == ERROR_SUCCESS) { if(!GetMissingIpAddresses(m_AuxList)) { DeleteDnsRec(m_AuxList); m_AuxList = NULL; status = ERROR_RETRY; goto Exit; } if(!CheckMxLoopback()) { m_fMxLoopBack = TRUE; DeleteDnsRec(m_AuxList); m_AuxList = NULL; TraceFunctLeaveEx((LPARAM) this); return; } } // // End of resolve: HandleCompleted data examines the DnsStatus and results, and sets up // member variables of CAsyncMxDns to either NDR messages, connect to the remote host // or ack this queue for retry when the object is deleted. // Exit: HandleCompletedData(status); return; }