/*++ Copyright (c) 1997-2001 Microsoft Corporation Module Name: remote.c Abstract: DNS Resolver Service. Remote APIs to resolver service. Author: Glenn Curtis (glennc) Feb 1997 Revision History: Jim Gilroy (jamesg) March 2000 cleanup --*/ #include "local.h" // // Local Definitions // typedef struct _POPUP_MSG_PARMS { LPWSTR Message; LPWSTR Title; } POPUP_MSG_PARMS, *PPOPUP_MSG_PARMS; // // Private protos // DNS_STATUS RslvrQueryToDnsServer( OUT PDNS_RECORD * ppRecord, IN PWSTR pwsName, IN WORD wType, IN DWORD Flags, OUT PBOOL pfCacheNegativeResponse ); BOOL IsKnownTimedOutAdapter( VOID ); VOID SetKnownTimedOutAdapter( VOID ); BOOL IsTimeToResetServerPriorities( VOID ); DWORD PopupMessageThread( IN PPOPUP_MSG_PARMS ); PDNS_RPC_CACHE_TABLE CreateCacheTableEntry( IN LPWSTR Name ); VOID FreeCacheTableEntryList( IN PDNS_RPC_CACHE_TABLE pCacheTableList ); BOOL IsEmptyDnsResponse( IN PDNS_RECORD ); // // Operations // DNS_STATUS CRrReadCache( IN DNS_RPC_HANDLE Reserved, OUT PDNS_RPC_CACHE_TABLE * ppCacheTable ) /*++ Routine Description: Arguments: Return Value: --*/ // CRrReadCache { DNS_STATUS status = ERROR_SUCCESS; PDNS_RPC_CACHE_TABLE pprevRpcEntry = NULL; DWORD iter; DWORD countEntries = 0; #define MAX_RPC_CACHE_ENTRY_COUNT (300) UNREFERENCED_PARAMETER(Reserved); DNSDBG( RPC, ( "CRrReadCache\n" )); if ( !ppCacheTable ) { return ERROR_INVALID_PARAMETER; } *ppCacheTable = NULL; DNSLOG_F1( "DNS Caching Resolver Service - CRrReadCache" ); if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "CRrReadCache - ERROR_ACCESS_DENIED" ); return ERROR_ACCESS_DENIED; } LOCK_CACHE(); DNSLOG_F2( " Current number of entries in cache : %d", g_EntryCount ); DNSLOG_F2( " Current number of RR sets in cache : %d", g_RecordSetCount ); // // Loop through all hash table slots looking for cache entries // to return. // for ( iter = 0; iter < g_HashTableSize; iter++ ) { PCACHE_ENTRY pentry = g_HashTable[iter]; DWORD iter2; while ( pentry && countEntries < MAX_RPC_CACHE_ENTRY_COUNT ) { PDNS_RPC_CACHE_TABLE prpcEntry; prpcEntry = CreateCacheTableEntry( pentry->pName ); if ( ! prpcEntry ) { // only failure is memory alloc FreeCacheTableEntryList( *ppCacheTable ); *ppCacheTable = NULL; status = ERROR_NOT_ENOUGH_MEMORY; goto ErrorExit; } // // insert new entry at end of current list // if ( pprevRpcEntry ) pprevRpcEntry->pNext = prpcEntry; else *ppCacheTable = prpcEntry; pprevRpcEntry = prpcEntry; countEntries++; // // fill in entry with current cached types // for ( iter2 = 0; iter2 < pentry->MaxCount; iter2++ ) { PDNS_RECORD prr = pentry->Records[iter2]; WORD type; if ( !prr ) { continue; } // DCR -- goofy, just make sure the same and index (or limit?) type = prr->wType; if ( ! prpcEntry->Type1 ) prpcEntry->Type1 = type; else if ( ! prpcEntry->Type2 ) prpcEntry->Type2 = type; else prpcEntry->Type3 = type; } pentry = pentry->pNext; } if ( countEntries > MAX_RPC_CACHE_ENTRY_COUNT ) { break; } } ErrorExit: UNLOCK_CACHE(); DNSLOG_F3( " CRrReadCache - Returning status : 0x%.8X\n\t%s", status, Dns_StatusString( status ) ); DNSLOG_F1( "" ); return status; } DNS_STATUS CRrReadCacheEntry( IN DNS_RPC_HANDLE Reserved, IN LPWSTR pwsName, IN WORD wType, OUT PDNS_RECORD * ppRRSet ) /*++ Routine Description: Arguments: Return Value: --*/ // CRrReadCacheEntry { DNS_STATUS status; PCACHE_ENTRY pentry; PDNS_RECORD prr; UNREFERENCED_PARAMETER(Reserved); DNSLOG_F1( "DNS Caching Resolver Service - CRrReadCacheEntry" ); DNSLOG_F1( " Arguments:" ); DNSLOG_F2( " Name : %S", pwsName ); DNSLOG_F2( " Type : %d", wType ); DNSLOG_F1( "" ); DNSDBG( RPC, ( "\nCRrReadCacheEntry( %S, %d )\n", pwsName, wType )); if ( !ppRRSet ) return ERROR_INVALID_PARAMETER; if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "CRrReadCacheEntry - ERROR_ACCESS_DENIED" ); return ERROR_ACCESS_DENIED; } // // find record in cache // - copy if not NAME_ERROR or EMPTY // - default to not-found error // (DOES_NOT_EXIST error) // *ppRRSet = NULL; status = DNS_ERROR_RECORD_DOES_NOT_EXIST; Cache_GetRecordsForRpc( ppRRSet, & status, pwsName, wType, 0 // no screening flags ); DNSLOG_F3( " CRrReadCacheEntry - Returning status : 0x%.8X\n\t%s", status, Dns_StatusString( status ) ); DNSLOG_F1( "" ); DNSDBG( RPC, ( "Leave CRrReadCacheEntry( %S, %d ) => %d\n\n", pwsName, wType, status )); return status; } DNS_STATUS CRrGetHashTableStats( IN DNS_RPC_HANDLE Reserved, OUT LPDWORD pdwCacheHashTableSize, OUT LPDWORD pdwCacheHashTableBucketSize, OUT LPDWORD pdwNumberOfCacheEntries, OUT LPDWORD pdwNumberOfRecords, OUT LPDWORD pdwNumberOfExpiredRecords, OUT PDNS_STATS_TABLE * ppStatsTable ) /*++ Routine Description: Arguments: Return Value: --*/ { PDNS_STATS_TABLE pprevRow = NULL; PDWORD_LIST_ITEM pprevItem = NULL; DWORD rowIter; DWORD itemIter; DWORD countExpiredRecords = 0; DWORD status = ERROR_SUCCESS; UNREFERENCED_PARAMETER(Reserved); if ( !pdwCacheHashTableSize || !pdwCacheHashTableBucketSize || !pdwNumberOfCacheEntries || !pdwNumberOfRecords || !pdwNumberOfExpiredRecords || !ppStatsTable ) { return ERROR_INVALID_PARAMETER; } DNSLOG_F1( "CRrGetHashTableStats" ); DNSDBG( RPC, ( "CRrGetHashTableStats\n" )); if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "CRrGetHashTableStats - ERROR_ACCESS_DENIED" ); return ERROR_ACCESS_DENIED; } LOCK_CACHE(); *pdwCacheHashTableSize = g_HashTableSize; //*pdwCacheHashTableBucketSize = g_CacheHashTableBucketSize; *pdwCacheHashTableBucketSize = 0; *pdwNumberOfCacheEntries = g_EntryCount; *pdwNumberOfRecords = g_RecordSetCount; *pdwNumberOfExpiredRecords = 0; // // read entire hash table // for ( rowIter = 0; rowIter < g_HashTableSize; rowIter++ ) { PCACHE_ENTRY pentry = g_HashTable[rowIter]; PDNS_STATS_TABLE pnewRow; // // create table for each new row // pnewRow = RPC_HEAP_ALLOC( sizeof(DNS_STATS_TABLE) ); if ( !pnewRow ) { status = ERROR_NOT_ENOUGH_MEMORY; goto Done; } if ( rowIter == 0 ) *ppStatsTable = pnewRow; else pprevRow->pNext = pnewRow; // // fill in row data (if any) // while ( pentry ) { PDWORD_LIST_ITEM pnewItem; pnewItem = RPC_HEAP_ALLOC( sizeof( DWORD_LIST_ITEM ) ); if ( !pnewItem ) { status = ERROR_NOT_ENOUGH_MEMORY; goto Done; } for ( itemIter = 0; itemIter < pentry->MaxCount; itemIter++ ) { PDNS_RECORD prr = pentry->Records[itemIter]; if ( prr ) { pnewItem->Value1++; if ( !Cache_IsRecordTtlValid( prr ) ) { pnewItem->Value2++; countExpiredRecords++; } } } if ( !pnewRow->pListItem ) pnewRow->pListItem = pnewItem; else pprevItem->pNext = pnewItem; pprevItem = pnewItem; pentry = pentry->pNext; } pprevRow = pnewRow; } Done: UNLOCK_CACHE(); *pdwNumberOfExpiredRecords = countExpiredRecords; return status; } BOOL IsKnownNetFailure( VOID ) /*++ Routine Description: Determine if we are in known net failure window. Arguments: None Return Value: TRUE if in known net failure FALSE otherwise --*/ { BOOL flag = FALSE; DNSDBG( TRACE, ( "IsKnownNetFailure()\n" )); LOCK_NET_FAILURE(); if ( g_NetFailureStatus ) { if ( g_NetFailureTime < Dns_GetCurrentTimeInSeconds() ) { g_NetFailureTime = 0; g_NetFailureStatus = ERROR_SUCCESS; flag = FALSE; } else { SetLastError( g_NetFailureStatus ); flag = TRUE; } } UNLOCK_NET_FAILURE(); return flag; } VOID SetKnownNetFailure( IN DNS_STATUS Status ) /*++ Routine Description: Set cause of net failure. Arguments: Status -- status code for cause of net failure Return Value: None --*/ { LPSTR DnsString = NULL; LPWSTR InsertStrings[3]; WCHAR String1[25]; WCHAR String2[256]; WCHAR String3[25]; DNSDBG( TRACE, ( "SetKnownNetFailure()\n" )); // // don't indicate failure during boot // if ( Dns_GetCurrentTimeInSeconds() < THREE_MINUTES_FROM_SYSTEM_BOOT ) { return; } if ( !g_LocalAddrArray || g_NetFailureCacheTime == 0 ) { // // We are in a no-net configuration, there is no need // to display the pop-up message. No point warning // of DNS configuration problems when the system is // off the net. // - or - // We are on a NT server, and therefore don't do poor network // performance caching. // return; } LOCK_NET_FAILURE(); g_NetFailureTime = Dns_GetCurrentTimeInSeconds() + g_NetFailureCacheTime; g_NetFailureStatus = Status; wsprintfW( String1, L"0x%.8X", Status ); DnsString = DnsStatusString( Status ); if ( DnsString ) { Dns_StringCopy( (PBYTE) String2, NULL, (PCHAR) DnsString, (WORD) strlen( DnsString ), DnsCharSetAnsi, DnsCharSetUnicode ); // // No need to free this since the string is just a pointer // to a global table entry. // // FREE_HEAP( DnsString ); } else { wsprintfW( String2, L"" ); } wsprintfW( String3, L"%d", g_NetFailureCacheTime ); if ( g_MessagePopupStrikes < 3 ) { g_MessagePopupStrikes++; } else { if ( Status != g_PreviousNetFailureStatus ) { // // DCR_PERF: should remove logging from inside lock // InsertStrings[0] = String1; InsertStrings[1] = String2; InsertStrings[2] = String3; ResolverLogEvent( EVENT_DNS_CACHE_NETWORK_PERF_WARNING, EVENTLOG_WARNING_TYPE, 3, InsertStrings, Status ); g_PreviousNetFailureStatus = Status; } g_MessagePopupStrikes = 0; } UNLOCK_NET_FAILURE(); } BOOL IsKnownTimedOutAdapter( VOID ) /*++ Routine Description: Determine if timed out adapter exists. Arguments: None Return Value: TRUE if timed out adapter FALSE otherwise --*/ { BOOL flag = FALSE; DNSDBG( TRACE, ( "IsKnownTimedOutAdapter()\n" )); // // DCR: don't really need lock for this? // - could check if lock taken? // but if beat it -- so what // LOCK_NET_FAILURE(); if ( g_fTimedOutAdapter ) { if ( g_TimedOutAdapterTime < Dns_GetCurrentTimeInSeconds() ) { DNSLOG_F1( " Timed out adapter cache expired, resseting adapter!" ); g_TimedOutAdapterTime = 0; g_fTimedOutAdapter = FALSE; flag = FALSE; } else { flag = TRUE; } } UNLOCK_NET_FAILURE(); return flag; } VOID SetKnownTimedOutAdapter( VOID ) { DNSDBG( TRACE, ( "SetKnownTimedOutAdapter()\n" )); if ( Dns_GetCurrentTimeInSeconds() < THREE_MINUTES_FROM_SYSTEM_BOOT ) { return; } if ( !g_LocalAddrArray ) { // // We are in a no-net configuration, there is no need // to display the pop-up message. No point warning // of DNS configuration problems when the system is // off the net. // return; } DNSLOG_F1( " Detected a timed out adapter, disabling it for a little while" ); LOCK_NET_FAILURE(); g_TimedOutAdapterTime = Dns_GetCurrentTimeInSeconds() + g_AdapterTimeoutLimit; g_fTimedOutAdapter = TRUE; UNLOCK_NET_FAILURE(); } DWORD PopupMessageThread( IN OUT PPOPUP_MSG_PARMS MsgParms ) /*++ Routine Description: Popup a message box with error. Arguments: MsgParms -- popup message parameters Return Value: ERROR_SUCCESS --*/ { MessageBoxW( NULL, MsgParms->Message, MsgParms->Title, MB_SERVICE_NOTIFICATION | MB_ICONWARNING | MB_OK ); GENERAL_HEAP_FREE( MsgParms->Message ); GENERAL_HEAP_FREE( MsgParms->Title ); GENERAL_HEAP_FREE( MsgParms ); return ERROR_SUCCESS; } BOOL ClientThreadNotAllowedAccess( VOID ) { #if 0 // // DCR: should probably access check only for flush cache or // delete entry // // // DCR: - This idea of adding a security check for // DNS RPC API is really debatable. The data // maintained by the DNS Caching Resolver is definately // not private. Since the cache mimicks the DNS protocol // as a non-handle based access to public information, // it would require an access check for every interface // and for every call. Doing this would required a context // switch into kernel mode to perform the check. This is // a lot of overhead just to protect an API that provides // access to widely available information. After all, the // cache is supposed to improve name resolution performance! // // Below is the start of some code to implement an access // check, though it sounds like I should call the Win32 // security function AccessCheck() and create a DNS cache // SID to compare the desired access of the client thread // against. This is not finished and I don't intend to // try finish it. // GENERIC_MAPPING DNSAccessMapping = { STANDARD_RIGHTS_READ, STANDARD_RIGHTS_WRITE, STANDARD_RIGHTS_EXECUTE }; HANDLE hThread = GetCurrentThread(); DWORD dwGrantedAccess; BOOL Result; if ( RpcImpersonateClient(NULL) ) return TRUE; hThread = GetCurrentThread(); if ( !hThread ) { RpcRevertToSelf(); return TRUE; } if ( OpenThreadToken( hThread, TOKEN_QUERY, FALSE, &hToken ) ) { if ( AccessCheck( pSD, hToken, STANDARD_RIGHTS_WRITE, &DNSAccessMapping, pPS, sizeof( *pPS ), &dwGrantedAccess, &Result ); } CloseHandle( hThread ); RpcRevertToSelf(); if ( Result ) return FALSE; else return TRUE; #endif return FALSE; } PDNS_RPC_CACHE_TABLE CreateCacheTableEntry( IN LPWSTR pwsName ) { PDNS_RPC_CACHE_TABLE prpcEntry = NULL; if ( ! pwsName ) return NULL; prpcEntry = (PDNS_RPC_CACHE_TABLE) RPC_HEAP_ALLOC_ZERO( sizeof(DNS_RPC_CACHE_TABLE) ); if ( prpcEntry == NULL ) return NULL; prpcEntry->Name = RPC_HEAP_ALLOC( sizeof(WCHAR) * (wcslen(pwsName) + 1) ); if ( ! prpcEntry->Name ) { RPC_HEAP_FREE( prpcEntry ); return NULL; } wcscpy( prpcEntry->Name, pwsName ); return prpcEntry; } VOID FreeCacheTableEntryList( IN PDNS_RPC_CACHE_TABLE pCacheTableList ) { while ( pCacheTableList ) { PDNS_RPC_CACHE_TABLE pNext = pCacheTableList->pNext; if ( pCacheTableList->Name ) { RPC_HEAP_FREE( pCacheTableList->Name ); pCacheTableList->Name = NULL; } RPC_HEAP_FREE( pCacheTableList ); pCacheTableList = pNext; } } BOOL IsEmptyDnsResponse( IN PDNS_RECORD pRecord ) { // // DCR_FIX: should be dnslib utility // // DCR_FIX: should distinguish referral and no-records // PDNS_RECORD pTempRecord = pRecord; BOOL fEmpty = TRUE; while ( pTempRecord ) { if ( pTempRecord->Flags.S.Section == DNSREC_ANSWER ) { fEmpty = FALSE; break; } pTempRecord = pTempRecord->pNext; } return fEmpty; } DNS_STATUS CRrUpdateTest( IN DNS_RPC_HANDLE Reserved, IN PWSTR pwsName, IN DWORD fOptions, IN IP_ADDRESS ServerIp ) /*++ Routine Description: Do update test for existing record. DCR: need UpdateTest() IPv6 capable Arguments: Return Value: ErrorCode from update attempt. --*/ { DNS_STATUS status = ERROR_SUCCESS; DNS_RECORD record; DWORD flags = fOptions; PSTR pnameTemp = NULL; PDNS_NETINFO pnetInfo = NULL; IP_ARRAY serverIpArray; PIP_ARRAY pserverIpArray = NULL; DNSLOG_F1( "DNS Caching Resolver Service - CRrUpdateTest" ); DNSDBG( RPC, ( "\nCRrUpdateTest()\n" )); // // Validate arguments // if ( !pwsName || !ServerIp ) { return ERROR_INVALID_PARAMETER; } // // make UTF8 name and FAZ // // DCR: not clear why all this work isn't just done in update API // pnameTemp = Dns_NameCopyAllocate( (PCHAR) pwsName, 0, DnsCharSetUnicode, DnsCharSetUtf8 ); if ( ! pnameTemp ) { return ERROR_NOT_ENOUGH_MEMORY; } serverIpArray.AddrCount = 1; serverIpArray.AddrArray[0] = ServerIp; pserverIpArray = &serverIpArray; status = Dns_FindAuthoritativeZoneLib( (PDNS_NAME) pnameTemp, 0, pserverIpArray, &pnetInfo ); if ( status != NO_ERROR ) { goto Cleanup; } // // build update prereq "nothing exists" record // RtlZeroMemory( &record, sizeof(DNS_RECORD) ); record.pName = (PDNS_NAME) pnameTemp; record.wType = DNS_TYPE_ANY; record.wDataLength = 0; record.Flags.DW = DNSREC_PREREQ | DNSREC_NOEXIST; // // update // status = Dns_UpdateLib( &record, 0, pnetInfo, NULL, NULL ); Cleanup: Dns_Free( pnameTemp ); NetInfo_Free( pnetInfo ); DNSDBG( RPC, ( "Leave CRrUpdateTest() => %d\n\n", status )); return status; } // // End remote.c //