/*++ Copyright (c) 1997-2001 Microsoft Corporation Module Name: query.c Abstract: Domain Name System (DNS) API Query routines. Author: Jim Gilroy (jamesg) January, 1997 Revision History: --*/ #include "local.h" // // TTL for answering IP string queries // (use a week) // #define IPSTRING_RECORD_TTL (604800) // // Max number of server's we'll ever bother to extract from packet // (much more and you're out of UDP packet space anyway) // #define MAX_NAME_SERVER_COUNT (20) // // Query utilities // // DCR: move to library packet stuff // BOOL IsEmptyDnsResponse( IN PDNS_RECORD pRecordList ) /*++ Routine Description: Check for no-answer response. Arguments: pRecordList -- record list to check Return Value: TRUE if no-answers FALSE if answers --*/ { PDNS_RECORD prr = pRecordList; BOOL fempty = TRUE; while ( prr ) { if ( prr->Flags.S.Section == DNSREC_ANSWER ) { fempty = FALSE; break; } prr = prr->pNext; } return fempty; } BOOL IsEmptyDnsResponseFromResolver( IN PDNS_RECORD pRecordList ) /*++ Routine Description: Check for no-answer response. Arguments: pRecordList -- record list to check Return Value: TRUE if no-answers FALSE if answers --*/ { PDNS_RECORD prr = pRecordList; BOOL fempty = TRUE; // // resolver sends every thing back as ANSWER section // or section==0 for host file // // // DCR: this is lame because the query interface to the // resolver is lame // while ( prr ) { if ( prr->Flags.S.Section == DNSREC_ANSWER || prr->Flags.S.Section == 0 ) { fempty = FALSE; break; } prr = prr->pNext; } return fempty; } VOID FixupNameOwnerPointers( IN OUT PDNS_RECORD pRecord ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { PDNS_RECORD prr = pRecord; PTSTR pname = pRecord->pName; DNSDBG( TRACE, ( "FixupNameOwnerPointers()\n" )); while ( prr ) { if ( prr->pName == NULL ) { prr->pName = pname; } else { pname = prr->pName; } prr = prr->pNext; } } BOOL IsCacheableNameError( IN PDNS_NETINFO pNetInfo ) /*++ Routine Description: Determine if name error is cacheable. To this is essentially a check that DNS received results on all networks. Arguments: pNetInfo -- pointer to network info used in query Return Value: TRUE if name error cacheable. FALSE otherwise (some network did not respond) --*/ { DWORD iter; PDNS_ADAPTER padapter; DNSDBG( TRACE, ( "IsCacheableNameError()\n" )); if ( !pNetInfo ) { ASSERT( FALSE ); return TRUE; } // // check each adapter // - any that are capable of responding (have DNS servers) // MUST have responded in order for response to be // cacheable // // DCR: return flags DCR // - adapter queried flag // - got response flag (valid response flag?) // - explict negative answer flag // // DCR: cachable negative should come back directly from query // perhaps in netinfo as flag -- "negative on all adapters" // for ( iter = 0; iter < pNetInfo->AdapterCount; iter++ ) { padapter = pNetInfo->AdapterArray[iter]; if ( ( padapter->InfoFlags & DNS_FLAG_IGNORE_ADAPTER ) || ( padapter->RunFlags & RUN_FLAG_STOP_QUERY_ON_ADAPTER ) ) { continue; } // if negative answer on adapter -- fine if ( padapter->Status == DNS_ERROR_RCODE_NAME_ERROR || padapter->Status == DNS_INFO_NO_RECORDS ) { ASSERT( padapter->RunFlags & RUN_FLAG_STOP_QUERY_ON_ADAPTER ); continue; } // note, the above should map one-to-one with query stop ASSERT( !(padapter->RunFlags & RUN_FLAG_STOP_QUERY_ON_ADAPTER) ); // if adapter has no DNS server -- fine // in this case PnP before useful, and the PnP event // will flush the cache if ( padapter->ServerCount == 0 ) { continue; } // otherwise, this adapter was queried but could not produce a response DNSDBG( TRACE, ( "IsCacheableNameError() -- FALSE\n" "\tadapter %d (%S) did not receive response\n" "\treturn status = %d\n" "\treturn flags = %08x\n", iter, padapter->pszAdapterGuidName, padapter->Status, padapter->RunFlags )); return FALSE; } return TRUE; } // // Query name building utils // BOOL ValidateQueryTld( IN PSTR pTld ) /*++ Routine Description: Validate query TLD Arguments: pTld -- TLD to validate Return Value: TRUE if valid FALSE otherwise --*/ { // // numeric // if ( g_ScreenBadTlds & DNS_TLD_SCREEN_NUMERIC ) { if ( Dns_IsNameNumeric_A( pTld ) ) { return FALSE; } } // // bogus TLDs // if ( g_ScreenBadTlds & DNS_TLD_SCREEN_WORKGROUP ) { if ( Dns_NameCompare_UTF8( "workgroup", pTld )) { return FALSE; } } // not sure about these // probably won't turn on screening by default if ( g_ScreenBadTlds & DNS_TLD_SCREEN_DOMAIN ) { if ( Dns_NameCompare_UTF8( "domain", pTld )) { return FALSE; } } if ( g_ScreenBadTlds & DNS_TLD_SCREEN_OFFICE ) { if ( Dns_NameCompare_UTF8( "office", pTld )) { return FALSE; } } if ( g_ScreenBadTlds & DNS_TLD_SCREEN_HOME ) { if ( Dns_NameCompare_UTF8( "home", pTld )) { return FALSE; } } return TRUE; } BOOL ValidateQueryName( IN PQUERY_BLOB pBlob, IN PSTR pName, IN PSTR pDomain ) /*++ Routine Description: Validate name for wire query. Arguments: pBlob -- query blob pName -- name; may be any sort of name pDomain -- domain name to append Return Value: TRUE if name query will be valid. FALSE otherwise. --*/ { WORD wtype; PSTR pnameTld; PSTR pdomainTld; // no screening -- bail if ( g_ScreenBadTlds == 0 ) { return TRUE; } // only screening for standard types // - A, AAAA, SRV wtype = pBlob->wType; if ( wtype != DNS_TYPE_A && wtype != DNS_TYPE_AAAA && wtype != DNS_TYPE_SRV ) { return TRUE; } // get name TLD pnameTld = Dns_GetTldForName( pName ); // // if no domain appended // - exclude single label // - exclude bad TLD (numeric, bogus domain) // - but allow root queries // // DCR: MS DCS screening // screen // _msdcs. // will probably be unappended query // if ( !pDomain ) { if ( !pnameTld || !ValidateQueryTld( pnameTld ) ) { goto Failed; } return TRUE; } // // domain appended // - exclude bad TLD (numeric, bogus domain) // - exclude matching TLD // pdomainTld = Dns_GetTldForName( pDomain ); if ( !pdomainTld ) { pdomainTld = pDomain; } if ( !ValidateQueryTld( pdomainTld ) ) { goto Failed; } // screen repeated TLD if ( g_ScreenBadTlds & DNS_TLD_SCREEN_REPEATED ) { if ( Dns_NameCompare_UTF8( pnameTld, pdomainTld ) ) { goto Failed; } } return TRUE; Failed: DNSDBG( QUERY, ( "Failed invalid query name:\n" "\tname %s\n" "\tdomain %s\n", pName, pDomain )); return FALSE; } PSTR GetNextAdapterDomainName( IN OUT PDNS_NETINFO pNetInfo ) /*++ Routine Description: Get next adapter domain name to query. Arguments: pNetInfo -- DNS Network info for query; adapter data will be modified (InfoFlags field) to indicate which adapter to query and which to skip query on Return Value: Ptr to domain name (UTF8) to query. NULL if no more domain names to query. --*/ { DWORD iter; PSTR pqueryDomain = NULL; DNSDBG( TRACE, ( "GetNextAdapterDomainName()\n" )); if ( ! pNetInfo ) { ASSERT( FALSE ); return NULL; } IF_DNSDBG( OFF ) { DnsDbg_NetworkInfo( "Net info to get adapter domain name from: ", pNetInfo ); } // // check each adapter // - first unqueried adapter with name is chosen // - other adapters with // - matching name => included in query // - non-matching => turned OFF for query // // DCR: query on\off should use adapter dynamic flags // for ( iter = 0; iter < pNetInfo->AdapterCount; iter++ ) { PDNS_ADAPTER padapter; PSTR pdomain; padapter = pNetInfo->AdapterArray[iter]; // netinfo should come in with name specific flags clean DNS_ASSERT( !(padapter->RunFlags & RUN_FLAG_SINGLE_NAME_MASK) ); // // ignore // - ignored adapter OR // - previously queried adapter domain // note: it can't match any "fresh" domain we come up with // as we always collect all matches if ( (padapter->InfoFlags & DNS_FLAG_IGNORE_ADAPTER) || (padapter->RunFlags & RUN_FLAG_QUERIED_ADAPTER_DOMAIN) ) { padapter->RunFlags |= RUN_FLAG_STOP_QUERY_ON_ADAPTER; continue; } // no domain name -- always off pdomain = padapter->pszAdapterDomain; if ( !pdomain ) { padapter->RunFlags |= (RUN_FLAG_QUERIED_ADAPTER_DOMAIN | RUN_FLAG_STOP_QUERY_ON_ADAPTER); continue; } // first "fresh" domain name -- save, turn on and flag as used if ( !pqueryDomain ) { pqueryDomain = pdomain; padapter->RunFlags |= RUN_FLAG_QUERIED_ADAPTER_DOMAIN; continue; } // other "fresh" domain names // - if matches query domain => on for query // - no match => off if ( Dns_NameCompare_UTF8( pqueryDomain, pdomain ) ) { padapter->RunFlags |= RUN_FLAG_QUERIED_ADAPTER_DOMAIN; continue; } else { padapter->RunFlags |= RUN_FLAG_STOP_QUERY_ON_ADAPTER; continue; } } // // if no adapter domain name -- clear STOP flag // - all adapters participate in other names (name devolution) // if ( !pqueryDomain ) { for ( iter = 0; iter < pNetInfo->AdapterCount; iter++ ) { PDNS_ADAPTER padapter = pNetInfo->AdapterArray[iter]; padapter->RunFlags &= (~RUN_FLAG_SINGLE_NAME_MASK ); } DNSDBG( INIT2, ( "GetNextAdapterDomainName out of adapter names.\n" )); pNetInfo->ReturnFlags |= RUN_FLAG_QUERIED_ADAPTER_DOMAIN; } IF_DNSDBG( INIT2 ) { if ( pqueryDomain ) { DnsDbg_NetworkInfo( "Net info after adapter name select: ", pNetInfo ); } } DNSDBG( INIT2, ( "Leaving GetNextAdapterDomainName() => %s\n", pqueryDomain )); return pqueryDomain; } PSTR GetNextDomainNameToAppend( IN OUT PDNS_NETINFO pNetInfo, OUT PDWORD pSuffixFlags ) /*++ Routine Description: Get next adapter domain name to query. Arguments: pNetInfo -- DNS Network info for query; adapter data will be modified (RunFlags field) to indicate which adapter to query and which to skip query on pSuffixFlags -- flags associated with the use of this suffix Return Value: Ptr to domain name (UTF8) to query. NULL if no more domain names to query. --*/ { PSTR psearchName; PSTR pdomain; // // search list if real search list // // if suffix flags zero, then this is REAL search list // or is PDN name // psearchName = SearchList_GetNextName( pNetInfo->pSearchList, FALSE, // not reset pSuffixFlags ); if ( psearchName && (*pSuffixFlags == 0) ) { // found regular search name -- done DNSDBG( INIT2, ( "getNextDomainName from search list => %s, %d\n", psearchName, *pSuffixFlags )); return( psearchName ); } // // try adapter domain names // // but ONLY if search list is dummy; if real we only // use search list entries // // DCR_CLEANUP: eliminate bogus search list // if ( pNetInfo->InfoFlags & DNS_FLAG_DUMMY_SEARCH_LIST && ! (pNetInfo->ReturnFlags & RUN_FLAG_QUERIED_ADAPTER_DOMAIN) ) { pdomain = GetNextAdapterDomainName( pNetInfo ); if ( pdomain ) { *pSuffixFlags = DNS_QUERY_USE_QUICK_TIMEOUTS; DNSDBG( INIT2, ( "getNextDomainName from adapter domain name => %s, %d\n", pdomain, *pSuffixFlags )); // back the search list up one tick // we queried through it above, so if it was returing // a name, we need to get that name again on next query if ( psearchName ) { ASSERT( pNetInfo->pSearchList->CurrentNameIndex > 0 ); pNetInfo->pSearchList->CurrentNameIndex--; } return( pdomain ); } } // // DCR_CLEANUP: remove devolution from search list and do explicitly // - its cheap (or do it once and save, but store separately) // // // finally use and devolved search names (or other nonsense) // *pSuffixFlags = DNS_QUERY_USE_QUICK_TIMEOUTS; DNSDBG( INIT2, ( "getNextDomainName from devolution\\other => %s, %d\n", psearchName, *pSuffixFlags )); return( psearchName ); } PSTR GetNextQueryName( IN OUT PQUERY_BLOB pBlob ) /*++ Routine Description: Get next name to query. Arguments: pBlob - blob of query information Uses: NameOriginalWire NameAttributes QueryCount pNetworkInfo Sets: NameWire -- set with appended wire name pNetworkInfo -- runtime flags set to indicate which adapters are queried NameFlags -- set with properties of name fAppendedName -- set when name appended Return Value: Ptr to name to query with. - will be orginal name on first query if name is multilabel name - otherwise will be NameWire buffer which will contain appended name composed of pszName and some domain name NULL if no more names to append --*/ { PSTR pnameOrig = pBlob->NameOriginalWire; PSTR pdomainName = NULL; PSTR pnameBuf; DWORD queryCount = pBlob->QueryCount; DWORD nameAttributes = pBlob->NameAttributes; DNSDBG( TRACE, ( "GetNextQueryName( %p )\n", pBlob )); // default suffix flags pBlob->NameFlags = 0; // // FQDN // - send FQDN only // if ( nameAttributes & DNS_NAME_IS_FQDN ) { if ( queryCount == 0 ) { #if 0 // currently won't even validate FQDN if ( ValidateQueryName( pBlob, pnameOrig, NULL ) ) { return pnameOrig; } #endif return pnameOrig; } DNSDBG( QUERY, ( "No append for FQDN name %s -- end query.\n", pnameOrig )); return NULL; } // // multilabel // - first pass on name itself -- if valid // // DCR: intelligent choice on multi-label whether append first // or go to wire first (example foo.ntdev) could append // first // if ( nameAttributes & DNS_NAME_MULTI_LABEL ) { if ( queryCount == 0 ) { if ( ValidateQueryName( pBlob, pnameOrig, NULL ) ) { return pnameOrig; } } if ( !g_AppendToMultiLabelName ) { DNSDBG( QUERY, ( "No append allowed on multi-label name %s -- end query.\n", pnameOrig )); return NULL; } // falls through to appending on multi-label names } // // not FQDN -- append a domain name // - next search name (if available) // - otherwise next adapter domain name // pnameBuf = pBlob->NameWire; while ( 1 ) { pdomainName = GetNextDomainNameToAppend( pBlob->pNetworkInfo, & pBlob->NameFlags ); if ( !pdomainName ) { DNSDBG( QUERY, ( "No more domain names to append -- end query\n" )); return NULL; } if ( !ValidateQueryName( pBlob, pnameOrig, pdomainName ) ) { continue; } // append domain name to name if ( Dns_NameAppend_A( pnameBuf, DNS_MAX_NAME_BUFFER_LENGTH, pnameOrig, pdomainName ) ) { pBlob->fAppendedName = TRUE; break; } } DNSDBG( QUERY, ( "GetNextQueryName() result => %s\n", pnameBuf )); return pnameBuf; } DNS_STATUS QueryDirectEx( IN OUT PDNS_MSG_BUF * ppMsgResponse, OUT PDNS_RECORD * ppResponseRecords, IN PDNS_HEADER pHeader, IN BOOL fNoHeaderCounts, IN PDNS_NAME pszQuestionName, IN WORD wQuestionType, IN PDNS_RECORD pRecords, IN DWORD dwFlags, IN PIP_ARRAY aipDnsServers, IN OUT PDNS_NETINFO pNetInfo ) /*++ Routine Description: Query. Arguments: ppMsgResponse -- addr to recv ptr to response buffer; caller MUST free buffer ppResponseRecord -- address to receive ptr to record list returned from query pHead -- DNS header to send fNoHeaderCounts - do NOT include record counts in copying header pszQuestionName -- DNS name to query; Unicode string if dwFlags has DNSQUERY_UNICODE_NAME set. ANSI string otherwise. wType -- query type pRecords -- address to receive ptr to record list returned from query dwFlags -- query flags aipDnsServers -- specific DNS servers to query; OPTIONAL, if specified overrides normal list associated with machine pDnsNetAdapters -- DNS servers to query; if NULL get current list Return Value: ERROR_SUCCESS if successful. Error code on failure. --*/ { PDNS_MSG_BUF psendMsg; DNS_STATUS status = DNS_ERROR_NO_MEMORY; DNSDBG( QUERY, ( "QueryDirectEx()\n" "\tname %s\n" "\ttype %d\n" "\theader %p\n" "\t - counts %d\n" "\trecords %p\n" "\tflags %08x\n" "\trecv msg %p\n" "\trecv records %p\n" "\tserver IPs %p\n" "\tadapter list %p\n", pszQuestionName, wQuestionType, pHeader, fNoHeaderCounts, pRecords, dwFlags, ppMsgResponse, ppResponseRecords, aipDnsServers, pNetInfo )); // // build send packet // psendMsg = Dns_BuildPacket( pHeader, fNoHeaderCounts, pszQuestionName, wQuestionType, pRecords, dwFlags, FALSE // query, not an update ); if ( !psendMsg ) { status = ERROR_INVALID_NAME; goto Cleanup; } #if MULTICAST_ENABLED // // QUESTION: mcast test is not complete here // - should first test that we actually do it // including whether we have DNS servers // FIXME: then when we do do it -- encapsulate it // ShouldMulicastQuery() // // Check to see if name is for something in the multicast local domain. // If so, set flag to multicast this query only. // if ( Dns_NameCompareEx( pszQuestionName, ( dwFlags & DNSQUERY_UNICODE_NAME ) ? (LPSTR) MULTICAST_DNS_LOCAL_DOMAIN_W : MULTICAST_DNS_LOCAL_DOMAIN, 0, ( dwFlags & DNSQUERY_UNICODE_NAME ) ? DnsCharSetUnicode : DnsCharSetUtf8 ) == DnsNameCompareRightParent ) { dwFlags |= DNS_QUERY_MULTICAST_ONLY; } #endif // // send query and receive response // Trace_LogQueryEvent( psendMsg, wQuestionType ); status = Dns_SendAndRecv( psendMsg, ppMsgResponse, ppResponseRecords, dwFlags, aipDnsServers, pNetInfo ); Trace_LogResponseEvent( psendMsg, ( ppResponseRecords && *ppResponseRecords ) ? (*ppResponseRecords)->wType : 0, status ); Cleanup: FREE_HEAP( psendMsg ); DNSDBG( QUERY, ( "Leaving QueryDirectEx(), status = %s (%d)\n", Dns_StatusString(status), status )); return( status ); } DNS_STATUS Query_SingleName( IN OUT PQUERY_BLOB pBlob ) /*++ Routine Description: Query single name. Arguments: pBlob - query blob Return Value: ERROR_SUCCESS if successful. Error code on failure. --*/ { PDNS_MSG_BUF psendMsg = NULL; DNS_STATUS status = DNS_ERROR_NO_MEMORY; DWORD flags = pBlob->Flags; DNSDBG( QUERY, ( "Query_SingleName( %p )\n", pBlob )); IF_DNSDBG( QUERY ) { DnsDbg_QueryBlob( "Enter Query_SingleName()", pBlob ); } // // cache\hostfile callback on appended name // - note that queried name was already done // (in resolver or in Query_Main()) // if ( pBlob->pfnQueryCache && pBlob->fAppendedName ) { if ( (pBlob->pfnQueryCache)( pBlob ) ) { status = pBlob->Status; goto Cleanup; } } // // if wire disallowed -- stop here // if ( flags & DNS_QUERY_NO_WIRE_QUERY ) { status = DNS_ERROR_NAME_NOT_FOUND_LOCALLY; pBlob->Status = status; goto Cleanup; } // // build send packet // psendMsg = Dns_BuildPacket( NULL, // no header 0, // no header counts pBlob->pNameWire, pBlob->wType, NULL, // no records flags, FALSE // query, not an update ); if ( !psendMsg ) { status = DNS_ERROR_INVALID_NAME; goto Cleanup; } #if MULTICAST_ENABLED // // QUESTION: mcast test is not complete here // - should first test that we actually do it // including whether we have DNS servers // FIXME: then when we do do it -- encapsulate it // ShouldMulicastQuery() // // Check to see if name is for something in the multicast local domain. // If so, set flag to multicast this query only. // if ( Dns_NameCompareEx( pBlob->pName, ( flags & DNSQUERY_UNICODE_NAME ) ? (LPSTR) MULTICAST_DNS_LOCAL_DOMAIN_W : MULTICAST_DNS_LOCAL_DOMAIN, 0, ( flags & DNSQUERY_UNICODE_NAME ) ? DnsCharSetUnicode : DnsCharSetUtf8 ) == DnsNameCompareRightParent ) { flags |= DNS_QUERY_MULTICAST_ONLY; } #endif // // send query and receive response // Trace_LogQueryEvent( psendMsg, pBlob->wType ); status = Dns_SendAndRecv( psendMsg, (flags & DNS_QUERY_RETURN_MESSAGE) ? &pBlob->pRecvMsg : NULL, & pBlob->pRecords, flags, pBlob->pDnsServers, pBlob->pNetworkInfo ); Trace_LogResponseEvent( psendMsg, ( pBlob->pRecords ) ? (pBlob->pRecords)->wType : 0, status ); Cleanup: FREE_HEAP( psendMsg ); DNSDBG( QUERY, ( "Leaving Query_SingleName(), status = %s (%d)\n", Dns_StatusString(status), status )); IF_DNSDBG( QUERY ) { DnsDbg_QueryBlob( "Blob leaving Query_SingleName()", pBlob ); } return( status ); } DNS_STATUS Query_Main( IN OUT PQUERY_BLOB pBlob ) /*++ Routine Description: Main query routine. Does all the query processing - local lookup - name appending - cache\hostfile lookup on appended name - query to server Arguments: pBlob -- query info blob Return Value: ERROR_SUCCESS if successful response. DNS_INFO_NO_RECORDS on no records for type response. DNS_ERROR_RCODE_NAME_ERROR on name error. DNS_ERROR_INVALID_NAME on bad name. None --*/ { DNS_STATUS status = DNS_ERROR_NAME_NOT_FOUND_LOCALLY; PSTR pdomainName = NULL; PDNS_RECORD precords; DWORD queryFlags; DWORD suffixFlags = 0; DWORD nameAttributes; DNS_STATUS bestQueryStatus = ERROR_SUCCESS; BOOL fcacheNegative = TRUE; DWORD flagsIn = pBlob->Flags; PDNS_NETINFO pnetInfo = pBlob->pNetworkInfo; DWORD nameLength; DWORD bufLength; DWORD queryCount; DNSDBG( TRACE, ( "Query_Main( %p )\n" "\t%S, f=%08x, type=%d, time = %d\n", pBlob, pBlob->pNameOrig, flagsIn, pBlob->wType, Dns_GetCurrentTimeInSeconds() )); // // clear out params // pBlob->pRecords = NULL; pBlob->pLocalRecords = NULL; pBlob->fCacheNegative = FALSE; pBlob->fNoIpLocal = FALSE; pBlob->NetFailureStatus = ERROR_SUCCESS; // // convert name to wire format // bufLength = DNS_MAX_NAME_BUFFER_LENGTH; nameLength = Dns_NameCopy( pBlob->NameOriginalWire, & bufLength, (PSTR) pBlob->pNameOrig, 0, // name is NULL terminated DnsCharSetUnicode, DnsCharSetWire ); if ( nameLength == 0 ) { return DNS_ERROR_INVALID_NAME; } nameLength--; pBlob->NameLength = nameLength; pBlob->pNameOrigWire = pBlob->NameOriginalWire; // // determine name properties // - determines number and order of name queries // nameAttributes = Dns_GetNameAttributes( pBlob->NameOriginalWire ); if ( flagsIn & DNS_QUERY_TREAT_AS_FQDN ) { nameAttributes |= DNS_NAME_IS_FQDN; } pBlob->NameAttributes = nameAttributes; // // hostfile lookup // - called in process // - hosts file lookup allowed // -> then must do hosts file lookup before appending\queries // // note: this matches the hostsfile\cache lookup in resolver // before call; hosts file queries to appended names are // handled together by callback in Query_SingleName() // // we MUST make this callback here, because it must PRECEDE // the local name call, as some customers specifically direct // some local mappings in the hosts file // if ( pBlob->pfnQueryCache == QueryHostFile && ! (flagsIn & DNS_QUERY_NO_HOSTS_FILE) ) { pBlob->pNameWire = pBlob->pNameOrigWire; if ( QueryHostFile( pBlob ) ) { status = pBlob->Status; goto Done; } } // // check for local name // - if successful, skip wire query // if ( ! (flagsIn & DNS_QUERY_NO_LOCAL_NAME) ) { status = GetRecordsForLocalName( pBlob ); if ( status == ERROR_SUCCESS && !pBlob->fNoIpLocal ) { DNS_ASSERT( pBlob->pRecords && pBlob->pRecords == pBlob->pLocalRecords ); goto Done; } } // // query until // - successfull // - exhaust names to query with // queryCount = 0; while ( 1 ) { PSTR pqueryName; // clean name specific info from list if ( queryCount != 0 ) { NetInfo_Clean( pnetInfo, CLEAR_LEVEL_SINGLE_NAME ); } // // next query name // pqueryName = GetNextQueryName( pBlob ); if ( !pqueryName ) { if ( queryCount == 0 ) { status = DNS_ERROR_INVALID_NAME; } break; } pBlob->QueryCount = ++queryCount; pBlob->pNameWire = pqueryName; DNSDBG( QUERY, ( "Query %d is for name %s\n", queryCount, pqueryName )); // // set flags // - passed in flags // - unicode results // - flags for this particular suffix pBlob->Flags = flagsIn | pBlob->NameFlags; // // clear any previously received records (shouldn't be any) // if ( pBlob->pRecords ) { DNS_ASSERT( FALSE ); Dns_RecordListFree( pBlob->pRecords ); pBlob->pRecords = NULL; } // // do the query for name // includes // - cache or hostfile lookup // - wire query // status = Query_SingleName( pBlob ); // // clean out records on "non-response" // // DCR: need to fix record return // - should keep records on any response (best response) // just make sure NO_RECORDS rcode is mapped // // the only time we keep them is FAZ // - ALLOW_EMPTY_AUTH flag set // - sending FQDN (or more precisely doing single query) // precords = pBlob->pRecords; if ( precords ) { if ( IsEmptyDnsResponse( precords ) ) { if ( (flagsIn & DNS_QUERY_ALLOW_EMPTY_AUTH_RESP) && ( (nameAttributes & DNS_NAME_IS_FQDN) || ((nameAttributes & DNS_NAME_MULTI_LABEL) && !g_AppendToMultiLabelName ) ) ) { // stop here as caller (probably FAZ code) // wants to get the authority records DNSDBG( QUERY, ( "Returning empty query response with authority records.\n" )); break; } else { Dns_RecordListFree( precords ); pBlob->pRecords = NULL; if ( status == NO_ERROR ) { status = DNS_INFO_NO_RECORDS; } } } } // successful query -- done if ( status == ERROR_SUCCESS ) { RTL_ASSERT( precords ); break; } #if 0 // // DCR_FIX0: lost adapter timeout from early in multi-name query // - callback here or some other approach // // this is resolver version // // reset server priorities on failures // do here to avoid washing out info in retry with new name // if ( status != ERROR_SUCCESS && (pnetInfo->ReturnFlags & DNS_FLAG_RESET_SERVER_PRIORITY) ) { if ( g_AdapterTimeoutCacheTime && Dns_DisableTimedOutAdapters( pnetInfo ) ) { fadapterTimedOut = TRUE; SetKnownTimedOutAdapter(); } } // // DCR_CLEANUP: lost intermediate timed out adapter deal // if ( status != NO_ERROR && (pnetInfo->ReturnFlags & DNS_FLAG_RESET_SERVER_PRIORITY) ) { Dns_DisableTimedOutAdapters( pnetInfo ); } #endif // // save first query error (for some errors) // if ( queryCount == 1 && ( status == DNS_ERROR_RCODE_NAME_ERROR || status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_INVALID_NAME || status == DNS_ERROR_RCODE_SERVER_FAILURE || status == DNS_ERROR_RCODE_FORMAT_ERROR ) ) { DNSDBG( QUERY, ( "Saving bestQueryStatus %d\n", status )); bestQueryStatus = status; } // // continue with other queries on some errors // // on NAME_ERROR or NO_RECORDS response // - check if this negative result will be // cacheable, if it holds up // // note: the reason we check every time is that when the // query involves several names, one or more may fail // with one network timing out, YET the final name // queried indeed is a NAME_ERROR everywhere; hence // we can not do the check just once on the final // negative response; // in short, every negative response must be determinative // in order for us to cache // if ( status == DNS_ERROR_RCODE_NAME_ERROR || status == DNS_INFO_NO_RECORDS ) { if ( fcacheNegative ) { fcacheNegative = IsCacheableNameError( pnetInfo ); } if ( status == DNS_INFO_NO_RECORDS ) { DNSDBG( QUERY, ( "Saving bestQueryStatus %d\n", status )); bestQueryStatus = status; } continue; } // server failure may indicate intermediate or remote // server timeout and hence also makes any final // name error determination uncacheable else if ( status == DNS_ERROR_RCODE_SERVER_FAILURE ) { fcacheNegative = FALSE; continue; } // busted name errors // - just continue with next query else if ( status == DNS_ERROR_INVALID_NAME || status == DNS_ERROR_RCODE_FORMAT_ERROR ) { continue; } // // other errors -- ex. timeout and winsock -- are terminal // else { fcacheNegative = FALSE; break; } } DNSDBG( QUERY, ( "Query_Main() -- name loop termination\n" "\tstatus = %d\n" "\tquery count = %d\n", status, queryCount )); // // if no queries then invalid name // - either name itself is invalid // OR // - single part name and don't have anything to append // DNS_ASSERT( queryCount != 0 || status == DNS_ERROR_INVALID_NAME ); // // success -- prioritize record data // // to prioritize // - prioritize is set // - have more than one A record // - can get IP list // // note: need the callback because resolver uses directly // local copy of IP address info, whereas direct query // RPC's a copy over from the resolver // // alternative would be some sort of "set IP source" // function that resolver would call when there's a // new list; then could have common function that // picks up source if available or does RPC // if ( status == ERROR_SUCCESS ) { if ( g_PrioritizeRecordData && Dns_RecordListCount( precords, DNS_TYPE_A ) > 1 && pBlob->pfnGetAddrArray ) { PDNS_ADDR_ARRAY paddrArray = (pBlob->pfnGetAddrArray)(); if ( paddrArray ) { pBlob->pRecords = Dns_PrioritizeRecordSetEx( precords, paddrArray ); FREE_HEAP( paddrArray ); } } } // // no-op common negative response // doing this for perf to skip extensive status code check below // else if ( status == DNS_ERROR_RCODE_NAME_ERROR || status == DNS_INFO_NO_RECORDS ) { // no-op } // // timeout indicates possible network problem // winsock errors indicate definite network problem // else if ( status == ERROR_TIMEOUT || status == WSAEFAULT || status == WSAENOTSOCK || status == WSAENETDOWN || status == WSAENETUNREACH || status == WSAEPFNOSUPPORT || status == WSAEAFNOSUPPORT || status == WSAEHOSTDOWN || status == WSAEHOSTUNREACH ) { pBlob->NetFailureStatus = status; } #if 0 // // DCR: not sure when to free message buffer // // - it is reused in Dns_QueryLib call, so no leak // - point is when to return it // - old QuickQueryEx() would dump when going around again? // not sure of the point of that // // // going around again -- free up message buffer // if ( ppMsgResponse && *ppMsgResponse ) { FREE_HEAP( *ppMsgResponse ); *ppMsgResponse = NULL; } #endif // // use NO-IP local name? // // if matched local name but had no IPs (IP6 currently) // then use default here if not successful wire query // if ( pBlob->fNoIpLocal ) { if ( status != ERROR_SUCCESS ) { Dns_RecordListFree( pBlob->pRecords ); pBlob->pRecords = pBlob->pLocalRecords; status = ERROR_SUCCESS; pBlob->Status = status; } else { Dns_RecordListFree( pBlob->pLocalRecords ); pBlob->pLocalRecords = NULL; } } // // if error, use "best" error // this is either // - original query response // - or NO_RECORDS response found later // if ( status != ERROR_SUCCESS && bestQueryStatus ) { status = bestQueryStatus; pBlob->Status = status; } // // set negative response cacheability // pBlob->fCacheNegative = fcacheNegative; Done: DNS_ASSERT( !pBlob->pLocalRecords || pBlob->pLocalRecords == pBlob->pRecords ); DNSDBG( TRACE, ( "Leave Query_Main()\n" "\tstatus = %d\n" "\ttime = %d\n", status, Dns_GetCurrentTimeInSeconds() )); IF_DNSDBG( QUERY ) { DnsDbg_QueryBlob( "Blob leaving Query_Main()", pBlob ); } // // DCR_HACK: remove me // // must return some records on success query // // not sure this is true on referral -- if so it's because we flag // as referral // ASSERT( status != ERROR_SUCCESS || pBlob->pRecords != NULL ); return status; } DNS_STATUS Query_InProcess( IN OUT PQUERY_BLOB pBlob ) /*++ Routine Description: Main direct in-process query routine. Arguments: pBlob -- query info blob Return Value: ERROR_SUCCESS if successful. DNS RCODE error for RCODE response. DNS_INFO_NO_RECORDS for no records response. ERROR_TIMEOUT on complete lookup failure. ErrorCode on local failure. --*/ { DNS_STATUS status = NO_ERROR; PDNS_NETINFO pnetInfo; PDNS_NETINFO pnetInfoLocal = NULL; PDNS_NETINFO pnetInfoOriginal; DNS_STATUS statusNetFailure = NO_ERROR; DNSDBG( TRACE, ( "Query_InProcess( %p )\n", pBlob )); // // skip queries in "net down" situation // if ( IsKnownNetFailure() ) { status = GetLastError(); goto Cleanup; } // // get network info // pnetInfo = pnetInfoOriginal = pBlob->pNetworkInfo; // // explicit DNS server list -- build into network info // - requires info from current list for search list or PDN // - then dump current list and use private version // if ( pBlob->pDnsServers ) { pnetInfo = NetInfo_CreateFromIpArray( pBlob->pDnsServers, 0, // no specific server TRUE, // build search info pnetInfo // use existing netinfo ); if ( !pnetInfo ) { status = DNS_ERROR_NO_MEMORY; goto Cleanup; } pnetInfoLocal = pnetInfo; } // // no network info -- get it // else if ( !pnetInfo ) { pnetInfoLocal = pnetInfo = GetNetworkInfo(); if ( ! pnetInfo ) { status = DNS_ERROR_NO_DNS_SERVERS; goto Cleanup; } } pBlob->pNetworkInfo = pnetInfo; // // make actual query to DNS servers // pBlob->pfnQueryCache = QueryHostFile; pBlob->pfnGetAddrArray = DnsGetLocalAddrArray; status = Query_Main( pBlob ); // // save net failure // - but not if passed in network info // only meaningful if its standard info // // DCR: fix statusNetFailure mess // if ( statusNetFailure ) { if ( !pBlob->pDnsServers ) { SetKnownNetFailure( status ); } } // // cleanup // Cleanup: NetInfo_Free( pnetInfoLocal ); pBlob->pNetworkInfo = pnetInfoOriginal; GUI_MODE_SETUP_WS_CLEANUP( g_InNTSetupMode ); return status; } // // Query utilities // DNS_STATUS GetDnsServerRRSet( OUT PDNS_RECORD * ppRecord, IN BOOLEAN fUnicode ) /*++ Routine Description: Create record list of None. Arguments: None. Return Value: None. --*/ { PDNS_NETINFO pnetInfo; DWORD iter; PDNS_RECORD prr; DNS_RRSET rrSet; DNS_CHARSET charSet = fUnicode ? DnsCharSetUnicode : DnsCharSetUtf8; DNSDBG( QUERY, ( "GetDnsServerRRSet()\n" )); DNS_RRSET_INIT( rrSet ); pnetInfo = GetNetworkInfo(); if ( !pnetInfo ) { goto Done; } // // loop through all adapters build record for each DNS server // for ( iter = 0; iter < pnetInfo->AdapterCount; iter++ ) { PDNS_ADAPTER padapter = pnetInfo->AdapterArray[iter]; PSTR pname; DWORD jiter; if ( !padapter ) { continue; } // DCR: goofy way to expose aliases // // if register the adapter's domain name, make it record name // this pname = padapter->pszAdapterDomain; if ( !pname || !( padapter->InfoFlags & DNS_FLAG_REGISTER_DOMAIN_NAME ) ) { pname = "."; } for ( jiter = 0; jiter < padapter->ServerCount; jiter++ ) { // DCR: IP6 DNS servers IP_UNION ipUnion; IPUNION_SET_IP4( &ipUnion, padapter->ServerArray[jiter].IpAddress ); prr = Dns_CreateForwardRecord( pname, & ipUnion, 0, // no TTL DnsCharSetUtf8, // name is UTF8 charSet // result set ); if ( prr ) { prr->Flags.S.Section = DNSREC_ANSWER; DNS_RRSET_ADD( rrSet, prr ); } } } Done: NetInfo_Free( pnetInfo ); *ppRecord = prr = rrSet.pFirstRR; DNSDBG( QUERY, ( "Leave GetDnsServerRRSet() => %d\n", (prr ? ERROR_SUCCESS : DNS_ERROR_NO_DNS_SERVERS) )); return (prr ? ERROR_SUCCESS : DNS_ERROR_NO_DNS_SERVERS); } // // DNS Query API // DNS_STATUS WINAPI privateNarrowToWideQuery( IN PCSTR pszName, IN WORD wType, IN DWORD Options, IN PIP_ARRAY pDnsServers OPTIONAL, OUT PDNS_RECORD * ppResultSet OPTIONAL, IN OUT PDNS_MSG_BUF * ppMessageResponse OPTIONAL, IN DNS_CHARSET CharSet ) /*++ Routine Description: Convert narrow to wide query. This routine simple avoids duplicate code in ANSI and UTF8 query routines. Arguments: pszName -- name to query wType -- type of query Options -- flags to query pDnsServers -- array of DNS servers to use in query ppResultSet -- addr to receive result DNS records ppMessageResponse -- addr to receive response message CharSet -- char set of original query Return Value: ERROR_SUCCESS on success. DNS RCODE error on query with RCODE DNS_INFO_NO_RECORDS on no records response. ErrorCode on failure. --*/ { DNS_STATUS status = NO_ERROR; PDNS_RECORD prrList = NULL; PWSTR pwideName = NULL; WORD nameLength; if ( !pszName ) { return ERROR_INVALID_PARAMETER; } nameLength = (WORD) strlen( pszName ); pwideName = ALLOCATE_HEAP( (nameLength + 1) * sizeof(WCHAR) ); if ( !pwideName ) { return DNS_ERROR_NO_MEMORY; } if ( !Dns_NameCopy( (PSTR) pwideName, NULL, (PSTR) pszName, nameLength, CharSet, DnsCharSetUnicode ) ) { status = ERROR_INVALID_NAME; goto Done; } status = DnsQuery_W( pwideName, wType, Options, pDnsServers, ppResultSet ? &prrList : NULL, ppMessageResponse ); // // convert result records back to ANSI (or UTF8) // if ( ppResultSet && prrList ) { *ppResultSet = Dns_RecordSetCopyEx( prrList, DnsCharSetUnicode, CharSet ); if ( ! *ppResultSet ) { status = DNS_ERROR_NO_MEMORY; } Dns_RecordListFree( prrList ); } // // cleanup // Done: FREE_HEAP( pwideName ); return status; } DNS_STATUS WINAPI DnsQuery_UTF8( IN PCSTR pszName, IN WORD wType, IN DWORD Options, IN PIP_ARRAY pDnsServers OPTIONAL, OUT PDNS_RECORD * ppResultSet OPTIONAL, IN OUT PDNS_MSG_BUF * ppMessageResponse OPTIONAL ) /*++ Routine Description: Public UTF8 query. Arguments: pszName -- name to query wType -- type of query Options -- flags to query pDnsServers -- array of DNS servers to use in query ppResultSet -- addr to receive result DNS records ppMessageResponse -- addr to receive response message Return Value: ERROR_SUCCESS on success. DNS RCODE error on query with RCODE DNS_INFO_NO_RECORDS on no records response. ErrorCode on failure. --*/ { return privateNarrowToWideQuery( pszName, wType, Options, pDnsServers, ppResultSet, ppMessageResponse, DnsCharSetUtf8 ); } DNS_STATUS WINAPI DnsQuery_A( IN PCSTR pszName, IN WORD wType, IN DWORD Options, IN PIP_ARRAY pDnsServers OPTIONAL, OUT PDNS_RECORD * ppResultSet OPTIONAL, IN OUT PDNS_MSG_BUF * ppMessageResponse OPTIONAL ) /*++ Routine Description: Public ANSI query. Arguments: pszName -- name to query wType -- type of query Options -- flags to query pDnsServers -- array of DNS servers to use in query ppResultSet -- addr to receive result DNS records ppMessageResponse -- addr to receive resulting message Return Value: ERROR_SUCCESS on success. DNS RCODE error on query with RCODE DNS_INFO_NO_RECORDS on no records response. ErrorCode on failure. --*/ { return privateNarrowToWideQuery( pszName, wType, Options, pDnsServers, ppResultSet, ppMessageResponse, DnsCharSetAnsi ); } DNS_STATUS WINAPI DnsQuery_W( IN PCWSTR pwsName, IN WORD wType, IN DWORD Options, IN PIP_ARRAY pDnsServers OPTIONAL, IN OUT PDNS_RECORD * ppResultSet OPTIONAL, IN OUT PDNS_MSG_BUF * ppMessageResponse OPTIONAL ) /*++ Routine Description: Public unicode query API Note, this unicode version is the main routine. The other public API call back through it. Arguments: pszName -- name to query wType -- type of query Options -- flags to query pDnsServers -- array of DNS servers to use in query ppResultSet -- addr to receive result DNS records ppMessageResponse -- addr to receive resulting message Return Value: ERROR_SUCCESS on success. DNS RCODE error on query with RCODE DNS_INFO_NO_RECORDS on no records response. ErrorCode on failure. --*/ { DNS_STATUS status = NO_ERROR; PDNS_NETINFO pnetInfo = NULL; PDNS_RECORD prpcRecord = NULL; DWORD rpcStatus = NO_ERROR; PQUERY_BLOB pblob; PWSTR pnameLocal = NULL; DNSDBG( TRACE, ( "\n\nDnsQuery_W()\n" "\tName = %S\n" "\twType = %d\n" "\tOptions = %08x\n" "\tpDnsServers = %p\n" "\tppMessage = %p\n", pwsName, wType, Options, pDnsServers, ppMessageResponse )); // // must ask for some kind of results // if ( !ppResultSet && !ppMessageResponse ) { return ERROR_INVALID_PARAMETER; } // // NULL name indicates localhost lookup // // DCR: NULL name lookup for localhost could be improved // - support NULL all the way through to wire // - have local IP routines just accept it // if ( !pwsName ) { pnameLocal = (PWSTR) Reg_GetHostName( DnsCharSetUnicode ); if ( !pnameLocal ) { return DNS_ERROR_NAME_NOT_FOUND_LOCALLY; } pwsName = (PCWSTR) pnameLocal; Options |= DNS_QUERY_CACHE_ONLY; } // clear OUT params if ( ppResultSet ) { *ppResultSet = NULL; } if ( ppMessageResponse ) { *ppMessageResponse = NULL; } // // IP string queries // if ( ppResultSet ) { PDNS_RECORD prr; prr = Dns_CreateRecordForIpString_W( pwsName, wType, IPSTRING_RECORD_TTL ); if ( prr ) { *ppResultSet = prr; status = ERROR_SUCCESS; goto Done; } } // // empty type A query get DNS servers // // DCR_CLEANUP: DnsQuery empty name query for DNS servers? // need better\safer approach to this // is this SDK doc'd // if ( ppResultSet && !ppMessageResponse && wType == DNS_TYPE_A && ( !wcscmp( pwsName, L"" ) || !wcscmp( pwsName, DNS_SERVER_QUERY_NAME ) ) ) { status = GetDnsServerRRSet( ppResultSet, TRUE // unicode ); goto Done; } // // BYPASS_CACHE // - required if want message buffer or specify server // list -- just set flag // - incompatible with CACHE_ONLY // - required to get EMPTY_AUTH_RESPONSE // if ( ppMessageResponse || pDnsServers || (Options & DNS_QUERY_ALLOW_EMPTY_AUTH_RESP) ) { Options |= DNS_QUERY_BYPASS_CACHE; //Options |= DNS_QUERY_NO_CACHE_DATA; } // // do direct query? // - not RPC-able type // - want message buffer // - specifying DNS servers // - want EMPTY_AUTH response records // // DCR: currently by-passing for type==ALL // this may be too common to do that; may want to // go to cache then determine if security records // or other stuff require us to query in process // // DCR: not clear what the EMPTY_AUTH benefit is // // DCR: currently BYPASSing whenever BYPASS is set // because otherwise we miss the hosts file // if fix so lookup in cache, but screen off non-hosts // data, then could resume going to cache // if ( !Dns_IsRpcRecordType(wType) && !(Options & DNS_QUERY_CACHE_ONLY) ) { goto InProcessQuery; } if ( Options & DNS_QUERY_BYPASS_CACHE ) #if 0 if ( (Options & DNS_QUERY_BYPASS_CACHE) && ( ppMessageResponse || pDnsServers || (Options & DNS_QUERY_ALLOW_EMPTY_AUTH_RESP) ) ) #endif { if ( Options & DNS_QUERY_CACHE_ONLY ) { status = ERROR_INVALID_PARAMETER; goto Done; } goto InProcessQuery; } // // querying through cache // - get cluster-filtering info // if ( g_IsServer ) { ENVAR_DWORD_INFO filterInfo; Reg_ReadDwordEnvar( RegIdFilterClusterIp, &filterInfo ); if ( filterInfo.fFound && filterInfo.Value ) { Options |= DNSP_QUERY_FILTER_CLUSTER; } } rpcStatus = NO_ERROR; RpcTryExcept { status = R_ResolverQuery( NULL, (PWSTR) pwsName, wType, Options, &prpcRecord ); } RpcExcept( DNS_RPC_EXCEPTION_FILTER ) { rpcStatus = RpcExceptionCode(); } RpcEndExcept // // cache unavailable // - bail if just querying cache // - otherwise query direct if ( rpcStatus != NO_ERROR ) { DNSDBG( TRACE, ( "DnsQuery_W() RPC failed status = %d\n", rpcStatus )); goto InProcessQuery; } if ( status == DNS_ERROR_NO_TCPIP ) { DNSDBG( TRACE, ( "DnsQuery_W() NO_TCPIP error!\n" "\tassume resolver security problem -- query in process!\n" )); RTL_ASSERT( !prpcRecord ); goto InProcessQuery; } // // return records // - screen out empty-auth responses // // DCR_FIX1: cache should convert and return NO_RECORDS response // directly (no need to do this here) // // DCR: UNLESS we allow return of these records // if ( prpcRecord ) { FixupNameOwnerPointers( prpcRecord ); if ( IsEmptyDnsResponseFromResolver( prpcRecord ) ) { Dns_RecordListFree( prpcRecord ); prpcRecord = NULL; if ( status == NO_ERROR ) { status = DNS_INFO_NO_RECORDS; } } *ppResultSet = prpcRecord; } RTL_ASSERT( status!=NO_ERROR || prpcRecord ); goto Done; // // query directly -- either skipping cache or it's unavailable // InProcessQuery: DNSDBG( TRACE, ( "DnsQuery_W() -- doing in process query\n" "\tpname = %S\n" "\ttype = %d\n", pwsName, wType )); // // load query blob // // DCR: set some sort of "want message buffer" flag if ppMessageResponse // exists // pblob = ALLOCATE_HEAP_ZERO( sizeof(*pblob) ); if ( !pblob ) { status = DNS_ERROR_NO_MEMORY; goto Done; } pblob->pNameOrig = (PWSTR) pwsName; pblob->wType = wType; pblob->Flags = Options | DNSQUERY_UNICODE_OUT; pblob->pDnsServers = pDnsServers; // // query // - then set OUT params status = Query_InProcess( pblob ); if ( ppResultSet ) { *ppResultSet = pblob->pRecords; RTL_ASSERT( status!=NO_ERROR || *ppResultSet ); } else { Dns_RecordListFree( pblob->pRecords ); } if ( ppMessageResponse ) { *ppMessageResponse = pblob->pRecvMsg; } FREE_HEAP( pblob ); Done: // sanity check if ( status==NO_ERROR && ppResultSet && !*ppResultSet ) { RTL_ASSERT( FALSE ); status = DNS_INFO_NO_RECORDS; } if ( pnameLocal ) { FREE_HEAP( pnameLocal ); } DNSDBG( TRACE, ( "Leave DnsQuery_W()\n" "\tstatus = %d\n" "\tresult set = %p\n\n\n", status, *ppResultSet )); return( status ); } // // DnsQueryEx() routines // DNS_STATUS WINAPI ShimDnsQueryEx( IN OUT PDNS_QUERY_INFO pQueryInfo ) /*++ Routine Description: Query DNS -- shim for main SDK query routine. Arguments: pQueryInfo -- blob describing query Return Value: ERROR_SUCCESS if successful query. Error code on failure. --*/ { PDNS_RECORD prrResult; WORD type = pQueryInfo->Type; DNS_STATUS status; DNS_LIST listAnswer; DNS_LIST listAlias; DNS_LIST listAdditional; DNS_LIST listAuthority; DNSDBG( TRACE, ( "ShimDnsQueryEx()\n" )); // // DCR: temp hack is to pass this to DNSQuery // status = DnsQuery_W( (PWSTR) pQueryInfo->pName, type, pQueryInfo->Flags, pQueryInfo->pDnsServers, & prrResult, NULL ); pQueryInfo->Status = status; // // cut result records appropriately // pQueryInfo->pAnswerRecords = NULL; pQueryInfo->pAliasRecords = NULL; pQueryInfo->pAdditionalRecords = NULL; pQueryInfo->pAuthorityRecords = NULL; if ( prrResult ) { PDNS_RECORD prr; PDNS_RECORD pnextRR; DNS_LIST_STRUCT_INIT( listAnswer ); DNS_LIST_STRUCT_INIT( listAlias ); DNS_LIST_STRUCT_INIT( listAdditional ); DNS_LIST_STRUCT_INIT( listAuthority ); // // break list into section specific lists // - section 0 for hostfile records // - note, this does pull RR sets apart, but // they, being in same section, should immediately // be rejoined // pnextRR = prrResult; while ( prr = pnextRR ) { pnextRR = prr->pNext; prr->pNext = NULL; if ( prr->Flags.S.Section == 0 || prr->Flags.S.Section == DNSREC_ANSWER ) { if ( prr->wType == DNS_TYPE_CNAME && type != DNS_TYPE_CNAME ) { DNS_LIST_STRUCT_ADD( listAlias, prr ); continue; } else { DNS_LIST_STRUCT_ADD( listAnswer, prr ); continue; } } else if ( prr->Flags.S.Section == DNSREC_ADDITIONAL ) { DNS_LIST_STRUCT_ADD( listAdditional, prr ); continue; } else { DNS_LIST_STRUCT_ADD( listAuthority, prr ); continue; } } // pack stuff back into blob pQueryInfo->pAnswerRecords = listAnswer.pFirst; pQueryInfo->pAliasRecords = listAlias.pFirst; pQueryInfo->pAuthorityRecords = listAuthority.pFirst; pQueryInfo->pAdditionalRecords = listAdditional.pFirst; //pQueryInfo->pSigRecords = listSig.pFirst; // // convert result records back to ANSI (or UTF8) // - convert each result set // - then paste back into query blob // // DCR_FIX0: handle issue of failure on conversion // if ( pQueryInfo->CharSet != DnsCharSetUnicode ) { PDNS_RECORD prr; PDNS_RECORD * prrSetPtr; prrSetPtr = & pQueryInfo->pAnswerRecords; for ( prrSetPtr = & pQueryInfo->pAnswerRecords; prrSetPtr <= & pQueryInfo->pAdditionalRecords; prrSetPtr++ ) { prr = *prrSetPtr; *prrSetPtr = Dns_RecordSetCopyEx( prr, DnsCharSetUnicode, pQueryInfo->CharSet ); Dns_RecordListFree( prr ); } } } // // replace name for originally narrow queries // if ( pQueryInfo->CharSet != DnsCharSetUnicode ) { ASSERT( pQueryInfo->CharSet != 0 ); ASSERT( pQueryInfo->pReservedName != NULL ); FREE_HEAP( pQueryInfo->pName ); pQueryInfo->pName = (LPTSTR) pQueryInfo->pReservedName; pQueryInfo->pReservedName = NULL; } // // indicate return if async // if ( pQueryInfo->hEvent ) { SetEvent( pQueryInfo->hEvent ); } return( status ); } DNS_STATUS WINAPI CombinedQueryEx( IN OUT PDNS_QUERY_INFO pQueryInfo, IN DNS_CHARSET CharSet ) /*++ Routine Description: Convert narrow to wide query. This routine simple avoids duplicate code in ANSI and UTF8 query routines. Arguments: pQueryInfo -- query info blob CharSet -- char set of original query Return Value: ERROR_SUCCESS on success. DNS RCODE error on query with RCODE DNS_INFO_NO_RECORDS on no records response. ErrorCode on failure. --*/ { DNS_STATUS status = NO_ERROR; PWSTR pwideName = NULL; HANDLE hthread; DWORD threadId; DNSDBG( TRACE, ( "CombinedQueryEx( %S%s, type=%d, flag=%08x, event=%p )\n", PRINT_STRING_WIDE_CHARSET( pQueryInfo->pName, CharSet ), PRINT_STRING_ANSI_CHARSET( pQueryInfo->pName, CharSet ), pQueryInfo->Type, pQueryInfo->Flags, pQueryInfo->hEvent )); // // set CharSet // pQueryInfo->CharSet = CharSet; if ( CharSet == DnsCharSetUnicode ) { pQueryInfo->pReservedName = 0; } // // if narrow name // - allocate wide name copy // - swap in wide name and make query wide // // DCR: allow NULL name? for local machine name? // else if ( CharSet == DnsCharSetAnsi || CharSet == DnsCharSetUtf8 ) { WORD nameLength; PSTR pnameNarrow; pnameNarrow = pQueryInfo->pName; if ( !pnameNarrow ) { return ERROR_INVALID_PARAMETER; } nameLength = (WORD) strlen( pnameNarrow ); pwideName = ALLOCATE_HEAP( (nameLength + 1) * sizeof(WCHAR) ); if ( !pwideName ) { return DNS_ERROR_NO_MEMORY; } if ( !Dns_NameCopy( (PSTR) pwideName, NULL, pnameNarrow, nameLength, CharSet, DnsCharSetUnicode ) ) { status = ERROR_INVALID_NAME; goto Failed; } pQueryInfo->pName = (LPTSTR) pwideName; pQueryInfo->pReservedName = pnameNarrow; } // // async? // - if event exists we are async // - spin up thread and call it // if ( pQueryInfo->hEvent ) { hthread = CreateThread( NULL, // no security 0, // default stack ShimDnsQueryEx, pQueryInfo, // param 0, // run immediately & threadId ); if ( !hthread ) { DNS_STATUS status = GetLastError(); DNSDBG( ANY, ( "Failed to create thread = %d\n", status )); if ( status == ERROR_SUCCESS ) { status = DNS_ERROR_NO_MEMORY; } goto Failed; } CloseHandle( hthread ); return( ERROR_IO_PENDING ); } // // otherwise make direct async call // return ShimDnsQueryEx( pQueryInfo ); Failed: FREE_HEAP( pwideName ); return( status ); } DNS_STATUS WINAPI DnsQueryExW( IN OUT PDNS_QUERY_INFO pQueryInfo ) /*++ Routine Description: Query DNS -- main SDK query routine. Arguments: pQueryInfo -- blob describing query Return Value: ERROR_SUCCESS if successful query. ERROR_IO_PENDING if successful async start. Error code on failure. --*/ { DNSDBG( TRACE, ( "DnsQueryExW( %S, type=%d, flag=%08x, event=%p )\n", pQueryInfo->pName, pQueryInfo->Type, pQueryInfo->Flags, pQueryInfo->hEvent )); return CombinedQueryEx( pQueryInfo, DnsCharSetUnicode ); } DNS_STATUS WINAPI DnsQueryExA( IN OUT PDNS_QUERY_INFO pQueryInfo ) /*++ Routine Description: Query DNS -- main SDK query routine. Arguments: pQueryInfo -- blob describing query Return Value: ERROR_SUCCESS if successful query. ERROR_IO_PENDING if successful async start. Error code on failure. --*/ { DNSDBG( TRACE, ( "DnsQueryExA( %s, type=%d, flag=%08x, event=%p )\n", pQueryInfo->pName, pQueryInfo->Type, pQueryInfo->Flags, pQueryInfo->hEvent )); return CombinedQueryEx( pQueryInfo, DnsCharSetAnsi ); } DNS_STATUS WINAPI DnsQueryExUTF8( IN OUT PDNS_QUERY_INFO pQueryInfo ) /*++ Routine Description: Query DNS -- main SDK query routine. Arguments: pQueryInfo -- blob describing query Return Value: ERROR_SUCCESS if successful query. ERROR_IO_PENDING if successful async start. Error code on failure. --*/ { DNSDBG( TRACE, ( "DnsQueryExUTF8( %s, type=%d, flag=%08x, event=%p )\n", pQueryInfo->pName, pQueryInfo->Type, pQueryInfo->Flags, pQueryInfo->hEvent )); return CombinedQueryEx( pQueryInfo, DnsCharSetUtf8 ); } // // Name collision API // // DCR_QUESTION: name collision -- is there any point to this? // DNS_STATUS WINAPI DnsCheckNameCollision_UTF8( IN PCSTR pszName, IN DWORD Options ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { DNS_STATUS status = NO_ERROR; PDNS_RECORD prrList = NULL; PDNS_RECORD prr = NULL; DWORD iter; BOOL fmatch = FALSE; WORD wtype = DNS_TYPE_A; if ( !pszName ) { return ERROR_INVALID_PARAMETER; } if ( Options & DNS_CHECK_AGAINST_HOST_ANY ) { wtype = DNS_TYPE_ANY; } status = DnsQuery_UTF8( pszName, wtype, DNS_QUERY_BYPASS_CACHE, NULL, &prrList, NULL ); if ( status == DNS_ERROR_RCODE_NAME_ERROR || status == DNS_INFO_NO_RECORDS ) { Dns_RecordListFree( prrList ); return NO_ERROR; } if ( status == NO_ERROR && Options == DNS_CHECK_AGAINST_HOST_ANY ) { Dns_RecordListFree( prrList ); return DNS_ERROR_RCODE_YXRRSET; } if ( status == NO_ERROR && Options == DNS_CHECK_AGAINST_HOST_DOMAIN_NAME ) { char TestName[DNS_MAX_NAME_LENGTH * 2]; PSTR pszHostName = Reg_GetHostName( DnsCharSetUtf8 ); PSTR pszPrimaryDomain = Reg_GetPrimaryDomainName( DnsCharSetUtf8 ); fmatch = TRUE; strcpy( TestName, pszHostName ); if ( pszPrimaryDomain ) { strcat( TestName, "." ); strcat( TestName, pszPrimaryDomain ); } if ( Dns_NameCompare_UTF8( pszHostName, (PSTR)pszName ) ) { fmatch = TRUE; } if ( !fmatch && pszPrimaryDomain && Dns_NameCompare_UTF8( TestName, (PSTR)pszName ) ) { fmatch = TRUE; } if ( !fmatch ) { PDNS_NETINFO pNetInfo = GetNetworkInfo(); if ( pNetInfo ) { for ( iter = 0; iter < pNetInfo->AdapterCount; iter++ ) { PSTR pszDomain = pNetInfo->AdapterArray[iter]-> pszAdapterDomain; if ( pszDomain ) { strcpy( TestName, pszHostName ); strcat( TestName, "." ); strcat( TestName, pszDomain ); if ( Dns_NameCompare_UTF8( TestName, (PSTR)pszName ) ) fmatch = TRUE; } } } NetInfo_Free( pNetInfo ); } FREE_HEAP( pszHostName ); FREE_HEAP( pszPrimaryDomain ); if ( fmatch ) { Dns_RecordListFree( prrList ); return DNS_ERROR_RCODE_YXRRSET; } } if ( status == NO_ERROR ) { PDNS_ADDRESS_INFO pAddressInfo = NULL; DWORD Count = DnsGetIpAddressInfoList( &pAddressInfo ); if ( Count == 0 ) { Dns_RecordListFree( prrList ); return DNS_ERROR_RCODE_YXRRSET; } prr = prrList; while ( prr ) { fmatch = FALSE; if ( prr->Flags.S.Section != DNSREC_ANSWER ) { prr = prr->pNext; continue; } if ( prr->wType == DNS_TYPE_CNAME ) { FREE_HEAP( pAddressInfo ); Dns_RecordListFree( prrList ); return DNS_ERROR_RCODE_YXRRSET; } for ( iter = 0; iter < Count; iter++ ) { if ( prr->Data.A.IpAddress == pAddressInfo[iter].ipAddress ) { fmatch = TRUE; } } if ( !fmatch ) { FREE_HEAP( pAddressInfo ); Dns_RecordListFree( prrList ); return DNS_ERROR_RCODE_YXRRSET; } prr = prr->pNext; } FREE_HEAP( pAddressInfo ); Dns_RecordListFree( prrList ); return NO_ERROR; } return status; } DNS_STATUS WINAPI DnsCheckNameCollision_A( IN PCSTR pszName, IN DWORD Options ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { PSTR pUtf8Name = NULL; DNS_STATUS status = NO_ERROR; // // DCR_CLEANUP: fix unnecessary alloc // DCR_PERF: eliminate alloc // if ( !pszName ) { return ERROR_INVALID_PARAMETER; } pUtf8Name = Dns_NameCopyAllocate( (PSTR) pszName, 0, DnsCharSetAnsi, DnsCharSetUtf8 ); if ( !pUtf8Name ) { return DNS_ERROR_NO_MEMORY; } status = DnsCheckNameCollision_UTF8( pUtf8Name, Options ); FREE_HEAP( pUtf8Name ); return status; } DNS_STATUS WINAPI DnsCheckNameCollision_W( IN PCWSTR pszName, IN DWORD Options ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { // // DCR_CLEANUP: fix unnecessary alloc // DCR_PERF: eliminate alloc // DNS_STATUS status = NO_ERROR; PSTR lpTempName = NULL; WORD nameLength; if ( !pszName ) { return ERROR_INVALID_PARAMETER; } nameLength = (WORD)wcslen( pszName ); lpTempName = ALLOCATE_HEAP( (nameLength + 1) * sizeof(WCHAR) ); if ( lpTempName == NULL ) { return DNS_ERROR_NO_MEMORY; } Dns_NameCopy( lpTempName, NULL, (PSTR) pszName, 0, DnsCharSetUnicode, DnsCharSetUtf8 ); status = DnsCheckNameCollision_UTF8( lpTempName, Options ); FREE_HEAP( lpTempName ); return status; } // // Roll your own query utilities // BOOL WINAPI DnsWriteQuestionToBuffer_W( IN OUT PDNS_MESSAGE_BUFFER pDnsBuffer, IN OUT LPDWORD pdwBufferSize, IN PWSTR pszName, IN WORD wType, IN WORD Xid, IN BOOL fRecursionDesired ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { // // DCR_CLEANUP: duplicate code with routine below ... surprise! // - eliminate duplicate // - probably can just pick up library routine // PCHAR pch; PCHAR pbufferEnd = NULL; if ( *pdwBufferSize >= DNS_MAX_UDP_PACKET_BUFFER_LENGTH ) { pbufferEnd = (PCHAR)pDnsBuffer + *pdwBufferSize; // clear header RtlZeroMemory( pDnsBuffer, sizeof(DNS_HEADER) ); // set for rewriting pch = pDnsBuffer->MessageBody; // write question name pch = Dns_WriteDottedNameToPacket( pch, pbufferEnd, (PCHAR) pszName, NULL, 0, TRUE ); if ( !pch ) { return FALSE; } // write question structure *(UNALIGNED WORD *) pch = htons( wType ); pch += sizeof(WORD); *(UNALIGNED WORD *) pch = DNS_RCLASS_INTERNET; pch += sizeof(WORD); // set question RR section count pDnsBuffer->MessageHead.QuestionCount = htons( 1 ); pDnsBuffer->MessageHead.RecursionDesired = (BOOLEAN)fRecursionDesired; pDnsBuffer->MessageHead.Xid = htons( Xid ); *pdwBufferSize = (DWORD)(pch - (PCHAR)pDnsBuffer); return TRUE; } else { *pdwBufferSize = DNS_MAX_UDP_PACKET_BUFFER_LENGTH; return FALSE; } } BOOL WINAPI DnsWriteQuestionToBuffer_UTF8( IN OUT PDNS_MESSAGE_BUFFER pDnsBuffer, IN OUT PDWORD pdwBufferSize, IN PSTR pszName, IN WORD wType, IN WORD Xid, IN BOOL fRecursionDesired ) /*++ Routine Description: None. Arguments: None. Return Value: None. --*/ { PCHAR pch; PCHAR pbufferEnd = NULL; if ( *pdwBufferSize >= DNS_MAX_UDP_PACKET_BUFFER_LENGTH ) { pbufferEnd = (PCHAR)pDnsBuffer + *pdwBufferSize; // clear header RtlZeroMemory( pDnsBuffer, sizeof(DNS_HEADER) ); // set for rewriting pch = pDnsBuffer->MessageBody; // write question name pch = Dns_WriteDottedNameToPacket( pch, pbufferEnd, pszName, NULL, 0, FALSE ); if ( !pch ) { return FALSE; } // write question structure *(UNALIGNED WORD *) pch = htons( wType ); pch += sizeof(WORD); *(UNALIGNED WORD *) pch = DNS_RCLASS_INTERNET; pch += sizeof(WORD); // set question RR section count pDnsBuffer->MessageHead.QuestionCount = htons( 1 ); pDnsBuffer->MessageHead.RecursionDesired = (BOOLEAN)fRecursionDesired; pDnsBuffer->MessageHead.Xid = htons( Xid ); *pdwBufferSize = (DWORD)(pch - (PCHAR)pDnsBuffer); return TRUE; } else { *pdwBufferSize = DNS_MAX_UDP_PACKET_BUFFER_LENGTH; return FALSE; } } // // Record list to\from results // VOID CombineRecordsInBlob( IN PDNS_RESULTS pResults, OUT PDNS_RECORD * ppRecords ) /*++ Routine Description: Query DNS -- shim for main SDK query routine. Arguments: pQueryInfo -- blob describing query Return Value: ERROR_SUCCESS if successful query. Error code on failure. --*/ { PDNS_RECORD prr; DNSDBG( TRACE, ( "CombineRecordsInBlob()\n" )); // // combine records back into one list // // note, working backwards so only touch records once // prr = Dns_RecordListAppend( pResults->pAuthorityRecords, pResults->pAdditionalRecords ); prr = Dns_RecordListAppend( pResults->pAnswerRecords, prr ); prr = Dns_RecordListAppend( pResults->pAliasRecords, prr ); *ppRecords = prr; } VOID BreakRecordsIntoBlob( OUT PDNS_RESULTS pResults, IN PDNS_RECORD pRecords, IN WORD wType ) /*++ Routine Description: Break single record list into results pblob-> Arguments: pResults -- results to fill in pRecords -- record list Return Value: None --*/ { PDNS_RECORD prr; PDNS_RECORD pnextRR; DNS_LIST listAnswer; DNS_LIST listAlias; DNS_LIST listAdditional; DNS_LIST listAuthority; DNSDBG( TRACE, ( "BreakRecordsIntoBlob()\n" )); // // clear blob // RtlZeroMemory( pResults, sizeof(*pResults) ); // // init building lists // DNS_LIST_STRUCT_INIT( listAnswer ); DNS_LIST_STRUCT_INIT( listAlias ); DNS_LIST_STRUCT_INIT( listAdditional ); DNS_LIST_STRUCT_INIT( listAuthority ); // // break list into section specific lists // - note, this does pull RR sets apart, but // they, being in same section, should immediately // be rejoined // // - note, hostfile records made have section=0 // this is no longer the case but preserve until // know this is solid and determine what section==0 // means // pnextRR = pRecords; while ( prr = pnextRR ) { pnextRR = prr->pNext; prr->pNext = NULL; if ( prr->Flags.S.Section == 0 || prr->Flags.S.Section == DNSREC_ANSWER ) { if ( prr->wType == DNS_TYPE_CNAME && wType != DNS_TYPE_CNAME ) { DNS_LIST_STRUCT_ADD( listAlias, prr ); continue; } else { DNS_LIST_STRUCT_ADD( listAnswer, prr ); continue; } } else if ( prr->Flags.S.Section == DNSREC_ADDITIONAL ) { DNS_LIST_STRUCT_ADD( listAdditional, prr ); continue; } else { DNS_LIST_STRUCT_ADD( listAuthority, prr ); continue; } } // pack stuff into blob pResults->pAnswerRecords = listAnswer.pFirst; pResults->pAliasRecords = listAlias.pFirst; pResults->pAuthorityRecords = listAuthority.pFirst; pResults->pAdditionalRecords = listAdditional.pFirst; } // // Random discontinued // #if 0 IP_ADDRESS findHostIpInRecordList( IN PDNS_RECORD pRecord, IN PDNS_NAME pszHostName ) /*++ Routine Description: Find IP for hostname, if its A record is in list. Arguments: pRecord - incoming RR set pszHostName - hostname to find Return Value: IP address matching hostname, if A record for hostname found. Zero if not found. --*/ { // // DCR: find best A record for name // currently only finds first; this is harmless but in // disjoint net situation would need sorted to find best // while ( pRecord ) { if ( pRecord->wType == DNS_TYPE_A && Dns_NameCompare( pRecord->pName, pszHostName ) ) { return( pRecord->Data.A.IpAddress ); } pRecord = pRecord->pNext; } return( 0 ); } #endif // // End query.c //