/*++ Copyright (c) 1995-2000 Microsoft Corporation Module Name: iparray.c Abstract: Domain Name System (DNS) Library IP Address array routines. Author: Jim Gilroy (jamesg) October 1995 Revision History: --*/ #include "local.h" // // Max IP count when doing IP array to\from string conversions // #define MAX_PARSE_IP (1000) // // Routines to handle actual array of IP addresses. // PIP_ADDRESS Dns_CreateIpAddressArrayCopy( IN PIP_ADDRESS aipAddress, IN DWORD cipAddress ) /*++ Routine Description: Create copy of IP address array. Arguments: aipAddress -- array of IP addresses cipAddress -- count of IP addresses Return Value: Ptr to IP address array copy, if successful NULL on failure. --*/ { PIP_ADDRESS pipArray; // validate if ( ! aipAddress || cipAddress == 0 ) { return( NULL ); } // allocate memory and copy pipArray = (PIP_ADDRESS) ALLOCATE_HEAP( cipAddress*sizeof(IP_ADDRESS) ); if ( ! pipArray ) { return( NULL ); } memcpy( pipArray, aipAddress, cipAddress*sizeof(IP_ADDRESS) ); return( pipArray ); } BOOL Dns_ValidateIpAddressArray( IN PIP_ADDRESS aipAddress, IN DWORD cipAddress, IN DWORD dwFlag ) /*++ Routine Description: Validate IP address array. Current checks: - existence - non-broadcast - non-lookback Arguments: aipAddress -- array of IP addresses cipAddress -- count of IP addresses dwFlag -- validity tests to do; currently unused Return Value: TRUE if valid IP addresses. FALSE if invalid address found. --*/ { DWORD i; // // protect against bad parameters // if ( cipAddress && ! aipAddress ) { return( FALSE ); } // // check each IP address // for ( i=0; i < cipAddress; i++) { if( aipAddress[i] == INADDR_ANY || aipAddress[i] == INADDR_BROADCAST || aipAddress[i] == INADDR_LOOPBACK ) { return( FALSE ); } } return( TRUE ); } // // IP_ARRAY routines // DWORD Dns_SizeofIpArray( IN PIP_ARRAY pIpArray ) /*++ Routine Description: Get size in bytes of IP address array. Arguments: pIpArray -- IP address array to find size of Return Value: Size in bytes of IP array. --*/ { if ( ! pIpArray ) { return 0; } return (pIpArray->AddrCount * sizeof(IP4_ADDRESS)) + sizeof(DWORD); } BOOL Dns_ProbeIpArray( IN PIP_ARRAY pIpArray ) /*++ Routine Description: Touch all entries in IP array to insure valid memory. Arguments: pIpArray -- ptr to IP address array Return Value: TRUE if successful. FALSE otherwise --*/ { DWORD i; BOOL result; if ( ! pIpArray ) { return( TRUE ); } for ( i=0; iAddrCount; i++ ) { result = ( pIpArray->AddrArray[i] == 0 ); } return( TRUE ); } #if 0 BOOL Dns_ValidateSizeOfIpArray( IN PIP_ARRAY pIpArray, IN DWORD dwMemoryLength ) /*++ Routine Description: Check that size of IP array, corresponds to length of memory. Arguments: pIpArray -- ptr to IP address array dwMemoryLength -- length of IP array memory Return Value: TRUE if IP array size matches memory length FALSE otherwise --*/ { return( Dns_SizeOfIpArray(pIpArray) == dwMemoryLength ); } #endif PIP_ARRAY Dns_CreateIpArray( IN DWORD AddrCount ) /*++ Routine Description: Create uninitialized IP address array. Arguments: AddrCount -- count of addresses array will hold Return Value: Ptr to uninitialized IP address array, if successful NULL on failure. --*/ { PIP_ARRAY pIpArray; DNSDBG( IPARRAY, ( "Dns_CreateIpArray() of count %d\n", AddrCount )); pIpArray = (PIP_ARRAY) ALLOCATE_HEAP_ZERO( (AddrCount * sizeof(IP_ADDRESS)) + sizeof(DWORD) ); if ( ! pIpArray ) { return( NULL ); } // // initialize IP count // pIpArray->AddrCount = AddrCount; DNSDBG( IPARRAY, ( "Dns_CreateIpArray() new array (count %d) at %p\n", AddrCount, pIpArray )); return( pIpArray ); } PIP_ARRAY Dns_BuildIpArray( IN DWORD AddrCount, IN PIP_ADDRESS pipAddrs ) /*++ Routine Description: Create IP address array structure from existing array of IP addresses. Arguments: AddrCount -- count of addresses in array pipAddrs -- IP address array Return Value: Ptr to IP address array. NULL on failure. --*/ { PIP_ARRAY pIpArray; if ( ! pipAddrs || ! AddrCount ) { return( NULL ); } // create IP array of desired size // then copy incoming array of addresses pIpArray = Dns_CreateIpArray( AddrCount ); if ( ! pIpArray ) { return( NULL ); } pIpArray->AddrCount = AddrCount; memcpy( pIpArray->AddrArray, pipAddrs, AddrCount * sizeof(IP_ADDRESS) ); return( pIpArray ); } PIP_ARRAY Dns_CopyAndExpandIpArray( IN PIP_ARRAY pIpArray, IN DWORD ExpandCount, IN BOOL fDeleteExisting ) /*++ Routine Description: Create expanded copy of IP address array. Arguments: pIpArray -- IP address array to copy ExpandCount -- number of IP to expand array size by fDeleteExisting -- TRUE to delete existing array; this is useful when function is used to grow existing IP array in place; note that locking must be done by caller note, that if new array creation FAILS -- then old array is NOT deleted Return Value: Ptr to IP array copy, if successful NULL on failure. --*/ { PIP_ARRAY pnewArray; DWORD newCount; // // no existing array -- just create desired size // if ( ! pIpArray ) { if ( ExpandCount ) { return Dns_CreateIpArray( ExpandCount ); } return( NULL ); } // // create IP array of desired size // then copy any existing addresses // pnewArray = Dns_CreateIpArray( pIpArray->AddrCount + ExpandCount ); if ( ! pnewArray ) { return( NULL ); } RtlCopyMemory( (PBYTE) pnewArray->AddrArray, (PBYTE) pIpArray->AddrArray, pIpArray->AddrCount * sizeof(IP4_ADDRESS) ); // // delete existing -- for "grow mode" // if ( fDeleteExisting ) { FREE_HEAP( pIpArray ); } return( pnewArray ); } PIP_ARRAY Dns_CreateIpArrayCopy( IN PIP_ARRAY pIpArray ) /*++ Routine Description: Create copy of IP address array. Arguments: pIpArray -- IP address array to copy Return Value: Ptr to IP address array copy, if successful NULL on failure. --*/ { #if 0 PIP_ARRAY pIpArrayCopy; if ( ! pIpArray ) { return( NULL ); } // create IP array of desired size // then copy entire structure pIpArrayCopy = Dns_CreateIpArray( pIpArray->AddrCount ); if ( ! pIpArrayCopy ) { return( NULL ); } memcpy( pIpArrayCopy, pIpArray, Dns_SizeofIpArray(pIpArray) ); return( pIpArrayCopy ); #endif // // call essentially "CopyEx" function // // note, not macroing this because this may well become // a DLL entry point // return Dns_CopyAndExpandIpArray( pIpArray, 0, // no expansion 0 // don't delete existing array ); } BOOL Dns_IsAddressInIpArray( IN PIP_ARRAY pIpArray, IN IP_ADDRESS ipAddress ) /*++ Routine Description: Check if IP array contains desired address. Arguments: pIpArray -- IP address array to copy Return Value: TRUE if address in array. Ptr to IP address array copy, if successful NULL on failure. --*/ { DWORD i; if ( ! pIpArray ) { return( FALSE ); } for (i=0; iAddrCount; i++) { if ( ipAddress == pIpArray->AddrArray[i] ) { return( TRUE ); } } return( FALSE ); } BOOL Dns_AddIpToIpArray( IN OUT PIP_ARRAY pIpArray, IN IP_ADDRESS NewIp ) /*++ Routine Description: Add IP address to IP array. Allowable "slot" in array, is any zero IP address. Arguments: pIpArray -- IP address array to add to NewIp -- IP address to add to array Return Value: TRUE if successful. FALSE if array full. --*/ { DWORD i; // // screen for existence // // this check makes it easy to write code that does // Add\Full?=>Expand loop without having to write // startup existence\create code // if ( !pIpArray ) { return( FALSE ); } for (i=0; iAddrCount; i++) { if ( pIpArray->AddrArray[i] == 0 ) { pIpArray->AddrArray[i] = NewIp; return( TRUE ); } else if ( pIpArray->AddrArray[i] == NewIp ) { return( TRUE ); } } return( FALSE ); } VOID Dns_ClearIpArray( IN OUT PIP_ARRAY pIpArray ) /*++ Routine Description: Clear memory in IP array. Arguments: pIpArray -- IP address array to clear Return Value: None. --*/ { // clear just the address list, leaving count intact RtlZeroMemory( pIpArray->AddrArray, pIpArray->AddrCount * sizeof(IP_ADDRESS) ); } VOID Dns_ReverseOrderOfIpArray( IN OUT PIP_ARRAY pIpArray ) /*++ Routine Description: Reorder the list of IPs in reverse. Arguments: pIpArray -- IP address array to reorder Return Value: None. --*/ { IP_ADDRESS tempIp; DWORD i; DWORD j; // // swap IPs working from ends to the middle // if ( pIpArray && pIpArray->AddrCount ) { for ( i = 0, j = pIpArray->AddrCount - 1; i < j; i++, j-- ) { tempIp = pIpArray->AddrArray[i]; pIpArray->AddrArray[i] = pIpArray->AddrArray[j]; pIpArray->AddrArray[j] = tempIp; } } } BOOL Dns_CheckAndMakeIpArraySubset( IN OUT PIP_ARRAY pIpArraySub, IN PIP_ARRAY pIpArraySuper ) /*++ Routine Description: Clear entries from IP array until it is subset of another IP array. Arguments: pIpArraySub -- IP array to make into subset pIpArraySuper -- IP array superset Return Value: TRUE if pIpArraySub is already subset. FALSE if needed to nix entries to make IP array a subset. --*/ { DWORD i; DWORD newIpCount; // // check each entry in subset IP array, // if not in superset IP array, eliminate it // newIpCount = pIpArraySub->AddrCount; for (i=0; i < newIpCount; i++) { if ( ! Dns_IsAddressInIpArray( pIpArraySuper, pIpArraySub->AddrArray[i] ) ) { // remove this IP entry and replace with // last IP entry in array newIpCount--; if ( i >= newIpCount ) { break; } pIpArraySub->AddrArray[i] = pIpArraySub->AddrArray[ newIpCount ]; } } // if eliminated entries, reset array count if ( newIpCount < pIpArraySub->AddrCount ) { pIpArraySub->AddrCount = newIpCount; return( FALSE ); } return( TRUE ); } INT WINAPI Dns_ClearIpFromIpArray( IN OUT PIP_ARRAY pIpArray, IN IP_ADDRESS IpDelete ) /*++ Routine Description: Clear IP address from IP array. Note difference between this function and Dns_DeleteIpFromIpArray() below. This function leaves list size unchanged allowing new adds. Arguments: pIpArray -- IP address array to add to IpDelete -- IP address to delete from array Return Value: Count of instances of IpDelete in array. --*/ { DWORD found = 0; INT i; INT currentLast; i = currentLast = pIpArray->AddrCount-1; while ( i >= 0 ) { if ( pIpArray->AddrArray[i] == IpDelete ) { pIpArray->AddrArray[i] = pIpArray->AddrArray[ currentLast ]; pIpArray->AddrArray[ currentLast ] = 0; currentLast--; found++; } i--; } return( found ); } INT WINAPI Dns_DeleteIpFromIpArray( IN OUT PIP_ARRAY pIpArray, IN IP_ADDRESS IpDelete ) /*++ Routine Description: Delete IP address from IP array. Note difference between this function and Dns_ClearIpFromIpArray() above. This delete leaves a SMALLER array. The IP slot is NON_RECOVERABLE. Arguments: pIpArray -- IP address array to add to IpDelete -- IP address to delete from array Return Value: Count of instances of IpDelete in array. --*/ { DWORD found; found = Dns_ClearIpFromIpArray( pIpArray, IpDelete ); pIpArray->AddrCount -= found; return( found ); } INT WINAPI Dns_CleanIpArray( IN OUT PIP_ARRAY pIpArray, IN DWORD Flag ) /*++ Routine Description: Clean IP array. Remove bogus stuff from IP Array: -- Zeros -- Loopback -- AutoNet Arguments: pIpArray -- IP address array to add to Flag -- which cleanups to make Return Value: Count of instances cleaned from array. --*/ { DWORD found = 0; INT i; INT currentLast; IP_ADDRESS ip; i = currentLast = pIpArray->AddrCount-1; while ( i >= 0 ) { ip = pIpArray->AddrArray[i]; if ( ( (Flag & DNS_IPARRAY_CLEAN_LOOPBACK) && ip == DNS_NET_ORDER_LOOPBACK ) || ( (Flag & DNS_IPARRAY_CLEAN_ZERO) && ip == 0 ) || ( (Flag & DNS_IPARRAY_CLEAN_AUTONET) && DNS_IS_AUTONET_IP(ip) ) ) { // remove IP from array pIpArray->AddrArray[i] = pIpArray->AddrArray[ currentLast ]; currentLast--; found++; } i--; } pIpArray->AddrCount -= found; return( found ); } DNS_STATUS WINAPI Dns_DiffOfIpArrays( IN PIP_ARRAY pIpArray1, IN PIP_ARRAY pIpArray2, OUT PIP_ARRAY * ppOnlyIn1, OUT PIP_ARRAY * ppOnlyIn2, OUT PIP_ARRAY * ppIntersect ) /*++ Routine Description: Computes differences and intersection of two IP arrays. Out arrays are allocated with Dns_Alloc(), caller must free with Dns_Free() Arguments: pIpArray1 -- IP array pIpArray2 -- IP array ppOnlyIn1 -- addr to recv IP array of addresses only in array 1 (not in array2) ppOnlyIn2 -- addr to recv IP array of addresses only in array 2 (not in array1) ppIntersect -- addr to recv IP array of intersection addresses Return Value: ERROR_SUCCESS if successful. DNS_ERROR_NO_MEMORY if unable to allocate memory for IP arrays. --*/ { DWORD j; DWORD ip; PIP_ARRAY intersectArray = NULL; PIP_ARRAY only1Array = NULL; PIP_ARRAY only2Array = NULL; // // create result IP arrays // if ( ppIntersect ) { intersectArray = Dns_CreateIpArrayCopy( pIpArray1 ); if ( !intersectArray ) { goto NoMem; } *ppIntersect = intersectArray; } if ( ppOnlyIn1 ) { only1Array = Dns_CreateIpArrayCopy( pIpArray1 ); if ( !only1Array ) { goto NoMem; } *ppOnlyIn1 = only1Array; } if ( ppOnlyIn2 ) { only2Array = Dns_CreateIpArrayCopy( pIpArray2 ); if ( !only2Array ) { goto NoMem; } *ppOnlyIn2 = only2Array; } // // clean the arrays // for ( j=0; j< pIpArray1->AddrCount; j++ ) { ip = pIpArray1->AddrArray[j]; // if IP in both arrays, delete from "only" arrays if ( Dns_IsAddressInIpArray( pIpArray2, ip ) ) { if ( only1Array ) { Dns_DeleteIpFromIpArray( only1Array, ip ); } if ( only2Array ) { Dns_DeleteIpFromIpArray( only2Array, ip ); } } // if IP not in both arrays, delete from intersection // note intersection started as IpArray1 else if ( intersectArray ) { Dns_DeleteIpFromIpArray( intersectArray, ip ); } } return( ERROR_SUCCESS ); NoMem: if ( intersectArray ) { FREE_HEAP( intersectArray ); } if ( only1Array ) { FREE_HEAP( only1Array ); } if ( only2Array ) { FREE_HEAP( only2Array ); } if ( ppIntersect ) { *ppIntersect = NULL; } if ( ppOnlyIn1 ) { *ppOnlyIn1 = NULL; } if ( ppOnlyIn2 ) { *ppOnlyIn2 = NULL; } return( DNS_ERROR_NO_MEMORY ); } BOOL WINAPI Dns_IsIntersectionOfIpArrays( IN PIP_ARRAY pIpArray1, IN PIP_ARRAY pIpArray2 ) /*++ Routine Description: Determine if there's intersection of two IP arrays. Arguments: pIpArray1 -- IP array pIpArray2 -- IP array Return Value: TRUE if intersection. FALSE if no intersection or empty or NULL array. --*/ { DWORD count; DWORD j; // // protect against NULL // this is called from the server on potentially changing (reconfigurable) // IP array pointers; this provides cheaper protection than // worrying about locking // if ( !pIpArray1 || !pIpArray2 ) { return( FALSE ); } // // same array // if ( pIpArray1 == pIpArray2 ) { return( TRUE ); } // // test that at least one IP in array 1 is in array 2 // count = pIpArray1->AddrCount; for ( j=0; j < count; j++ ) { if ( Dns_IsAddressInIpArray( pIpArray2, pIpArray1->AddrArray[j] ) ) { return( TRUE ); } } // no intersection return( FALSE ); } BOOL Dns_AreIpArraysEqual( IN PIP_ARRAY pIpArray1, IN PIP_ARRAY pIpArray2 ) /*++ Routine Description: Determines if IP arrays are equal. Arguments: pIpArray1 -- IP array pIpArray2 -- IP array Return Value: TRUE if arrays equal. FALSE otherwise. --*/ { DWORD j; DWORD count; // // same array? or missing array? // if ( pIpArray1 == pIpArray2 ) { return( TRUE ); } if ( !pIpArray1 || !pIpArray2 ) { return( FALSE ); } // // arrays the same length? // count = pIpArray1->AddrCount; if ( count != pIpArray2->AddrCount ) { return( FALSE ); } // // test that each IP in array 1 is in array 2 // // test that each IP in array 2 is in array 1 // - do second test in case of duplicates // that fool equal-lengths check // for ( j=0; j < count; j++ ) { if ( !Dns_IsAddressInIpArray( pIpArray2, pIpArray1->AddrArray[j] ) ) { return( FALSE ); } } for ( j=0; j < count; j++ ) { if ( !Dns_IsAddressInIpArray( pIpArray1, pIpArray2->AddrArray[j] ) ) { return( FALSE ); } } // equal arrays return( TRUE ); } DNS_STATUS WINAPI Dns_UnionOfIpArrays( IN PIP_ARRAY pIpArray1, IN PIP_ARRAY pIpArray2, OUT PIP_ARRAY * ppUnion ) /*++ Routine Description: Computes the union of two IP arrays. Out array is allocated with Dns_Alloc(), caller must free with Dns_Free() Arguments: pIpArray1 -- IP array pIpArray2 -- IP array ppUnion -- addr to recv IP array of addresses in array 1 and in array2 Return Value: ERROR_SUCCESS if successful. DNS_ERROR_NO_MEMORY if unable to allocate memory for IP array. --*/ { DWORD j; DWORD ip; DWORD Count = 0; PIP_ARRAY punionArray = NULL; // // create result IP arrays // if ( !ppUnion ) { return( ERROR_INVALID_PARAMETER ); } punionArray = Dns_CreateIpArray( pIpArray1->AddrCount + pIpArray2->AddrCount ); if ( !punionArray ) { goto NoMem; } *ppUnion = punionArray; // // create union from arrays // for ( j = 0; j < pIpArray1->AddrCount; j++ ) { ip = pIpArray1->AddrArray[j]; if ( !Dns_IsAddressInIpArray( punionArray, ip ) ) { Dns_AddIpToIpArray( punionArray, ip ); Count++; } } for ( j = 0; j < pIpArray2->AddrCount; j++ ) { ip = pIpArray2->AddrArray[j]; if ( !Dns_IsAddressInIpArray( punionArray, ip ) ) { Dns_AddIpToIpArray( punionArray, ip ); Count++; } } punionArray->AddrCount = Count; return( ERROR_SUCCESS ); NoMem: if ( punionArray ) { FREE_HEAP( punionArray ); *ppUnion = NULL; } return( DNS_ERROR_NO_MEMORY ); } DNS_STATUS Dns_CreateIpArrayFromMultiIpString( IN LPSTR pszMultiIpString, OUT PIP_ARRAY * ppIpArray ) /*++ Routine Description: Create IP array out of multi-IP string. Arguments: pszMultiIpString -- string containing IP addresses; separator is whitespace or comma ppIpArray -- addr to receive ptr to allocated IP array Return Value: ERROR_SUCCESS if one or more valid IP addresses in string. DNS_ERROR_INVALID_IP_ADDRESS if parsing error. DNS_ERROR_NO_MEMORY if can not create IP array. --*/ { PCHAR pch; CHAR ch; PCHAR pbuf; PCHAR pbufStop; DNS_STATUS status = ERROR_SUCCESS; DWORD countIp = 0; IP_ADDRESS ip; CHAR buffer[ IP_ADDRESS_STRING_LENGTH+2 ]; IP_ADDRESS arrayIp[ MAX_PARSE_IP ]; // stop byte for IP string buffer // - note we put extra byte pad in buffer above // this allows us to write ON stop byte and use // that to detect invalid-long IP string // pbufStop = buffer + IP_ADDRESS_STRING_LENGTH; // // DCR: use IP array builder for local IP address // then need Dns_CreateIpArrayFromMultiIpString() // to use count\alloc method when buffer overflows // to do this we'd need to do parsing in loop // and skip conversion when count overflow, but set // flag to go back in again with allocated buffer // // safer would be to tokenize-count, alloc, build from tokens // // // loop until reach end of string // pch = pszMultiIpString; while ( countIp < MAX_PARSE_IP ) { // skip whitespace while ( ch = *pch++ ) { if ( ch == ' ' || ch == '\t' || ch == ',' ) { continue; } break; } if ( !ch ) { break; } // // copy next IP string into buffer // - stop copy at whitespace or NULL // - on invalid-long IP string, stop copying // but continue parsing, so can still get any following IPs // note, we actually write ON the buffer stop byte as our // "invalid-long" detection mechanism // pbuf = buffer; do { if ( pbuf <= pbufStop ) { *pbuf++ = ch; } ch = *pch++; } while ( ch && ch != ' ' && ch != ',' && ch != '\t' ); // // convert buffer into IP address // - insure was valid length string // - null terminate // if ( pbuf <= pbufStop ) { *pbuf = 0; ip = inet_addr( buffer ); if ( ip == INADDR_BROADCAST ) { status = DNS_ERROR_INVALID_IP_ADDRESS; } else { arrayIp[ countIp++ ] = ip; } } else { status = DNS_ERROR_INVALID_IP_ADDRESS; } // quit if at end of string if ( !ch ) { break; } } // // if successfully parsed IP addresses, create IP array // note, we'll return what we have even if some addresses are // bogus, status code will indicate the parsing problem // // note, if explicitly passed empty string, then create // empty IP array, don't error // if ( countIp == 0 && *pszMultiIpString != 0 ) { *ppIpArray = NULL; status = DNS_ERROR_INVALID_IP_ADDRESS; } else { *ppIpArray = Dns_BuildIpArray( countIp, arrayIp ); if ( !*ppIpArray ) { status = DNS_ERROR_NO_MEMORY; } IF_DNSDBG( IPARRAY ) { DnsDbg_IpArray( "New Parsed IP array", NULL, // no name *ppIpArray ); } } return( status ); } LPSTR Dns_CreateMultiIpStringFromIpArray( IN PIP_ARRAY pIpArray, IN CHAR chSeparator OPTIONAL ) /*++ Routine Description: Create IP array out of multi-IP string. Arguments: pIpArray -- IP array to generate string for chSeparator -- separating character between strings; OPTIONAL, if not given, blank is used Return Value: Ptr to string representation of IP array. Caller must free. --*/ { PCHAR pch; DWORD i; PCHAR pszip; DWORD length; PCHAR pchstop; CHAR buffer[ IP_ADDRESS_STRING_LENGTH*MAX_PARSE_IP + 1 ]; // // if no IP array, return NULL string // this allows this function to simply indicate when registry // delete rather than write is indicated // if ( !pIpArray ) { return( NULL ); } // if no separator, use blank if ( !chSeparator ) { chSeparator = ' '; } // // loop through all IPs in array, appending each // pch = buffer; pchstop = pch + ( IP_ADDRESS_STRING_LENGTH * (MAX_PARSE_IP-1) ); *pch = 0; for ( i=0; i < pIpArray->AddrCount; i++ ) { if ( pch >= pchstop ) { break; } pszip = IP_STRING( pIpArray->AddrArray[i] ); if ( pszip ) { length = strlen( pszip ); memcpy( pch, pszip, length ); pch += length; *pch++ = chSeparator; } } // if wrote any strings, then write terminator over last separator if ( pch != buffer ) { *--pch = 0; } // create copy of buffer as return length = (DWORD)(pch - buffer) + 1; pch = ALLOCATE_HEAP( length ); if ( !pch ) { return( NULL ); } memcpy( pch, buffer, length ); DNSDBG( IPARRAY, ( "String representation %s of IP array at %p\n", pch, pIpArray )); return( pch ); } // // End iparray.c //