/*++ Copyright (c) 1997-2000 Microsoft Corporation Module Name: rrcomp.c Abstract: Domain Name System (DNS) Library Compare resource record routines. Author: Jim Gilroy (jamesg) February, 1997 Revision History: --*/ #include "local.h" #include "locale.h" // for setlocale stuff for Win9x // // Type specific RR compare routine prototypes // BOOL ARecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare A records. All these routines assume: - type compare complete - datalength compare completed - NO unicode Arguments: pRR1 - first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { return( pRR1->Data.A.IpAddress == pRR2->Data.A.IpAddress ); } BOOL PtrRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare PTR compatible record. Includes: NS, PTR, CNAME, MB, MR, MG, MD, MF Arguments: pRR1 - first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { return Dns_NameComparePrivate( (LPSTR) pRR1->Data.PTR.pNameHost, (LPSTR) pRR2->Data.PTR.pNameHost, RECORD_CHARSET(pRR1) ); } BOOL MxRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare MX compatible record. Includes: MX, RT, AFSDB Arguments: pRR1 - first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { // verify preference match first if ( pRR1->Data.MX.wPreference != pRR2->Data.MX.wPreference ) { return( FALSE ); } // then result is name comparison return Dns_NameComparePrivate( (LPSTR) pRR1->Data.MX.pNameExchange, (LPSTR) pRR2->Data.MX.pNameExchange, RECORD_CHARSET(pRR1) ); } BOOL SoaRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare SOA record. Arguments: pRR1 - first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { // verify integer data match first if ( memcmp( & pRR1->Data.SOA.dwSerialNo, & pRR2->Data.SOA.dwSerialNo, SIZEOF_SOA_FIXED_DATA ) ) { return( FALSE ); } // match check names // - primary name server // - admin email name if ( ! Dns_NameComparePrivate( (LPSTR) pRR1->Data.SOA.pNamePrimaryServer, (LPSTR) pRR2->Data.SOA.pNamePrimaryServer, RECORD_CHARSET(pRR1) ) ) { return( FALSE ); } return Dns_NameComparePrivate( (LPSTR) pRR1->Data.SOA.pNameAdministrator, (LPSTR) pRR2->Data.SOA.pNameAdministrator, RECORD_CHARSET(pRR1) ); } BOOL MinfoRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare MINFO and RP records. Arguments: pRR1 -- first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { if ( ! Dns_NameComparePrivate( (LPSTR) pRR1->Data.MINFO.pNameMailbox, (LPSTR) pRR2->Data.MINFO.pNameMailbox, RECORD_CHARSET(pRR1) ) ) { return( FALSE ); } return Dns_NameComparePrivate( (LPSTR) pRR1->Data.MINFO.pNameErrorsMailbox, (LPSTR) pRR2->Data.MINFO.pNameErrorsMailbox, RECORD_CHARSET(pRR1) ); } BOOL TxtRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare TXT compatible records. Includes: TXT, X25, HINFO, ISDN Arguments: pRR1 -- first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { DWORD count; PCHAR * pstring1; PCHAR * pstring2; // // compare every string // since string order DOES matter // - find string count // - compare each (case-sensitive) // count = pRR1->Data.TXT.dwStringCount; if ( count != pRR2->Data.TXT.dwStringCount ) { return( FALSE ); } pstring1 = (PCHAR *) pRR1->Data.TXT.pStringArray; pstring2 = (PCHAR *) pRR2->Data.TXT.pStringArray; while ( count-- ) { if ( IS_UNICODE_RECORD(pRR1) ) { if ( wcscmp( (LPWSTR)*pstring1++, (LPWSTR)*pstring2++ ) != 0 ) { return( FALSE ); } } else { if ( strcmp( *pstring1++, *pstring2++ ) != 0 ) { return( FALSE ); } } } return( TRUE ); } BOOL FlatRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare flat data records. Includes AAAA type. Arguments: pRR1 -- first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { if ( pRR1->wDataLength != pRR2->wDataLength ) { return( FALSE ); } return( !memcmp( & pRR1->Data, & pRR2->Data, pRR1->wDataLength ) ); } BOOL SrvRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare SRV record. Arguments: pRR1 -- first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { // verify integer data match first if ( memcmp( & pRR1->Data.SRV.wPriority, & pRR2->Data.SRV.wPriority, SIZEOF_SRV_FIXED_DATA ) ) { return( FALSE ); } // then result is compare on target host return Dns_NameComparePrivate( (LPSTR) pRR1->Data.SRV.pNameTarget, (LPSTR) pRR2->Data.SRV.pNameTarget, RECORD_CHARSET(pRR1) ); } BOOL AtmaRecordCompare( IN PDNS_RECORD pRR1, IN PDNS_RECORD pRR2 ) /*++ Routine Description: Compare ATMA record. Arguments: pRR1 -- first record pRR2 -- second record Return Value: TRUE if record data equal FALSE otherwise --*/ { WORD length = pRR1->wDataLength; if ( length > pRR2->wDataLength ) { length = pRR2->wDataLength; } // verify integer data match first if ( pRR1->Data.ATMA.AddressType != pRR2->Data.ATMA.AddressType ) { return( FALSE ); } if ( memcmp( pRR1->Data.ATMA.Address, pRR2->Data.ATMA.Address, length ) != 0 ) { return( FALSE ); } return( TRUE ); } // // RR compare routines jump table // typedef BOOL (* RR_COMPARE_FUNCTION)( PDNS_RECORD, PDNS_RECORD ); //extern RR_COMPARE_FUNCTION RRCompareTable[]; RR_COMPARE_FUNCTION RRCompareTable[] = { NULL, // ZERO ARecordCompare, // A PtrRecordCompare, // NS PtrRecordCompare, // MD PtrRecordCompare, // MF PtrRecordCompare, // CNAME SoaRecordCompare, // SOA PtrRecordCompare, // MB PtrRecordCompare, // MG PtrRecordCompare, // MR NULL, // NULL NULL, //WksRecordCompare, // WKS PtrRecordCompare, // PTR TxtRecordCompare, // HINFO MinfoRecordCompare, // MINFO MxRecordCompare, // MX TxtRecordCompare, // TXT MinfoRecordCompare, // RP MxRecordCompare, // AFSDB TxtRecordCompare, // X25 TxtRecordCompare, // ISDN MxRecordCompare, // RT NULL, // NSAP NULL, // NSAPPTR NULL, // SIG NULL, // KEY NULL, // PX NULL, // GPOS FlatRecordCompare, // AAAA NULL, // LOC NULL, // NXT NULL, // EID NULL, // NIMLOC SrvRecordCompare, // SRV AtmaRecordCompare, // ATMA NULL, // NAPTR NULL, // KX NULL, // CERT NULL, // A6 NULL, // DNAME NULL, // SINK NULL, // OPT NULL, // 42 NULL, // 43 NULL, // 44 NULL, // 45 NULL, // 46 NULL, // 47 NULL, // 48 // // NOTE: last type indexed by type ID MUST be set // as MAX_SELF_INDEXED_TYPE #define in record.h // (see note above in record info table) // // Pseudo record types // NULL, // TKEY NULL, // TSIG // // MS only types // FlatRecordCompare, // WINS NULL, // WINSR }; BOOL WINAPI Dns_RecordCompare( IN PDNS_RECORD pRecord1, IN PDNS_RECORD pRecord2 ) /*++ Routine Description: Compare two records. Record compare ignores TTL and flags and section information. Arguments: pRecord1 -- first record pRecord2 -- second record Return Value: TRUE if records equal. FALSE otherwise. --*/ { BOOL fresult; WORD type = pRecord1->wType; WORD index; IF_DNSDBG( UPDATE ) { DNS_PRINT(( "Dns_RecordCompare()\n" "\tfirst = %p\n" "\tfirst = %p\n", pRecord1, pRecord2 )); } // // verify that both records have same character set // if ( RECORD_CHARSET(pRecord1) != RECORD_CHARSET(pRecord2) ) { DNS_PRINT(( "ERROR: comparing records with non-matching character sets!\n" )); // // If they are different and one of them is undefined, just use // the defined char set of the other for each. // if ( !RECORD_CHARSET(pRecord1) && RECORD_CHARSET(pRecord2) ) { RECORD_CHARSET(pRecord1) = RECORD_CHARSET(pRecord2); } if ( !RECORD_CHARSET(pRecord2) && RECORD_CHARSET(pRecord1) ) { RECORD_CHARSET(pRecord2) = RECORD_CHARSET(pRecord1); } } // // compare type // if ( type != pRecord2->wType ) { DNSDBG( UPDATE, ( "record compare failed -- type mismatch\n" )); return( FALSE ); } // // compare names // if ( ! Dns_NameComparePrivate( pRecord1->pName, pRecord2->pName, RECORD_CHARSET( pRecord1 ) ) ) { DNSDBG( UPDATE, ( "record compare failed -- owner name mismatch\n" )); return( FALSE ); } // // compare data // index = INDEX_FOR_TYPE( type ); DNS_ASSERT( index <= MAX_RECORD_TYPE_INDEX ); if ( !index || !RRCompareTable[ index ] ) { fresult = FlatRecordCompare( pRecord1, pRecord2 ); } else { fresult = RRCompareTable[ index ]( pRecord1, pRecord2 ); } IF_DNSDBG( UPDATE ) { DNS_PRINT(( "Dns_RecordCompare(%p, %p) returns = %d.\n", pRecord1, pRecord2, fresult )); } return( fresult ); } DNS_STATUS buildUnmatchedRecordSet( OUT PDNS_RECORD * ppDiffRR, IN OUT PDNS_RECORD pRR ) /*++ Routine Description: Build new list of difference records. Arguments: ppDiffRR - address to recieve PTR to new set pRR - incoming RR set; matched records marked wReserved TRUE Return Value: ERROR_SUCCESS if successful. Error code on failure. --*/ { PDNS_RECORD pcur; PDNS_RECORD pnew; DNS_RRSET rrset; // init comparison rrset DNS_RRSET_INIT( rrset ); // // loop through RR set, add records to either match or diff sets // pcur = pRR; while ( pcur ) { if ( ! IS_RR_MATCHED(pcur) ) { // make copy of record pnew = Dns_RecordCopyEx( pcur, RECORD_CHARSET(pcur), RECORD_CHARSET(pcur) ); if ( !pnew ) { // DCR_FIX1: last error not set on all Dns_RecordCopy() failures // Charlie Wickham was getting some random win32 error // // DNS_STATUS status = GetLastError(); // assume unable to copy because of invalid data DNS_PRINT(( "ERROR: unable to copy record at %p\n" "\thence unable to build diff of set at %p\n", pcur, pRR )); Dns_RecordListFree( rrset.pFirstRR ); *ppDiffRR = NULL; //return( status ? status : ERROR_INVALID_DATA ); return( ERROR_INVALID_DATA ); } DNS_RRSET_ADD( rrset, pnew ); } pcur = pcur->pNext; } *ppDiffRR = rrset.pFirstRR; return( ERROR_SUCCESS ); } DWORD isUnmatchedRecordInSet( IN PDNS_RECORD pRR ) /*++ Routine Description: Check if unmatched record in set. Arguments: pRR - incoming RR set; matched records marked wReserved TRUE Return Value: Count of all unmatched records in set. Zero if all records matched. --*/ { PDNS_RECORD pcur; DWORD countUnmatched = 0; // // loop through RR set check for unmatched records // pcur = pRR; while ( pcur ) { if ( ! IS_RR_MATCHED(pcur) ) { countUnmatched++; } pcur = pcur->pNext; } return( countUnmatched ); } DNS_SET_COMPARE_RESULT WINAPI Dns_RecordSetCompareEx( IN OUT PDNS_RECORD pRR1, IN OUT PDNS_RECORD pRR2, OUT PDNS_RECORD * ppDiff1, OPTIONAL OUT PDNS_RECORD * ppDiff2 OPTIONAL ) /*++ Routine Description: Compare two records. Record compare ignores TTL and flags and section information. Arguments: pRR1 - first incoming RR set pRR2 - second incoming RR set ppDiff1 - addr to receive ptr to unmatched records from first set ppDiff2 - addr to receive ptr to unmatched records from second set Return Value: Result indicating relationship -- or error on allocation error: DnsSetCompareError DnsSetCompareIdentical DnsSetCompareNoOverlap DnsSetCompareOneSubsetOfTwo DnsSetCompareTwoSubsetOfOne DnsSetCompareIntersection --*/ { PDNS_RECORD pcur1; PDNS_RECORD pcur2; DWORD count1 = 0; DWORD count2 = 0; DNS_STATUS status; DWORD unmatched1; DWORD unmatched2; DNS_SET_COMPARE_RESULT result; // // init RR sets for compare // - clear reserved field used as matched flag in compare // pcur1 = pRR1; while ( pcur1 ) { CLEAR_RR_MATCHED(pcur1); pcur1 = pcur1->pNext; count1++; } pcur1 = pRR2; while ( pcur1 ) { CLEAR_RR_MATCHED(pcur1); pcur1 = pcur1->pNext; count2++; } // // loop through set 1 // attempt match of each record to all records in set 2 // except those already matched pcur1 = pRR1; while ( pcur1 ) { pcur2 = pRR2; while ( pcur2 ) { if ( !IS_RR_MATCHED(pcur2) && Dns_RecordCompare( pcur1, pcur2 ) ) { SET_RR_MATCHED(pcur1); SET_RR_MATCHED(pcur2); } pcur2 = pcur2->pNext; } pcur1 = pcur1->pNext; } // // get diff record lists, return // - if no diffs, then have match // // tedious, but do all this error handling because it is easy for // user to pass in bad records that may fail copy routines, need // way to easily report info, even if only for debugging apps calling in // if ( ppDiff2 ) { *ppDiff2 = NULL; } if ( ppDiff1 ) { *ppDiff1 = NULL; status = buildUnmatchedRecordSet( ppDiff1, pRR1 ); if ( status != ERROR_SUCCESS ) { goto Failed; } } if ( ppDiff2 ) { status = buildUnmatchedRecordSet( ppDiff2, pRR2 ); if ( status != ERROR_SUCCESS ) { if ( ppDiff1 && *ppDiff1 ) { Dns_RecordListFree( *ppDiff1 ); } goto Failed; } } // // determine relationship between sets // // impl note: the only better way i could see doing this // is to map relationships directly to bit flags // however, our enum type doesn't map to bit flags, so // then would have to run mapping table to map flags to enum // // note, we do compare so that NULL lists comes out the first // as no-overlap rather than as subset // unmatched1 = isUnmatchedRecordInSet( pRR1 ); unmatched2 = isUnmatchedRecordInSet( pRR2 ); if ( unmatched1 == count1 ) { ASSERT( unmatched2 == count2 ); result = DnsSetCompareNoOverlap; } else if ( unmatched1 == 0 ) { if ( unmatched2 == 0 ) { result = DnsSetCompareIdentical; } else { ASSERT( unmatched2 != count2 ); result = DnsSetCompareOneSubsetOfTwo; } } else if ( unmatched2 == 0 ) { result = DnsSetCompareTwoSubsetOfOne; } else { ASSERT( unmatched2 != count2 ); result = DnsSetCompareIntersection; } return( result ); Failed: SetLastError( status ); return( DnsSetCompareError ); } BOOL WINAPI Dns_RecordSetCompare( IN OUT PDNS_RECORD pRR1, IN OUT PDNS_RECORD pRR2, OUT PDNS_RECORD * ppDiff1, OPTIONAL OUT PDNS_RECORD * ppDiff2 OPTIONAL ) /*++ Routine Description: Compare two records. Record compare ignores TTL and flags and section information. Arguments: pRR1 - first incoming RR set pRR2 - second incoming RR set ppDiff1 - addr to receive ptr to unmatched records from first set ppDiff2 - addr to receive ptr to unmatched records from second set Return Value: TRUE if record sets equal. FALSE otherwise. --*/ { DNS_SET_COMPARE_RESULT result; result = Dns_RecordSetCompareEx( pRR1, pRR2, ppDiff1, ppDiff2 ); return( result == DnsSetCompareIdentical ); } BOOL WINAPI Dns_RecordSetCompareForIntersection( IN OUT PDNS_RECORD pRR1, IN OUT PDNS_RECORD pRR2 ) /*++ Routine Description: Compare two record sets for intersection. Arguments: pRR1 - first incoming RR set pRR2 - second incoming RR set Return Value: TRUE if record sets intersect. FALSE otherwise. --*/ { DNS_SET_COMPARE_RESULT result; result = Dns_RecordSetCompareEx( pRR1, pRR2, NULL, NULL ); return( result == DnsSetCompareIdentical || result == DnsSetCompareIntersection || result == DnsSetCompareOneSubsetOfTwo || result == DnsSetCompareTwoSubsetOfOne ); } // // End rrcomp.c // BOOL WINAPI Dns_DeleteRecordFromList( IN OUT PDNS_RECORD * ppRRList, IN PDNS_RECORD pRRDelete ) /*++ Routine Description: Delete matching record from record list. Arguments: ppRRList -- addr of record list ptr note that in case FIRST record in list is deleted, ppRRList is set with ptr to new first record in the list (possibly NULL) pRRDelete -- record to delete Return Value: TRUE if record found in list and deleted. FALSE if no match. --*/ { PDNS_RECORD prr; PDNS_RECORD pprev; DNSDBG( TRACE, ( "Dns_DeleteRecordFromList( %p, %p )\n", ppRRList, pRRDelete )); // // loop through list // // note, startup works as pNext is first field in DNS_RECORD // so access to pNext is simply dereference of the record ptr // -- same operation as deref of list address to get first record; // this allows us to have pprev pointer starting at ppRRList, and // eliminates special code for handling delete of first record // prr = (PDNS_RECORD) ppRRList; while ( pprev = prr, prr = prr->pNext ) { if ( Dns_RecordCompare( prr, pRRDelete ) ) { pprev->pNext = prr->pNext; Dns_RecordFree( prr ); return TRUE; } } return FALSE; }