/*++ Copyright (c) 1997-2000 Microsoft Corporation Module Name: rconvert.c Abstract: Domain Name System (DNS) Server -- Admin Client Library RPC record conversion routines. Convert records in RPC buffer to DNS_RECORD type. Author: Jim Gilroy (jamesg) April, 1997 Revision History: --*/ #include "dnsclip.h" #define IS_COMPLETE_NODE( pRpcNode ) \ (!!((pRpcNode)->dwFlags & DNS_RPC_NODE_FLAG_COMPLETE)) // // Copy-convert string from RPC format (UTF8) into DNS_RECORD buffer // - assume previously allocated required buffer // // Note: no difference between string and name conversion as we're // going FROM UTF8 // #define COPY_UTF8_STR_TO_BUFFER( buf, psz, len, charSet ) \ Dns_StringCopy( \ (buf), \ NULL, \ (psz), \ (len), \ DnsCharSetUtf8, \ (charSet) ) // // RPC record to DNS_RECORD conversion routines // PDNS_RECORD ARpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read A record data from packet. Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; DNS_ASSERT( pRpcRR->wDataLength == sizeof( IP_ADDRESS ) ); precord = Dns_AllocateRecord( sizeof( IP_ADDRESS ) ); if ( !precord ) { return NULL; } precord->Data.A.IpAddress = pRpcRR->Data.A.ipAddress; return precord; } PDNS_RECORD PtrRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Process PTR compatible record from wire. Includes: NS, PTR, CNAME, MB, MR, MG, MD, MF Arguments: pRpcRR - message being read CharSet - character set for resulting record Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; PDNS_RPC_NAME pname = &pRpcRR->Data.PTR.nameNode; WORD bufLength; // // PTR data is another domain name // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = sizeof( DNS_PTR_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // write hostname into buffer, immediately following PTR data struct // precord->Data.PTR.pNameHost = (PCHAR)&precord->Data + sizeof(DNS_PTR_DATA); COPY_UTF8_STR_TO_BUFFER( precord->Data.PTR.pNameHost, pname->achName, pname->cchNameLength, CharSet ); return precord; } PDNS_RECORD SoaRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read SOA record from wire. Arguments: pRR - ptr to record with RR set context pRpcRR - message being read pchData - ptr to RR data field pchEnd - ptr to byte after data field Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; WORD bufLength; DWORD dwLength; PDNS_RPC_NAME pnamePrimary = &pRpcRR->Data.SOA.namePrimaryServer; PDNS_RPC_NAME pnameAdmin; // // verify names in SOA record // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pnamePrimary) ) { DNS_ASSERT( FALSE ); return NULL; } pnameAdmin = DNS_GET_NEXT_NAME(pnamePrimary); if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pnameAdmin) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = sizeof( DNS_SOA_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pnamePrimary->cchNameLength, CharSet ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pnameAdmin->cchNameLength, CharSet ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy fixed fields // RtlCopyMemory( (PCHAR) & precord->Data.SOA.dwSerialNo, (PCHAR) & pRpcRR->Data.SOA.dwSerialNo, SIZEOF_SOA_FIXED_DATA ); // // copy names into RR buffer // - primary server immediately follows SOA data struct // - responsible party follows primary server // precord->Data.SOA.pNamePrimaryServer = (PCHAR)&precord->Data + sizeof(DNS_SOA_DATA); dwLength = COPY_UTF8_STR_TO_BUFFER( precord->Data.SOA.pNamePrimaryServer, pnamePrimary->achName, (DWORD)pnamePrimary->cchNameLength, CharSet ); precord->Data.SOA.pNameAdministrator = precord->Data.SOA.pNamePrimaryServer + dwLength; COPY_UTF8_STR_TO_BUFFER( precord->Data.SOA.pNameAdministrator, pnameAdmin->achName, (DWORD)pnameAdmin->cchNameLength, CharSet ); return precord; } PDNS_RECORD TxtRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read TXT compatible record from wire. Includes: TXT, X25, HINFO, ISDN Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; DWORD bufLength = 0; DWORD length = 0; INT count = 0; PCHAR pch; PCHAR pchend; PCHAR pchbuffer; PCHAR * ppstring; PDNS_RPC_NAME pname = &pRpcRR->Data.TXT.stringData; // // determine required buffer length and allocate // - allocate space for each string // - and ptr for each string // pch = (PCHAR)&pRpcRR->Data.TXT; pchend = pch + pRpcRR->wDataLength; while ( pch < pchend ) { length = (UCHAR) *pch++; pch += length; count++; bufLength += STR_BUF_SIZE_GIVEN_UTF8_LEN( length, CharSet ); } if ( pch != pchend ) { DNS_PRINT(( "ERROR: Invalid RPCstring data\n" " pch = %p\n" " pchEnd = %p\n" " count = %d\n" " length = %d\n", pch, pchend, count, length )); SetLastError( ERROR_INVALID_DATA ); return NULL; } // allocate bufLength += (WORD) DNS_TEXT_RECORD_LENGTH(count); precord = Dns_AllocateRecord( (WORD)bufLength ); if ( !precord ) { return NULL; } precord->Data.TXT.dwStringCount = count; // // go back through list copying strings to buffer // - ptrs to strings are saved to argv like data section // ppstring walks through this section // - first string written immediately following data section // - each subsequent string immediately follows predecessor // pchbuffer keeps ptr to position to write strings // pch = (PCHAR)&pRpcRR->Data.TXT; ppstring = precord->Data.TXT.pStringArray; pchbuffer = (PCHAR)ppstring + (count * sizeof(PCHAR)); while ( pch < pchend ) { length = (DWORD)((UCHAR) *pch++); *ppstring++ = pchbuffer; pchbuffer += COPY_UTF8_STR_TO_BUFFER( pchbuffer, pch, length, CharSet ); pch += length; #if DBG DNS_PRINT(( "Read text string %s\n", * (ppstring - 1) )); count--; #endif } DNS_ASSERT( pch == pchend && count == 0 ); return precord; } PDNS_RECORD MinfoRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read MINFO record from wire. Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; WORD bufLength; DWORD dwLength; PDNS_RPC_NAME pname1 = &pRpcRR->Data.MINFO.nameMailBox; PDNS_RPC_NAME pname2; // // verify names in MINFO record // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname1) ) { DNS_ASSERT( FALSE ); return NULL; } pname2 = DNS_GET_NEXT_NAME(pname1); if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname2) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = (WORD) ( sizeof( DNS_MINFO_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname1->cchNameLength, CharSet ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname2->cchNameLength, CharSet ) ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy names into RR buffer // - mailbox immediately follows MINFO data struct // - errors mailbox immediately follows primary server // precord->Data.MINFO.pNameMailbox = (PCHAR)&precord->Data + sizeof( DNS_MINFO_DATA ); dwLength = COPY_UTF8_STR_TO_BUFFER( precord->Data.MINFO.pNameMailbox, pname1->achName, (DWORD)pname1->cchNameLength, CharSet ); precord->Data.MINFO.pNameErrorsMailbox = precord->Data.MINFO.pNameMailbox + dwLength; COPY_UTF8_STR_TO_BUFFER( precord->Data.MINFO.pNameErrorsMailbox, pname2->achName, (DWORD)pname2->cchNameLength, CharSet ); return precord; } PDNS_RECORD MxRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read MX compatible record from wire. Includes: MX, RT, AFSDB Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; PDNS_RPC_NAME pname = &pRpcRR->Data.MX.nameExchange; WORD bufLength; // // MX exchange is another DNS name // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = sizeof( DNS_MX_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy preference // precord->Data.MX.wPreference = pRpcRR->Data.MX.wPreference; // // write hostname into buffer, immediately following MX struct // precord->Data.MX.pNameExchange = (PCHAR)&precord->Data + sizeof( DNS_MX_DATA ); COPY_UTF8_STR_TO_BUFFER( precord->Data.MX.pNameExchange, pname->achName, pname->cchNameLength, CharSet ); return precord; } PDNS_RECORD FlatRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read memory copy compatible record from wire. Includes AAAA and WINS types. Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; WORD bufLength; // // determine required buffer length and allocate // bufLength = pRpcRR->wDataLength; precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy packet data to record // RtlCopyMemory( & precord->Data, (PCHAR) &pRpcRR->Data.A, bufLength ); return precord; } PDNS_RECORD SrvRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read SRV record from wire. Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; PDNS_RPC_NAME pname = &pRpcRR->Data.SRV.nameTarget; WORD bufLength; // // SRV target host is another DNS name // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = sizeof( DNS_SRV_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy SRV fixed fields // precord->Data.SRV.wPriority = pRpcRR->Data.SRV.wPriority; precord->Data.SRV.wWeight = pRpcRR->Data.SRV.wWeight; precord->Data.SRV.wPort = pRpcRR->Data.SRV.wPort; // // write hostname into buffer, immediately following SRV struct // precord->Data.SRV.pNameTarget = (PCHAR)&precord->Data + sizeof( DNS_SRV_DATA ); COPY_UTF8_STR_TO_BUFFER( precord->Data.SRV.pNameTarget, pname->achName, pname->cchNameLength, CharSet ); return precord; } PDNS_RECORD NbstatRpcRecordConvert( IN PDNS_RPC_RECORD pRpcRR, IN DNS_CHARSET CharSet ) /*++ Routine Description: Read WINSR record from wire. Arguments: pRpcRR - message being read Return Value: Ptr to new record if successful. NULL on failure. --*/ { PDNS_RECORD precord; PDNS_RPC_NAME pname = &pRpcRR->Data.WINSR.nameResultDomain; WORD bufLength; // // WINSR target host is another DNS name // if ( ! DNS_IS_NAME_IN_RECORD(pRpcRR, pname) ) { DNS_ASSERT( FALSE ); return NULL; } // // determine required buffer length and allocate // bufLength = sizeof( DNS_WINSR_DATA ) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, CharSet ); precord = Dns_AllocateRecord( bufLength ); if ( !precord ) { return NULL; } // // copy WINSR fixed fields // precord->Data.WINSR.dwMappingFlag = pRpcRR->Data.WINSR.dwMappingFlag; precord->Data.WINSR.dwLookupTimeout = pRpcRR->Data.WINSR.dwLookupTimeout; precord->Data.WINSR.dwCacheTimeout = pRpcRR->Data.WINSR.dwCacheTimeout; // // write hostname into buffer, immediately following WINSR struct // precord->Data.WINSR.pNameResultDomain = (PCHAR)&precord->Data + sizeof( DNS_WINSR_DATA ); COPY_UTF8_STR_TO_BUFFER( precord->Data.WINSR.pNameResultDomain, pname->achName, pname->cchNameLength, CharSet ); return precord; } // // RR conversion from RPC buffer to DNS_RECORD // typedef PDNS_RECORD (* RR_CONVERT_FUNCTION)( PDNS_RPC_RECORD, DNS_CHARSET ); RR_CONVERT_FUNCTION RRRpcConvertTable[] = { NULL, // ZERO ARpcRecordConvert, // A PtrRpcRecordConvert, // NS PtrRpcRecordConvert, // MD PtrRpcRecordConvert, // MF PtrRpcRecordConvert, // CNAME SoaRpcRecordConvert, // SOA PtrRpcRecordConvert, // MB PtrRpcRecordConvert, // MG PtrRpcRecordConvert, // MR NULL, // NULL FlatRpcRecordConvert, // WKS PtrRpcRecordConvert, // PTR TxtRpcRecordConvert, // HINFO MinfoRpcRecordConvert, // MINFO MxRpcRecordConvert, // MX TxtRpcRecordConvert, // TXT MinfoRpcRecordConvert, // RP MxRpcRecordConvert, // AFSDB TxtRpcRecordConvert, // X25 TxtRpcRecordConvert, // ISDN MxRpcRecordConvert, // RT NULL, // NSAP NULL, // NSAPPTR NULL, // SIG NULL, // KEY NULL, // PX NULL, // GPOS FlatRpcRecordConvert, // AAAA NULL, // 29 NULL, // 30 NULL, // 31 NULL, // 32 SrvRpcRecordConvert, // SRV NULL, // ATMA NULL, // 35 NULL, // 36 NULL, // 37 NULL, // 38 NULL, // 39 NULL, // 40 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) // note these follow, but require OFFSET_TO_WINS_RR subtraction // from actual type value NULL, // TKEY NULL, // TSIG FlatRpcRecordConvert, // WINS NbstatRpcRecordConvert // WINS-R }; // // API for doing conversion // PDNS_RECORD DnsConvertRpcBufferToRecords( IN PBYTE * ppByte, IN PBYTE pStopByte, IN DWORD cRecords, IN PDNS_NAME pszNodeName, IN BOOLEAN fUnicode ) /*++ Routine Description: Convert RPC buffer records to standard DNS records. Arguments: ppByte -- addr of ptr into buffer where records start pStopByte -- stop byte of buffer cRecords -- number of records to convert pszNodeName -- node name (in desired format, not converted) fUnicode -- flag, write records into unicode Return Value: Ptr to new record(s) if successful. NULL on failure. --*/ { PDNS_RPC_RECORD prpcRecord = (PDNS_RPC_RECORD)*ppByte; PDNS_RECORD precord; DNS_RRSET rrset; WORD index; WORD type; DNS_CHARSET charSet; RR_CONVERT_FUNCTION pFunc; DNS_ASSERT( DNS_IS_DWORD_ALIGNED(prpcRecord) ); IF_DNSDBG( RPC2 ) { DNS_PRINT(( "Enter DnsConvertRpcBufferToRecords()\n" " pRpcRecord = %p\n" " Count = %d\n" " Nodename = %s%S\n", prpcRecord, cRecords, DNSSTRING_UTF8( fUnicode, pszNodeName ), DNSSTRING_WIDE( fUnicode, pszNodeName ) )); } DNS_RRSET_INIT( rrset ); // // loop until out of nodes // while( cRecords-- ) { if ( (PBYTE)prpcRecord >= pStopByte || (PBYTE)&prpcRecord->Data + prpcRecord->wDataLength > pStopByte ) { DNS_PRINT(( "ERROR: Bogus buffer at %p\n" " Record leads past buffer end at %p\n" " with %d records remaining\n", prpcRecord, pStopByte, cRecords+1 )); DNS_ASSERT( FALSE ); return NULL; } // // convert record // set unicode flag if converting // charSet = DnsCharSetUtf8; if ( fUnicode ) { charSet = DnsCharSetUnicode; } type = prpcRecord->wType; index = INDEX_FOR_TYPE( type ); DNS_ASSERT( index <= MAX_RECORD_TYPE_INDEX ); if ( !index || !(pFunc = RRRpcConvertTable[ index ]) ) { // if unknown type try flat record copy -- best we can // do to protect if server added new types since admin built DNS_PRINT(( "ERROR: no RPC to DNS_RECORD conversion routine for type %d\n" " using flat conversion routine\n", type )); pFunc = FlatRpcRecordConvert; } precord = (*pFunc)( prpcRecord, charSet ); if ( ! precord ) { DNS_PRINT(( "ERROR: Record build routine failure for record type %d\n" " status = %p\n\n", type, GetLastError() )); prpcRecord = DNS_GET_NEXT_RPC_RECORD(prpcRecord); continue; } // // fill out record structure // precord->pName = pszNodeName; precord->wType = type; RECORD_CHARSET( precord ) = charSet; // // DEVNOTE: data types (root hint, glue set) // if ( prpcRecord->dwFlags & DNS_RPC_RECORD_FLAG_CACHE_DATA ) { precord->Flags.S.Section = DNSREC_CACHE_DATA; } else { precord->Flags.S.Section = DNSREC_ZONE_DATA; } IF_DNSDBG( INIT ) { DnsDbg_Record( "New record built\n", precord ); } // // link into RR set // DNS_RRSET_ADD( rrset, precord ); prpcRecord = DNS_GET_NEXT_RPC_RECORD(prpcRecord); } IF_DNSDBG( RPC2 ) { DnsDbg_RecordSet( "Finished DnsConvertRpcBufferToRecords() ", rrset.pFirstRR ); } // reset ptr in buffer *ppByte = (PBYTE) prpcRecord; return rrset.pFirstRR; } PDNS_NODE DnsConvertRpcBufferNode( IN PDNS_RPC_NODE pRpcNode, IN PBYTE pStopByte, IN BOOLEAN fUnicode ) /*++ Routine Description: Convert RPC buffer records to standard DNS records. Arguments: pRpcNode -- ptr to RPC node in buffer pStopByte -- stop byte of buffer fUnicode -- flag, write records into unicode Return Value: Ptr to new node if successful. NULL on failure. --*/ { PDNS_NODE pnode; PDNS_RPC_NAME pname; PBYTE pendNode; IF_DNSDBG( RPC2 ) { DnsDbg_RpcNode( "Enter DnsConvertRpcBufferNode() ", pRpcNode ); } // // validate node // DNS_ASSERT( DNS_IS_DWORD_ALIGNED(pRpcNode) ); pendNode = (PBYTE)pRpcNode + pRpcNode->wLength; if ( pendNode > pStopByte ) { DNS_ASSERT( FALSE ); return NULL; } pname = &pRpcNode->dnsNodeName; if ( (PBYTE)DNS_GET_NEXT_NAME(pname) > pendNode ) { DNS_ASSERT( FALSE ); return NULL; } // // create node // pnode = (PDNS_NODE) ALLOCATE_HEAP( sizeof(DNS_NODE) + STR_BUF_SIZE_GIVEN_UTF8_LEN( pname->cchNameLength, fUnicode ) ); if ( !pnode ) { return NULL; } pnode->pNext = NULL; pnode->pRecord = NULL; pnode->Flags.W = 0; // // copy owner name, starts directly after node structure // pnode->pName = (PWCHAR) ((PBYTE)pnode + sizeof(DNS_NODE)); if ( ! Dns_StringCopy( (PCHAR) pnode->pName, NULL, pname->achName, pname->cchNameLength, DnsCharSetUtf8, // UTF8 in fUnicode ? DnsCharSetUnicode : DnsCharSetUtf8 ) ) { // name conversion error DNS_ASSERT( FALSE ); FREE_HEAP( pnode ); return NULL; } IF_DNSDBG( RPC2 ) { DnsDbg_RpcName( "Node name in RPC buffer: ", pname, "\n" ); DnsDbg_String( "Converted name ", (PCHAR) pnode->pName, fUnicode, "\n" ); } // // set flags // - name always internal // - catch domain roots // pnode->Flags.S.Unicode = fUnicode; if ( pRpcNode->dwChildCount || (pRpcNode->dwFlags & DNS_RPC_NODE_FLAG_STICKY) ) { pnode->Flags.S.Domain = TRUE; } IF_DNSDBG( RPC2 ) { DnsDbg_Node( "Finished DnsConvertRpcBufferNode() ", pnode, TRUE ); // view the records } return pnode; } PDNS_NODE DnsConvertRpcBuffer( OUT PDNS_NODE * ppNodeLast, IN DWORD dwBufferLength, IN BYTE abBuffer[], IN BOOLEAN fUnicode ) { PBYTE pbyte; PBYTE pstopByte; INT countRecords; PDNS_NODE pnode; PDNS_NODE pnodeFirst = NULL; PDNS_NODE pnodeLast = NULL; PDNS_RECORD precord; IF_DNSDBG( RPC2 ) { DNS_PRINT(( "DnsConvertRpcBuffer( %p ), len = %d\n", abBuffer, dwBufferLength )); } // // find stop byte // DNS_ASSERT( DNS_IS_DWORD_ALIGNED(abBuffer) ); pstopByte = abBuffer + dwBufferLength; pbyte = abBuffer; // // loop until out of nodes // while( pbyte < pstopByte ) { // // build owner node // - only build complete nodes // - add to list // if ( !IS_COMPLETE_NODE( (PDNS_RPC_NODE)pbyte ) ) { break; } pnode = DnsConvertRpcBufferNode( (PDNS_RPC_NODE)pbyte, pstopByte, fUnicode ); if ( !pnode ) { DNS_ASSERT( FALSE ); // DEVNOTE: cleanup return NULL; } if ( !pnodeFirst ) { pnodeFirst = pnode; pnodeLast = pnode; } else { pnodeLast->pNext = pnode; pnodeLast = pnode; } countRecords = ((PDNS_RPC_NODE)pbyte)->wRecordCount; pbyte += ((PDNS_RPC_NODE)pbyte)->wLength; pbyte = DNS_NEXT_DWORD_PTR(pbyte); // // for each node, build all records // if ( countRecords ) { precord = DnsConvertRpcBufferToRecords( & pbyte, pstopByte, countRecords, (PCHAR) pnode->pName, fUnicode ); if ( !precord ) { DNS_ASSERT( FALSE ); } pnode->pRecord = precord; } } // set last node and return first node *ppNodeLast = pnodeLast; return pnodeFirst; } // // End rconvert.c //