mdnsNSP.c   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>

#include	"ClientCommon.h"
#include	"CommonServices.h"
#include	"DebugServices.h"

#include	<iphlpapi.h>
#include	<guiddef.h>
#include	<ws2spi.h>
#include	<shlwapi.h>



#include	"dns_sd.h"

#pragma comment(lib, "DelayImp.lib")

#ifdef _MSC_VER
#define swprintf _snwprintf
#define snprintf _snprintf
#endif

#define MAX_LABELS 128

#if 0
#pragma mark == Structures ==
#endif

//===========================================================================================================================
//	Structures
//===========================================================================================================================

typedef struct	Query *		QueryRef;
typedef struct	Query		Query;
struct	Query
{
	QueryRef			next;
	int					refCount;
	DWORD				querySetFlags;
	WSAQUERYSETW *		querySet;
	size_t				querySetSize;
	HANDLE				data4Event;
	HANDLE				data6Event;
	HANDLE				cancelEvent;
	HANDLE				waitHandles[ 3 ];
	DWORD				waitCount;
	DNSServiceRef		resolver4;
	DNSServiceRef		resolver6;
	char				name[ kDNSServiceMaxDomainName ];
	size_t				nameSize;
	uint8_t				numValidAddrs;
	uint32_t			addr4;
	bool				addr4Valid;
	uint8_t				addr6[16];
	u_long				addr6ScopeId;
	bool				addr6Valid;
};

#define BUFFER_INITIAL_SIZE		4192
#define ALIASES_INITIAL_SIZE	5

typedef struct HostsFile
{
	int			m_bufferSize;
	char	*	m_buffer;
	FILE	*	m_fp;
} HostsFile;


typedef struct HostsFileInfo
{
	struct hostent		m_host;
	struct HostsFileInfo	*	m_next;
} HostsFileInfo;


#if 0
#pragma mark == Prototypes ==
#endif

//===========================================================================================================================
//	Prototypes
//===========================================================================================================================

// DLL Exports

BOOL WINAPI		DllMain( HINSTANCE inInstance, DWORD inReason, LPVOID inReserved );
STDAPI			DllRegisterServer( void );
STDAPI			DllRegisterServer( void );

	
// NSP SPIs

int	WSPAPI	NSPCleanup( LPGUID inProviderID );

DEBUG_LOCAL int WSPAPI
	NSPLookupServiceBegin(
		LPGUID					inProviderID,
		LPWSAQUERYSETW			inQuerySet,
		LPWSASERVICECLASSINFOW	inServiceClassInfo,
		DWORD					inFlags,   
		LPHANDLE				outLookup );

DEBUG_LOCAL int WSPAPI
	NSPLookupServiceNext(  
		HANDLE			inLookup,
		DWORD			inFlags,
		LPDWORD			ioBufferLength,
		LPWSAQUERYSETW	outResults );

DEBUG_LOCAL int WSPAPI	NSPLookupServiceEnd( HANDLE inLookup );

DEBUG_LOCAL int WSPAPI
	NSPSetService(
		LPGUID					inProviderID,						
		LPWSASERVICECLASSINFOW	inServiceClassInfo,   
		LPWSAQUERYSETW			inRegInfo,				  
		WSAESETSERVICEOP		inOperation,			   
		DWORD					inFlags );

DEBUG_LOCAL int WSPAPI	NSPInstallServiceClass( LPGUID inProviderID, LPWSASERVICECLASSINFOW inServiceClassInfo );
DEBUG_LOCAL int WSPAPI	NSPRemoveServiceClass( LPGUID inProviderID, LPGUID inServiceClassID );
DEBUG_LOCAL int WSPAPI	NSPGetServiceClassInfo(	LPGUID inProviderID, LPDWORD ioBufSize, LPWSASERVICECLASSINFOW ioServiceClassInfo );

// Private

#define	NSPLock()		EnterCriticalSection( &gLock );
#define	NSPUnlock()		LeaveCriticalSection( &gLock );

DEBUG_LOCAL OSStatus	QueryCreate( const WSAQUERYSETW *inQuerySet, DWORD inQuerySetFlags, QueryRef *outRef );
DEBUG_LOCAL OSStatus	QueryRetain( QueryRef inRef );
DEBUG_LOCAL OSStatus	QueryRelease( QueryRef inRef );

DEBUG_LOCAL void CALLBACK_COMPAT
	QueryRecordCallback4(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inInterfaceIndex,
		DNSServiceErrorType	inErrorCode,
		const char *		inName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext );

DEBUG_LOCAL void CALLBACK_COMPAT
	QueryRecordCallback6(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inInterfaceIndex,
		DNSServiceErrorType	inErrorCode,
		const char *		inName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext );

DEBUG_LOCAL OSStatus
	QueryCopyQuerySet( 
		QueryRef 				inRef, 
		const WSAQUERYSETW *	inQuerySet, 
		DWORD 					inQuerySetFlags, 
		WSAQUERYSETW **			outQuerySet, 
		size_t *				outSize );

DEBUG_LOCAL void
	QueryCopyQuerySetTo( 
		QueryRef 				inRef, 
		const WSAQUERYSETW *	inQuerySet, 
		DWORD 					inQuerySetFlags, 
		WSAQUERYSETW *			outQuerySet );

DEBUG_LOCAL size_t	QueryCopyQuerySetSize( QueryRef inRef, const WSAQUERYSETW *inQuerySet, DWORD inQuerySetFlags );

#if( DEBUG )
	void	DebugDumpQuerySet( DebugLevel inLevel, const WSAQUERYSETW *inQuerySet );
	
	#define	dlog_query_set( LEVEL, SET )		DebugDumpQuerySet( LEVEL, SET )
#else
	#define	dlog_query_set( LEVEL, SET )
#endif

DEBUG_LOCAL BOOL		InHostsTable( const char * name );
DEBUG_LOCAL BOOL		IsLocalName( HostsFileInfo * node );
DEBUG_LOCAL BOOL		IsSameName( HostsFileInfo * node, const char * name );
DEBUG_LOCAL OSStatus	HostsFileOpen( HostsFile ** self, const char * fname );
DEBUG_LOCAL OSStatus	HostsFileClose( HostsFile * self );
DEBUG_LOCAL void		HostsFileInfoFree( HostsFileInfo * info );
DEBUG_LOCAL OSStatus	HostsFileNext( HostsFile * self, HostsFileInfo ** hInfo );
DEBUG_LOCAL DWORD		GetScopeId( DWORD ifIndex );

#ifdef ENABLE_REVERSE_LOOKUP
DEBUG_LOCAL OSStatus	IsReverseLookup( LPCWSTR name, size_t size );
#endif


#if 0
#pragma mark == Globals ==
#endif

//===========================================================================================================================
//	Globals
//===========================================================================================================================

// {B600E6E9-553B-4a19-8696-335E5C896153}
DEBUG_LOCAL HINSTANCE				gInstance			= NULL;
DEBUG_LOCAL wchar_t				*	gNSPName			= L"mdnsNSP";
DEBUG_LOCAL GUID					gNSPGUID			= { 0xb600e6e9, 0x553b, 0x4a19, { 0x86, 0x96, 0x33, 0x5e, 0x5c, 0x89, 0x61, 0x53 } };
DEBUG_LOCAL LONG					gRefCount			= 0;
DEBUG_LOCAL CRITICAL_SECTION		gLock;
DEBUG_LOCAL bool					gLockInitialized 	= false;
DEBUG_LOCAL QueryRef				gQueryList	 		= NULL;
DEBUG_LOCAL HostsFileInfo		*	gHostsFileInfo		= NULL;
typedef DWORD
	( WINAPI * GetAdaptersAddressesFunctionPtr )( 
			ULONG 					inFamily, 
			DWORD 					inFlags, 
			PVOID 					inReserved, 
			PIP_ADAPTER_ADDRESSES 	inAdapter, 
			PULONG					outBufferSize );

DEBUG_LOCAL HMODULE								gIPHelperLibraryInstance			= NULL;
DEBUG_LOCAL GetAdaptersAddressesFunctionPtr		gGetAdaptersAddressesFunctionPtr	= NULL;



#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	DllMain
//===========================================================================================================================

BOOL APIENTRY	DllMain( HINSTANCE inInstance, DWORD inReason, LPVOID inReserved )
{
	DEBUG_USE_ONLY( inInstance );
	DEBUG_UNUSED( inReserved );

	switch( inReason )
	{
		case DLL_PROCESS_ATTACH:			
			gInstance = inInstance;		
			gHostsFileInfo	= NULL;
			debug_initialize( kDebugOutputTypeWindowsEventLog, "mDNS NSP", inInstance );
			debug_set_property( kDebugPropertyTagPrintLevel, kDebugLevelNotice );
			dlog( kDebugLevelTrace, "\n" );
			dlog( kDebugLevelVerbose, "%s: process attach\n", __ROUTINE__ );

			break;
		
		case DLL_PROCESS_DETACH:
			HostsFileInfoFree( gHostsFileInfo );
			gHostsFileInfo = NULL;
			dlog( kDebugLevelVerbose, "%s: process detach\n", __ROUTINE__ );
			break;
		
		case DLL_THREAD_ATTACH:
			dlog( kDebugLevelVerbose, "%s: thread attach\n", __ROUTINE__ );
			break;
		
		case DLL_THREAD_DETACH:
			dlog( kDebugLevelVerbose, "%s: thread detach\n", __ROUTINE__ );
			break;
		
		default:
			dlog( kDebugLevelNotice, "%s: unknown reason code (%d)\n", __ROUTINE__, inReason );
			break;
	}

	return( TRUE );
}


//===========================================================================================================================
//	DllRegisterServer
//===========================================================================================================================

STDAPI	DllRegisterServer( void )
{
	WSADATA		wsd;
	WCHAR		path[ MAX_PATH ];
	HRESULT		err;
	
	dlog( kDebugLevelTrace, "DllRegisterServer\n" );

	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );

	// Unregister before registering to workaround an installer
	// problem during upgrade installs.

	WSCUnInstallNameSpace( &gNSPGUID );

	err = GetModuleFileNameW( gInstance, path, MAX_PATH );
	err = translate_errno( err != 0, errno_compat(), kUnknownErr );
	require_noerr( err, exit );

	err = WSCInstallNameSpace( gNSPName, path, NS_DNS, 1, &gNSPGUID );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
exit:

	WSACleanup();
	return( err );
}

//===========================================================================================================================
//	DllUnregisterServer
//===========================================================================================================================

STDAPI	DllUnregisterServer( void )
{
	WSADATA		wsd;
	HRESULT err;
	
	dlog( kDebugLevelTrace, "DllUnregisterServer\n" );
	
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
	err = WSCUnInstallNameSpace( &gNSPGUID );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
		
exit:

	WSACleanup();
	return err;
}


//===========================================================================================================================
//	NSPStartup
//
//	This function is called when our namespace DLL is loaded. It sets up the NSP functions we implement and initializes us.
//===========================================================================================================================

int WSPAPI	NSPStartup( LPGUID inProviderID, LPNSP_ROUTINE outRoutines )
{
	OSStatus		err;
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s (GUID=%U, refCount=%ld)\n", __ROUTINE__, inProviderID, gRefCount );
	
	// Only initialize if this is the first time NSPStartup is called. 
	
	if( InterlockedIncrement( &gRefCount ) != 1 )
	{
		err = NO_ERROR;
		goto exit;
	}
	
	// Initialize our internal state.
	
	InitializeCriticalSection( &gLock );
	gLockInitialized = true;
	
	// Set the size to exclude NSPIoctl because we don't implement it.
	
	outRoutines->cbSize					= FIELD_OFFSET( NSP_ROUTINE, NSPIoctl );
	outRoutines->dwMajorVersion			= 4;
	outRoutines->dwMinorVersion			= 4;
	outRoutines->NSPCleanup				= NSPCleanup;
	outRoutines->NSPLookupServiceBegin	= NSPLookupServiceBegin;
	outRoutines->NSPLookupServiceNext	= NSPLookupServiceNext;
	outRoutines->NSPLookupServiceEnd	= NSPLookupServiceEnd;
	outRoutines->NSPSetService			= NSPSetService;
	outRoutines->NSPInstallServiceClass	= NSPInstallServiceClass;
	outRoutines->NSPRemoveServiceClass	= NSPRemoveServiceClass;
	outRoutines->NSPGetServiceClassInfo	= NSPGetServiceClassInfo;
	
	// See if we can get the address for the GetAdaptersAddresses() API.  This is only in XP, but we want our
	// code to run on older versions of Windows

	if ( !gIPHelperLibraryInstance )
	{
		gIPHelperLibraryInstance = LoadLibrary( TEXT( "Iphlpapi" ) );
		if( gIPHelperLibraryInstance )
		{
			gGetAdaptersAddressesFunctionPtr = (GetAdaptersAddressesFunctionPtr) GetProcAddress( gIPHelperLibraryInstance, "GetAdaptersAddresses" );
		}
	}

	err = NO_ERROR;
	
exit:
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	if( err != NO_ERROR )
	{
		NSPCleanup( inProviderID );
		SetLastError( (DWORD) err );
		return( SOCKET_ERROR );
	}
	return( NO_ERROR );
}

//===========================================================================================================================
//	NSPCleanup
//
//	This function is called when our namespace DLL is unloaded. It cleans up anything we set up in NSPStartup.
//===========================================================================================================================

int	WSPAPI	NSPCleanup( LPGUID inProviderID )
{
	DEBUG_USE_ONLY( inProviderID );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s (GUID=%U, refCount=%ld)\n", __ROUTINE__, inProviderID, gRefCount );
	
	// Only initialize if this is the first time NSPStartup is called.
	
	if( InterlockedDecrement( &gRefCount ) != 0 )
	{
		goto exit;
	}
	
	// Stop any outstanding queries.
	
	if( gLockInitialized )
	{
		NSPLock();
	}
	while( gQueryList )
	{
		check_string( gQueryList->refCount == 1, "NSPCleanup with outstanding queries!" );
		QueryRelease( gQueryList );
	}
	if( gLockInitialized )
	{
		NSPUnlock();
	}
	
	if( gLockInitialized )
	{
		gLockInitialized = false;
		DeleteCriticalSection( &gLock );
	}

	if( gIPHelperLibraryInstance )
	{
		BOOL ok;
				
		ok = FreeLibrary( gIPHelperLibraryInstance );
		check_translated_errno( ok, GetLastError(), kUnknownErr );
		gIPHelperLibraryInstance = NULL;
	}
	
exit:
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	return( NO_ERROR );
}

//===========================================================================================================================
//	NSPLookupServiceBegin
//
//	This function maps to the WinSock WSALookupServiceBegin function. It starts the lookup process and returns a HANDLE 
//	that can be used in subsequent operations. Subsequent calls only need to refer to this query by the handle as 
//	opposed to specifying the query parameters each time.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI
	NSPLookupServiceBegin(
		LPGUID					inProviderID,
		LPWSAQUERYSETW			inQuerySet,
		LPWSASERVICECLASSINFOW	inServiceClassInfo,
		DWORD					inFlags,   
		LPHANDLE				outLookup )
{
	OSStatus		err;
	QueryRef		obj;
	LPCWSTR			name;
	size_t			size;
	LPCWSTR			p;
	DWORD           type;
	DWORD			n;
	DWORD			i;
	INT				family;
	INT				protocol;
	
	DEBUG_UNUSED( inProviderID );
	DEBUG_UNUSED( inServiceClassInfo );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	
	obj = NULL;
	require_action( inQuerySet, exit, err = WSAEINVAL );
	name = inQuerySet->lpszServiceInstanceName;
	require_action_quiet( name, exit, err = WSAEINVAL );
	require_action( outLookup, exit, err = WSAEINVAL );
	
	dlog( kDebugLevelTrace, "%s (flags=0x%08X, name=\"%S\")\n", __ROUTINE__, inFlags, name );
	dlog_query_set( kDebugLevelVerbose, inQuerySet );
	
	// Check if we can handle this type of request and if we support any of the protocols being requested.
	// We only support the DNS namespace, TCP and UDP protocols, and IPv4. Only blob results are supported.
	
	require_action_quiet( inFlags & (LUP_RETURN_ADDR|LUP_RETURN_BLOB), exit, err = WSASERVICE_NOT_FOUND );
	
	type = inQuerySet->dwNameSpace;
	require_action_quiet( ( type == NS_DNS ) || ( type == NS_ALL ), exit, err = WSASERVICE_NOT_FOUND );
	
	n = inQuerySet->dwNumberOfProtocols;
	if( n > 0 )
	{
		require_action( inQuerySet->lpafpProtocols, exit, err = WSAEINVAL );
		for( i = 0; i < n; ++i )
		{
			family = inQuerySet->lpafpProtocols[ i ].iAddressFamily;
			protocol = inQuerySet->lpafpProtocols[ i ].iProtocol;
			if( ( family == AF_INET ) && ( ( protocol == IPPROTO_UDP ) || ( protocol == IPPROTO_TCP ) ) )
			{
				break;
			}
		}
		require_action_quiet( i < n, exit, err = WSASERVICE_NOT_FOUND );
	}
	
	// Check if the name ends in ".local" and if not, exit with an error since we only resolve .local names.
	// The name may or may not end with a "." (fully qualified) so handle both cases. DNS is also case 
	// insensitive the check for .local has to be case insensitive (.LoCaL is equivalent to .local). This
	// manually does the wchar_t strlen and stricmp to avoid needing any special wchar_t versions of the 
	// libraries. It is probably faster to do the inline compare than invoke functions to do it anyway.
	
	for( p = name; *p; ++p ) {}		// Find end of string
	size = (size_t)( p - name );
	require_action_quiet( size > sizeof_string( ".local" ), exit, err = WSASERVICE_NOT_FOUND );
	
	p = name + ( size - 1 );
	p = ( *p == '.' ) ? ( p - sizeof_string( ".local" ) ) : ( ( p - sizeof_string( ".local" ) ) + 1 );
	if	( ( ( p[ 0 ] != '.' )						||
		( ( p[ 1 ] != 'L' ) && ( p[ 1 ] != 'l' ) )	||
		( ( p[ 2 ] != 'O' ) && ( p[ 2 ] != 'o' ) )	||
		( ( p[ 3 ] != 'C' ) && ( p[ 3 ] != 'c' ) )	||
		( ( p[ 4 ] != 'A' ) && ( p[ 4 ] != 'a' ) )	||
		( ( p[ 5 ] != 'L' ) && ( p[ 5 ] != 'l' ) ) ) )
	{
#ifdef ENABLE_REVERSE_LOOKUP

		err = IsReverseLookup( name, size );

#else

		err = WSASERVICE_NOT_FOUND;

#endif

		require_noerr( err, exit );
	}
	else
	{
		const char	*	replyDomain;
		char			translated[ kDNSServiceMaxDomainName ];
		int				n;
		int				labels		= 0;
		const char	*	label[MAX_LABELS];
		char			text[64];

		n = WideCharToMultiByte( CP_UTF8, 0, name, -1, translated, sizeof( translated ), NULL, NULL );
		require_action( n > 0, exit, err = WSASERVICE_NOT_FOUND );

		// <rdar://problem/4050633>

		// Don't resolve multi-label name

		// <rdar://problem/5914160> Eliminate use of GetNextLabel in mdnsNSP
		// Add checks for GetNextLabel returning NULL, individual labels being greater than
		// 64 bytes, and the number of labels being greater than MAX_LABELS
		replyDomain = translated;

		while (replyDomain && *replyDomain && labels < MAX_LABELS)
		{
			label[labels++]	= replyDomain;
			replyDomain		= GetNextLabel(replyDomain, text);
		}

		require_action( labels == 2, exit, err = WSASERVICE_NOT_FOUND );

		// <rdar://problem/3936771>
		//
		// Check to see if the name of this host is in the hosts table. If so,
		// don't try and resolve it
		
		require_action( InHostsTable( translated ) == FALSE, exit, err = WSASERVICE_NOT_FOUND );
	}

	// The name ends in .local ( and isn't in the hosts table ), .0.8.e.f.ip6.arpa, or .254.169.in-addr.arpa so start the resolve operation. Lazy initialize DNS-SD if needed.
		
	NSPLock();
	
	err = QueryCreate( inQuerySet, inFlags, &obj );
	NSPUnlock();
	require_noerr( err, exit );
	
	*outLookup = (HANDLE) obj;
	
exit:
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	if( err != NO_ERROR )
	{
		SetLastError( (DWORD) err );
		return( SOCKET_ERROR );
	}
	return( NO_ERROR );
}

//===========================================================================================================================
//	NSPLookupServiceNext
//
//	This function maps to the Winsock call WSALookupServiceNext. This routine takes a handle to a previously defined 
//	query and attempts to locate a service matching the criteria defined by the query. If so, that instance is returned 
//	in the lpqsResults parameter.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI
	NSPLookupServiceNext(  
		HANDLE			inLookup,
		DWORD			inFlags,
		LPDWORD			ioSize,
		LPWSAQUERYSETW	outResults )
{
	BOOL			data4;
	BOOL			data6;
	OSStatus		err;
	QueryRef		obj;
	DWORD			waitResult;
	size_t			size;
	
	DEBUG_USE_ONLY( inFlags );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	
	data4 = FALSE;
	data6 = FALSE;
	obj = NULL;
	NSPLock();
	err = QueryRetain( (QueryRef) inLookup );
	require_noerr( err, exit );
	obj = (QueryRef) inLookup;
	require_action( ioSize, exit, err = WSAEINVAL );
	require_action( outResults, exit, err = WSAEINVAL );
	
	dlog( kDebugLevelTrace, "%s (lookup=%#p, flags=0x%08X, *ioSize=%d)\n", __ROUTINE__, inLookup, inFlags, *ioSize );
	
	// Wait for data or a cancel. Release the lock while waiting. This is safe because we've retained the query.

	NSPUnlock();
	waitResult = WaitForMultipleObjects( obj->waitCount, obj->waitHandles, FALSE, 2 * 1000 );
	NSPLock();
	require_action_quiet( waitResult != ( WAIT_OBJECT_0 ), exit, err = WSA_E_CANCELLED );
	err = translate_errno( ( waitResult == WAIT_OBJECT_0 + 1 ) || ( waitResult == WAIT_OBJECT_0 + 2 ), (OSStatus) GetLastError(), WSASERVICE_NOT_FOUND );
	require_noerr_quiet( err, exit );

	// If we've received an IPv4 reply, then hang out briefly for an IPv6 reply

	if ( waitResult == WAIT_OBJECT_0 + 1 )
	{
		data4 = TRUE;
		data6 = WaitForSingleObject( obj->data6Event, 100 ) == WAIT_OBJECT_0 ? TRUE : FALSE;
	}

	// Else we've received an IPv6 reply, so hang out briefly for an IPv4 reply

	else if ( waitResult == WAIT_OBJECT_0 + 2 )
	{
		data4 = WaitForSingleObject( obj->data4Event, 100 ) == WAIT_OBJECT_0 ? TRUE : FALSE;
		data6 = TRUE;
	}

	if ( data4 )
	{
		__try
		{
			err = DNSServiceProcessResult(obj->resolver4);
		}
		__except( EXCEPTION_EXECUTE_HANDLER )
		{
			err = kUnknownErr;
		}

		require_noerr( err, exit );
	}

	if ( data6 )
	{
		__try
		{
			err = DNSServiceProcessResult( obj->resolver6 );
		}
		__except( EXCEPTION_EXECUTE_HANDLER )
		{
			err = kUnknownErr;
		}

		require_noerr( err, exit );
	}

	require_action_quiet( obj->addr4Valid || obj->addr6Valid, exit, err = WSA_E_NO_MORE );

	// Copy the externalized query results to the callers buffer (if it fits).
	
	size = QueryCopyQuerySetSize( obj, obj->querySet, obj->querySetFlags );
	require_action( size <= (size_t) *ioSize, exit, err = WSAEFAULT );
	
	QueryCopyQuerySetTo( obj, obj->querySet, obj->querySetFlags, outResults );
	outResults->dwOutputFlags = RESULT_IS_ADDED;
	obj->addr4Valid = false;
	obj->addr6Valid = false;

exit:
	if( obj )
	{
		QueryRelease( obj );
	}
	NSPUnlock();
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	if( err != NO_ERROR )
	{
		SetLastError( (DWORD) err );
		return( SOCKET_ERROR );
	}
	return( NO_ERROR );
}

//===========================================================================================================================
//	NSPLookupServiceEnd
//
//	This function maps to the Winsock call WSALookupServiceEnd. Once the user process has finished is query (usually 
//	indicated when WSALookupServiceNext returns the error WSA_E_NO_MORE) a call to this function is made to release any 
//	allocated resources associated with the query.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI	NSPLookupServiceEnd( HANDLE inLookup )
{
	OSStatus		err;

	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	
	dlog( kDebugLevelTrace, "%s (lookup=%#p)\n", __ROUTINE__, inLookup );
	
	NSPLock();
	err = QueryRelease( (QueryRef) inLookup );
	NSPUnlock();
	require_noerr( err, exit );
	
exit:
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	if( err != NO_ERROR )
	{
		SetLastError( (DWORD) err );
		return( SOCKET_ERROR );
	}
	return( NO_ERROR );
}

//===========================================================================================================================
//	NSPSetService
//
//	This function maps to the Winsock call WSASetService. This routine is called when the user wants to register or 
//	deregister an instance of a server with our service. For registration, the user needs to associate the server with a 
//	service class. For deregistration the service class is required along with the servicename. The inRegInfo parameter 
//	contains a WSAQUERYSET structure defining the server (such as protocol and address where it is).
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI
	NSPSetService(
		LPGUID					inProviderID,						
		LPWSASERVICECLASSINFOW	inServiceClassInfo,   
		LPWSAQUERYSETW			inRegInfo,				  
		WSAESETSERVICEOP		inOperation,			   
		DWORD					inFlags )
{
	DEBUG_UNUSED( inProviderID );
	DEBUG_UNUSED( inServiceClassInfo );
	DEBUG_UNUSED( inRegInfo );
	DEBUG_UNUSED( inOperation );
	DEBUG_UNUSED( inFlags );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s\n", __ROUTINE__ );
	
	// We don't allow services to be registered so always return an error.
	
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	return( WSAEINVAL );
}

//===========================================================================================================================
//	NSPInstallServiceClass
//
//	This function maps to the Winsock call WSAInstallServiceClass. This routine is used to install a service class which 
//	is used to define certain characteristics for a group of services. After a service class is registered, an actual
//	instance of a server may be registered.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI	NSPInstallServiceClass( LPGUID inProviderID, LPWSASERVICECLASSINFOW inServiceClassInfo )
{
	DEBUG_UNUSED( inProviderID );
	DEBUG_UNUSED( inServiceClassInfo );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s\n", __ROUTINE__ );
	
	// We don't allow service classes to be installed so always return an error.

	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	return( WSA_INVALID_PARAMETER );
}

//===========================================================================================================================
//	NSPRemoveServiceClass
//
//	This function maps to the Winsock call WSARemoveServiceClass. This routine removes a previously registered service 
//	class. This is accomplished by connecting to the namespace service and writing the GUID which defines the given 
//	service class.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI	NSPRemoveServiceClass( LPGUID inProviderID, LPGUID inServiceClassID )
{
	DEBUG_UNUSED( inProviderID );
	DEBUG_UNUSED( inServiceClassID );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s\n", __ROUTINE__ );
	
	// We don't allow service classes to be installed so always return an error.
	
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	return( WSATYPE_NOT_FOUND );
}

//===========================================================================================================================
//	NSPGetServiceClassInfo
//
//	This function maps to the Winsock call WSAGetServiceClassInfo. This routine returns the information associated with 
//	a given service class.
//===========================================================================================================================

DEBUG_LOCAL int WSPAPI	NSPGetServiceClassInfo(	LPGUID inProviderID, LPDWORD ioSize, LPWSASERVICECLASSINFOW ioServiceClassInfo )
{
	DEBUG_UNUSED( inProviderID );
	DEBUG_UNUSED( ioSize );
	DEBUG_UNUSED( ioServiceClassInfo );
	
	dlog( kDebugLevelTrace, "%s begin (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	dlog( kDebugLevelTrace, "%s\n", __ROUTINE__ );
	
	// We don't allow service classes to be installed so always return an error.
	
	dlog( kDebugLevelTrace, "%s end   (ticks=%d)\n", __ROUTINE__, GetTickCount() );
	return( WSATYPE_NOT_FOUND );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	QueryCreate
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL OSStatus	QueryCreate( const WSAQUERYSETW *inQuerySet, DWORD inQuerySetFlags, QueryRef *outRef )
{
	OSStatus		err;
	QueryRef		obj;
	char			name[ kDNSServiceMaxDomainName ];
	int				n;
	QueryRef *		p;
	SOCKET			s4;
	SOCKET			s6;

	obj = NULL;
	check( inQuerySet );
	check( inQuerySet->lpszServiceInstanceName );
	check( outRef );
	
	// Convert the wchar_t name to UTF-8.
	
	n = WideCharToMultiByte( CP_UTF8, 0, inQuerySet->lpszServiceInstanceName, -1, name, sizeof( name ), NULL, NULL );
	err = translate_errno( n > 0, (OSStatus) GetLastError(), WSAEINVAL );
	require_noerr( err, exit );
	
	// Allocate the object and append it to the list. Append immediately so releases of partial objects work.
	
	obj = (QueryRef) calloc( 1, sizeof( *obj ) );
	require_action( obj, exit, err = WSA_NOT_ENOUGH_MEMORY );
	
	obj->refCount = 1;
	
	for( p = &gQueryList; *p; p = &( *p )->next ) {}	// Find the end of the list.
	*p = obj;
	
	// Set up cancel event

	obj->cancelEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
	require_action( obj->cancelEvent, exit, err = WSA_NOT_ENOUGH_MEMORY );

	// Set up events to signal when A record data is ready
	
	obj->data4Event = CreateEvent( NULL, TRUE, FALSE, NULL );
	require_action( obj->data4Event, exit, err = WSA_NOT_ENOUGH_MEMORY );
	
	// Start the query.  Handle delay loaded DLL errors.

	__try
	{
		err = DNSServiceQueryRecord( &obj->resolver4, 0, 0, name, kDNSServiceType_A, kDNSServiceClass_IN, QueryRecordCallback4, obj );
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		err = kUnknownErr;
	}

	require_noerr( err, exit );

	// Attach the socket to the event

	__try
	{
		s4 = DNSServiceRefSockFD(obj->resolver4);
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		s4 = INVALID_SOCKET;
	}

	err = translate_errno( s4 != INVALID_SOCKET, errno_compat(), kUnknownErr );
	require_noerr( err, exit );

	WSAEventSelect(s4, obj->data4Event, FD_READ|FD_CLOSE);
	
	// Set up events to signal when AAAA record data is ready
	
	obj->data6Event = CreateEvent( NULL, TRUE, FALSE, NULL );
	require_action( obj->data6Event, exit, err = WSA_NOT_ENOUGH_MEMORY );
	
	// Start the query.  Handle delay loaded DLL errors.

	__try
	{
		err = DNSServiceQueryRecord( &obj->resolver6, 0, 0, name, kDNSServiceType_AAAA, kDNSServiceClass_IN, QueryRecordCallback6, obj );
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		err = kUnknownErr;
	}

	require_noerr( err, exit );

	// Attach the socket to the event

	__try
	{
		s6 = DNSServiceRefSockFD(obj->resolver6);
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		s6 = INVALID_SOCKET;
	}

	err = translate_errno( s6 != INVALID_SOCKET, errno_compat(), kUnknownErr );
	require_noerr( err, exit );

	WSAEventSelect(s6, obj->data6Event, FD_READ|FD_CLOSE);

	obj->waitCount = 0;
	obj->waitHandles[ obj->waitCount++ ] = obj->cancelEvent;
	obj->waitHandles[ obj->waitCount++ ] = obj->data4Event;
	obj->waitHandles[ obj->waitCount++ ] = obj->data6Event;
	
	check( obj->waitCount == sizeof_array( obj->waitHandles ) );
	
	// Copy the QuerySet so it can be returned later.
	
	obj->querySetFlags = inQuerySetFlags;
	inQuerySetFlags = ( inQuerySetFlags & ~( LUP_RETURN_ADDR | LUP_RETURN_BLOB ) ) | LUP_RETURN_NAME;
	err = QueryCopyQuerySet( obj, inQuerySet, inQuerySetFlags, &obj->querySet, &obj->querySetSize );
	require_noerr( err, exit );
	
	// Success!
	
	*outRef	= obj;
	obj 	= NULL;
	err 	= NO_ERROR;

exit:
	if( obj )
	{
		QueryRelease( obj );
	}
	return( err );
}

//===========================================================================================================================
//	QueryRetain
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL OSStatus	QueryRetain( QueryRef inRef )
{
	OSStatus		err;
	QueryRef		obj;
	
	for( obj = gQueryList; obj; obj = obj->next )
	{
		if( obj == inRef )
		{
			break;
		}
	}
	require_action( obj, exit, err = WSA_INVALID_HANDLE );
	
	++inRef->refCount;
	err = NO_ERROR;
	
exit:
	return( err );
}

//===========================================================================================================================
//	QueryRelease
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL OSStatus	QueryRelease( QueryRef inRef )
{
	OSStatus		err;
	QueryRef *		p;
	BOOL			ok;
		
	// Find the item in the list.
	
	for( p = &gQueryList; *p; p = &( *p )->next )
	{
		if( *p == inRef )
		{
			break;
		}
	}
	require_action( *p, exit, err = WSA_INVALID_HANDLE );
	
	// Signal a cancel to unblock any threads waiting for results.
	
	if( inRef->cancelEvent )
	{
		ok = SetEvent( inRef->cancelEvent );
		check_translated_errno( ok, GetLastError(), WSAEINVAL );
	}
	
	// Stop the query.
	
	if( inRef->resolver4 )
	{
		__try
		{
			DNSServiceRefDeallocate( inRef->resolver4 );
		}
		__except( EXCEPTION_EXECUTE_HANDLER )
		{
		}
		
		inRef->resolver4 = NULL;
	}

	if ( inRef->resolver6 )
	{
		__try
		{
			DNSServiceRefDeallocate( inRef->resolver6 );
		}
		__except( EXCEPTION_EXECUTE_HANDLER )
		{
		}

		inRef->resolver6 = NULL;
	}
	
	// Decrement the refCount. Fully release if it drops to 0. If still referenced, just exit.
	
	if( --inRef->refCount != 0 )
	{
		err = NO_ERROR;
		goto exit;
	}
	*p = inRef->next;
	
	// Release resources.
	
	if( inRef->cancelEvent )
	{
		ok = CloseHandle( inRef->cancelEvent );
		check_translated_errno( ok, GetLastError(), WSAEINVAL );
	}
	if( inRef->data4Event )
	{
		ok = CloseHandle( inRef->data4Event );
		check_translated_errno( ok, GetLastError(), WSAEINVAL );
	}
	if( inRef->data6Event )
	{
		ok = CloseHandle( inRef->data6Event );
		check_translated_errno( ok, GetLastError(), WSAEINVAL );
	}
	if( inRef->querySet )
	{
		free( inRef->querySet );
	}
	free( inRef );
	err = NO_ERROR;
	
exit:
	return( err );
}

//===========================================================================================================================
//	QueryRecordCallback4
//===========================================================================================================================

DEBUG_LOCAL void CALLBACK_COMPAT
	QueryRecordCallback4(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inInterfaceIndex,
		DNSServiceErrorType	inErrorCode,
		const char *		inName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext )
{
	QueryRef			obj;
	const char *		src;
	char *				dst;
	BOOL				ok;
	
	DEBUG_UNUSED( inFlags );
	DEBUG_UNUSED( inInterfaceIndex );
	DEBUG_UNUSED( inTTL );

	NSPLock();
	obj = (QueryRef) inContext;
	check( obj );
	require_noerr( inErrorCode, exit );
	require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
	require( inRRClass   == kDNSServiceClass_IN, exit );
	require( inRRType    == kDNSServiceType_A, exit );
	require( inRDataSize == 4, exit );
	
	dlog( kDebugLevelTrace, "%s (flags=0x%08X, name=%s, rrType=%d, rDataSize=%d)\n", 
		__ROUTINE__, inFlags, inName, inRRType, inRDataSize );
		
	// Copy the name if needed.
	
	if( obj->name[ 0 ] == '\0' )
	{
		src = inName;
		dst = obj->name;
		while( *src != '\0' )
		{
			*dst++ = *src++;
		}
		*dst = '\0';
		obj->nameSize = (size_t)( dst - obj->name );
		check( obj->nameSize < sizeof( obj->name ) );
	}
	
	// Copy the data.
	
	memcpy( &obj->addr4, inRData, inRDataSize );
	obj->addr4Valid = true;
	obj->numValidAddrs++;
	
	// Signal that a result is ready.
	
	check( obj->data4Event );
	ok = SetEvent( obj->data4Event );
	check_translated_errno( ok, GetLastError(), WSAEINVAL );
	
	// Stop the resolver after the first response.
	
	__try
	{
		DNSServiceRefDeallocate( inRef );
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
	}

	obj->resolver4 = NULL;

exit:
	NSPUnlock();
}

#if 0
#pragma mark -
#endif


//===========================================================================================================================
//	QueryRecordCallback6
//===========================================================================================================================

DEBUG_LOCAL void CALLBACK_COMPAT
	QueryRecordCallback6(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inInterfaceIndex,
		DNSServiceErrorType	inErrorCode,
		const char *		inName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext )
{
	QueryRef			obj;
	const char *		src;
	char *				dst;
	BOOL				ok;
	
	DEBUG_UNUSED( inFlags );
	DEBUG_UNUSED( inInterfaceIndex );
	DEBUG_UNUSED( inTTL );

	NSPLock();
	obj = (QueryRef) inContext;
	check( obj );
	require_noerr( inErrorCode, exit );
	require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
	require( inRRClass   == kDNSServiceClass_IN, exit );
	require( inRRType    == kDNSServiceType_AAAA, exit );
	require( inRDataSize == 16, exit );
	
	dlog( kDebugLevelTrace, "%s (flags=0x%08X, name=%s, rrType=%d, rDataSize=%d)\n", 
		__ROUTINE__, inFlags, inName, inRRType, inRDataSize );

	// Copy the name if needed.
	
	if( obj->name[ 0 ] == '\0' )
	{
		src = inName;
		dst = obj->name;
		while( *src != '\0' )
		{
			*dst++ = *src++;
		}
		*dst = '\0';
		obj->nameSize = (size_t)( dst - obj->name );
		check( obj->nameSize < sizeof( obj->name ) );
	}
	
	// Copy the data.
	
	memcpy( &obj->addr6, inRData, inRDataSize );

	obj->addr6ScopeId = GetScopeId( inInterfaceIndex );
	require( obj->addr6ScopeId, exit );
	obj->addr6Valid	  = true;
	obj->numValidAddrs++;

	// Signal that we're done
	
	check( obj->data6Event );
	ok = SetEvent( obj->data6Event );
	check_translated_errno( ok, GetLastError(), WSAEINVAL );

	// Stop the resolver after the first response.
	
	__try
	{
		DNSServiceRefDeallocate( inRef );
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
	}

	obj->resolver6 = NULL;

exit:

	
	
	NSPUnlock();
}


//===========================================================================================================================
//	QueryCopyQuerySet
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL OSStatus
	QueryCopyQuerySet( 
		QueryRef 				inRef, 
		const WSAQUERYSETW *	inQuerySet, 
		DWORD 					inQuerySetFlags, 
		WSAQUERYSETW **			outQuerySet, 
		size_t *				outSize )
{
	OSStatus			err;
	size_t				size;
	WSAQUERYSETW *		qs;
	
	check( inQuerySet );
	check( outQuerySet );
	
	size  = QueryCopyQuerySetSize( inRef, inQuerySet, inQuerySetFlags );
	qs = (WSAQUERYSETW *) calloc( 1, size );
	require_action( qs, exit, err = WSA_NOT_ENOUGH_MEMORY  );
	
	QueryCopyQuerySetTo( inRef, inQuerySet, inQuerySetFlags, qs );
	
	*outQuerySet = qs;
	if( outSize )
	{
		*outSize = size;
	}
	qs = NULL;
	err = NO_ERROR;
	
exit:
	if( qs )
	{
		free( qs );
	}
	return( err );	
}



//===========================================================================================================================
//	QueryCopyQuerySetTo
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL void
	QueryCopyQuerySetTo( 
		QueryRef 				inRef, 
		const WSAQUERYSETW *	inQuerySet, 
		DWORD 					inQuerySetFlags, 
		WSAQUERYSETW *			outQuerySet )
{
	uint8_t *		dst;
	LPCWSTR			s;
	LPWSTR			q;
	DWORD			n;
	DWORD			i;
	
#if( DEBUG )
	size_t			debugSize;
	
	debugSize = QueryCopyQuerySetSize( inRef, inQuerySet, inQuerySetFlags );
#endif

	check( inQuerySet );
	check( outQuerySet );

	dst = (uint8_t *) outQuerySet;
	
	// Copy the static portion of the results.
	
	*outQuerySet = *inQuerySet;
	dst += sizeof( *inQuerySet );
	
	if( inQuerySetFlags & LUP_RETURN_NAME )
	{
		s = inQuerySet->lpszServiceInstanceName;
		if( s )
		{
			outQuerySet->lpszServiceInstanceName = (LPWSTR) dst;
			q = (LPWSTR) dst;
			while( ( *q++ = *s++ ) != 0 ) {}
			dst = (uint8_t *) q;
		}
	}
	else
	{
		outQuerySet->lpszServiceInstanceName = NULL;
	}
	
	if( inQuerySet->lpServiceClassId )
	{
		outQuerySet->lpServiceClassId  = (LPGUID) dst;
		*outQuerySet->lpServiceClassId = *inQuerySet->lpServiceClassId;
		dst += sizeof( *inQuerySet->lpServiceClassId );
	}
	
	if( inQuerySet->lpVersion )
	{
		outQuerySet->lpVersion  = (LPWSAVERSION) dst;
		*outQuerySet->lpVersion = *inQuerySet->lpVersion;
		dst += sizeof( *inQuerySet->lpVersion );
	}
	
	s = inQuerySet->lpszComment;
	if( s )
	{
		outQuerySet->lpszComment = (LPWSTR) dst;
		q = (LPWSTR) dst;
		while( ( *q++ = *s++ ) != 0 ) {}
		dst = (uint8_t *) q;
	}
	
	if( inQuerySet->lpNSProviderId )
	{
		outQuerySet->lpNSProviderId  = (LPGUID) dst;
		*outQuerySet->lpNSProviderId = *inQuerySet->lpNSProviderId;
		dst += sizeof( *inQuerySet->lpNSProviderId );
	}
	
	s = inQuerySet->lpszContext;
	if( s )
	{
		outQuerySet->lpszContext = (LPWSTR) dst;
		q = (LPWSTR) dst;
		while( ( *q++ = *s++ ) != 0 ) {}
		dst = (uint8_t *) q;
	}
		
	n = inQuerySet->dwNumberOfProtocols;

	if( n > 0 )
	{
		check( inQuerySet->lpafpProtocols );
		
		outQuerySet->lpafpProtocols = (LPAFPROTOCOLS) dst;
		for( i = 0; i < n; ++i )
		{
			outQuerySet->lpafpProtocols[ i ] = inQuerySet->lpafpProtocols[ i ];
			dst += sizeof( *inQuerySet->lpafpProtocols );
		}
	}
		
	s = inQuerySet->lpszQueryString;
	if( s )
	{
		outQuerySet->lpszQueryString = (LPWSTR) dst;
		q = (LPWSTR) dst;
		while( ( *q++ = *s++ ) != 0 ) {}
		dst = (uint8_t *) q;
	}
	
	// Copy the address(es).
	
	if( ( inQuerySetFlags & LUP_RETURN_ADDR ) && ( inRef->numValidAddrs > 0 ) )
	{
		struct sockaddr_in	*	addr4;
		struct sockaddr_in6	*	addr6;
		int						index;
		
		outQuerySet->dwNumberOfCsAddrs	= inRef->numValidAddrs;
		outQuerySet->lpcsaBuffer 		= (LPCSADDR_INFO) dst;
		dst 							+= ( sizeof( *outQuerySet->lpcsaBuffer ) ) * ( inRef->numValidAddrs ) ;
		index							= 0;
		
		if ( inRef->addr4Valid )
		{	
			outQuerySet->lpcsaBuffer[ index ].LocalAddr.lpSockaddr 			= NULL;
			outQuerySet->lpcsaBuffer[ index ].LocalAddr.iSockaddrLength		= 0;
		
			outQuerySet->lpcsaBuffer[ index ].RemoteAddr.lpSockaddr 		= (LPSOCKADDR) dst;
			outQuerySet->lpcsaBuffer[ index ].RemoteAddr.iSockaddrLength	= sizeof( struct sockaddr_in );
		
			addr4 															= (struct sockaddr_in *) dst;
			memset( addr4, 0, sizeof( *addr4 ) );
			addr4->sin_family												= AF_INET;
			memcpy( &addr4->sin_addr, &inRef->addr4, 4 );
			dst 															+= sizeof( *addr4 );
		
			outQuerySet->lpcsaBuffer[ index ].iSocketType 					= AF_INET;		// Emulate Tcpip NSP
			outQuerySet->lpcsaBuffer[ index ].iProtocol						= IPPROTO_UDP;	// Emulate Tcpip NSP

			index++;
		}

		if ( inRef->addr6Valid )
		{
			outQuerySet->lpcsaBuffer[ index ].LocalAddr.lpSockaddr 			= NULL;
			outQuerySet->lpcsaBuffer[ index ].LocalAddr.iSockaddrLength		= 0;
		
			outQuerySet->lpcsaBuffer[ index ].RemoteAddr.lpSockaddr 		= (LPSOCKADDR) dst;
			outQuerySet->lpcsaBuffer[ index ].RemoteAddr.iSockaddrLength	= sizeof( struct sockaddr_in6 );
		
			addr6 															= (struct sockaddr_in6 *) dst;
			memset( addr6, 0, sizeof( *addr6 ) );
			addr6->sin6_family												= AF_INET6;
			addr6->sin6_scope_id											= inRef->addr6ScopeId;
			memcpy( &addr6->sin6_addr, &inRef->addr6, 16 );
			dst 															+= sizeof( *addr6 );
		
			outQuerySet->lpcsaBuffer[ index ].iSocketType 					= AF_INET6;		// Emulate Tcpip NSP
			outQuerySet->lpcsaBuffer[ index ].iProtocol						= IPPROTO_UDP;	// Emulate Tcpip NSP
		}
	}
	else
	{
		outQuerySet->dwNumberOfCsAddrs	= 0;
		outQuerySet->lpcsaBuffer 		= NULL;
	}
	
	// Copy the hostent blob.
	
	if( ( inQuerySetFlags & LUP_RETURN_BLOB ) && inRef->addr4Valid )
	{
		uint8_t *				base;
		struct hostent *		he;
		uintptr_t *				p;

		outQuerySet->lpBlob	 = (LPBLOB) dst;
		dst 				+= sizeof( *outQuerySet->lpBlob );
		
		base = dst;
		he	 = (struct hostent *) dst;
		dst += sizeof( *he );
		
		he->h_name = (char *)( dst - base );
		memcpy( dst, inRef->name, inRef->nameSize + 1 );
		dst += ( inRef->nameSize + 1 );
		
		he->h_aliases 	= (char **)( dst - base );
		p	  			= (uintptr_t *) dst;
		*p++  			= 0;
		dst 		 	= (uint8_t *) p;
		
		he->h_addrtype 	= AF_INET;
		he->h_length	= 4;
		
		he->h_addr_list	= (char **)( dst - base );
		p	  			= (uintptr_t *) dst;
		dst 		   += ( 2 * sizeof( *p ) );
		*p++			= (uintptr_t)( dst - base );
		*p++			= 0;
		p	  			= (uintptr_t *) dst;
		*p++			= (uintptr_t) inRef->addr4;
		dst 		 	= (uint8_t *) p;
		
		outQuerySet->lpBlob->cbSize 	= (ULONG)( dst - base );
		outQuerySet->lpBlob->pBlobData	= (BYTE *) base;
	}
	dlog_query_set( kDebugLevelVerbose, outQuerySet );

	check( (size_t)( dst - ( (uint8_t *) outQuerySet ) ) == debugSize );
}

//===========================================================================================================================
//	QueryCopyQuerySetSize
//
//	Warning: Assumes the NSP lock is held.
//===========================================================================================================================

DEBUG_LOCAL size_t	QueryCopyQuerySetSize( QueryRef inRef, const WSAQUERYSETW *inQuerySet, DWORD inQuerySetFlags )
{
	size_t		size;
	LPCWSTR		s;
	LPCWSTR		p;
	
	check( inRef );
	check( inQuerySet );
	
	// Calculate the size of the static portion of the results.
	
	size = sizeof( *inQuerySet );
	
	if( inQuerySetFlags & LUP_RETURN_NAME )
	{
		s = inQuerySet->lpszServiceInstanceName;
		if( s )
		{
			for( p = s; *p; ++p ) {}
			size += (size_t)( ( ( p - s ) + 1 ) * sizeof( *p ) );
		}
	}
	
	if( inQuerySet->lpServiceClassId )
	{
		size += sizeof( *inQuerySet->lpServiceClassId );
	}
	
	if( inQuerySet->lpVersion )
	{
		size += sizeof( *inQuerySet->lpVersion );
	}
	
	s = inQuerySet->lpszComment;
	if( s )
	{
		for( p = s; *p; ++p ) {}
		size += (size_t)( ( ( p - s ) + 1 ) * sizeof( *p ) );
	}
	
	if( inQuerySet->lpNSProviderId )
	{
		size += sizeof( *inQuerySet->lpNSProviderId );
	}
	
	s = inQuerySet->lpszContext;
	if( s )
	{
		for( p = s; *p; ++p ) {}
		size += (size_t)( ( ( p - s ) + 1 ) * sizeof( *p ) );
	}
	
	size += ( inQuerySet->dwNumberOfProtocols * sizeof( *inQuerySet->lpafpProtocols ) );
	
	s = inQuerySet->lpszQueryString;
	if( s )
	{
		for( p = s; *p; ++p ) {}
		size += (size_t)( ( ( p - s ) + 1 ) * sizeof( *p ) );
	}
	
	// Calculate the size of the address(es).
	
	if( ( inQuerySetFlags & LUP_RETURN_ADDR ) && inRef->addr4Valid )
	{
		size += sizeof( *inQuerySet->lpcsaBuffer );
		size += sizeof( struct sockaddr_in );
	}

	if( ( inQuerySetFlags & LUP_RETURN_ADDR ) && inRef->addr6Valid )
	{
		size += sizeof( *inQuerySet->lpcsaBuffer );
		size += sizeof( struct sockaddr_in6 );
	}
	
	// Calculate the size of the hostent blob.
	
	if( ( inQuerySetFlags & LUP_RETURN_BLOB ) && inRef->addr4Valid )
	{
		size += sizeof( *inQuerySet->lpBlob );	// Blob ptr/size structure
		size += sizeof( struct hostent );		// Old-style hostent structure
		size += ( inRef->nameSize + 1 );		// Name and null terminator
		size += 4;								// Alias list terminator (0 offset)
		size += 4;								// Offset to address.
		size += 4;								// Address list terminator (0 offset)
		size += 4;								// IPv4 address
	}
	return( size );
}

#if 0
#pragma mark -
#endif

#if( DEBUG )
//===========================================================================================================================
//	DebugDumpQuerySet
//===========================================================================================================================

#define	DebugSocketFamilyToString( FAM )	( ( FAM ) == AF_INET )  ? "AF_INET"  : \
											( ( FAM ) == AF_INET6 ) ? "AF_INET6" : ""

#define	DebugSocketProtocolToString( PROTO )	( ( PROTO ) == IPPROTO_UDP ) ? "IPPROTO_UDP" : \
												( ( PROTO ) == IPPROTO_TCP ) ? "IPPROTO_TCP" : ""

#define	DebugNameSpaceToString( NS )			( ( NS ) == NS_DNS ) ? "NS_DNS" : ( ( NS ) == NS_ALL ) ? "NS_ALL" : ""

void	DebugDumpQuerySet( DebugLevel inLevel, const WSAQUERYSETW *inQuerySet )
{
	DWORD		i;
	
	check( inQuerySet );

	// Fixed portion of the QuerySet.
		
	dlog( inLevel, "QuerySet:\n" );
	dlog( inLevel, "    dwSize:                  %d (expected %d)\n", inQuerySet->dwSize, sizeof( *inQuerySet ) );
	if( inQuerySet->lpszServiceInstanceName )
	{
		dlog( inLevel, "    lpszServiceInstanceName: %S\n", inQuerySet->lpszServiceInstanceName );
	}
	else
	{
		dlog( inLevel, "    lpszServiceInstanceName: <null>\n" );
	}
	if( inQuerySet->lpServiceClassId )
	{
		dlog( inLevel, "    lpServiceClassId:        %U\n", inQuerySet->lpServiceClassId );
	}
	else
	{
		dlog( inLevel, "    lpServiceClassId:        <null>\n" );
	}
	if( inQuerySet->lpVersion )
	{
		dlog( inLevel, "    lpVersion:\n" );
		dlog( inLevel, "        dwVersion:               %d\n", inQuerySet->lpVersion->dwVersion );
		dlog( inLevel, "        dwVersion:               %d\n", inQuerySet->lpVersion->ecHow );
	}
	else
	{
		dlog( inLevel, "    lpVersion:               <null>\n" );
	}
	if( inQuerySet->lpszComment )
	{
		dlog( inLevel, "    lpszComment:             %S\n", inQuerySet->lpszComment );
	}
	else
	{
		dlog( inLevel, "    lpszComment:             <null>\n" );
	}
	dlog( inLevel, "    dwNameSpace:             %d %s\n", inQuerySet->dwNameSpace, 
		DebugNameSpaceToString( inQuerySet->dwNameSpace ) );
	if( inQuerySet->lpNSProviderId )
	{
		dlog( inLevel, "    lpNSProviderId:          %U\n", inQuerySet->lpNSProviderId );
	}
	else
	{
		dlog( inLevel, "    lpNSProviderId:          <null>\n" );
	}
	if( inQuerySet->lpszContext )
	{
		dlog( inLevel, "    lpszContext:             %S\n", inQuerySet->lpszContext );
	}
	else
	{
		dlog( inLevel, "    lpszContext:             <null>\n" );
	}
	dlog( inLevel, "    dwNumberOfProtocols:     %d\n", inQuerySet->dwNumberOfProtocols );
	dlog( inLevel, "    lpafpProtocols:          %s\n", inQuerySet->lpafpProtocols ? "" : "<null>" );
	for( i = 0; i < inQuerySet->dwNumberOfProtocols; ++i )
	{
		if( i != 0 )
		{
			dlog( inLevel, "\n" );
		}
		dlog( inLevel, "        iAddressFamily:          %d %s\n", inQuerySet->lpafpProtocols[ i ].iAddressFamily, 
			DebugSocketFamilyToString( inQuerySet->lpafpProtocols[ i ].iAddressFamily ) );
		dlog( inLevel, "        iProtocol:               %d %s\n", inQuerySet->lpafpProtocols[ i ].iProtocol, 
			DebugSocketProtocolToString( inQuerySet->lpafpProtocols[ i ].iProtocol ) );
	}
	if( inQuerySet->lpszQueryString )
	{
		dlog( inLevel, "    lpszQueryString:         %S\n", inQuerySet->lpszQueryString );
	}
	else
	{
		dlog( inLevel, "    lpszQueryString:         <null>\n" );
	}
	dlog( inLevel, "    dwNumberOfCsAddrs:       %d\n", inQuerySet->dwNumberOfCsAddrs );
	dlog( inLevel, "    lpcsaBuffer:             %s\n", inQuerySet->lpcsaBuffer ? "" : "<null>" );
	for( i = 0; i < inQuerySet->dwNumberOfCsAddrs; ++i )
	{
		if( i != 0 )
		{
			dlog( inLevel, "\n" );
		}
		if( inQuerySet->lpcsaBuffer[ i ].LocalAddr.lpSockaddr && 
			( inQuerySet->lpcsaBuffer[ i ].LocalAddr.iSockaddrLength > 0 ) )
		{
			dlog( inLevel, "        LocalAddr:               %##a\n", 
				inQuerySet->lpcsaBuffer[ i ].LocalAddr.lpSockaddr );
		}
		else
		{
			dlog( inLevel, "        LocalAddr:               <null/empty>\n" );
		}
		if( inQuerySet->lpcsaBuffer[ i ].RemoteAddr.lpSockaddr && 
			( inQuerySet->lpcsaBuffer[ i ].RemoteAddr.iSockaddrLength > 0 ) )
		{
			dlog( inLevel, "        RemoteAddr:              %##a\n", 
				inQuerySet->lpcsaBuffer[ i ].RemoteAddr.lpSockaddr );
		}
		else
		{
			dlog( inLevel, "        RemoteAddr:              <null/empty>\n" );
		}
		dlog( inLevel, "        iSocketType:             %d\n", inQuerySet->lpcsaBuffer[ i ].iSocketType );
		dlog( inLevel, "        iProtocol:               %d\n", inQuerySet->lpcsaBuffer[ i ].iProtocol );
	}
	dlog( inLevel, "    dwOutputFlags:           %d\n", inQuerySet->dwOutputFlags );
	
	// Blob portion of the QuerySet.
	
	if( inQuerySet->lpBlob )
	{
		dlog( inLevel, "    lpBlob:\n" );
		dlog( inLevel, "        cbSize:                  %ld\n", inQuerySet->lpBlob->cbSize );
		dlog( inLevel, "        pBlobData:               %#p\n", inQuerySet->lpBlob->pBlobData );
		dloghex( inLevel, 12, NULL, 0, 0, NULL, 0, 
			inQuerySet->lpBlob->pBlobData, inQuerySet->lpBlob->pBlobData, inQuerySet->lpBlob->cbSize, 
			kDebugFlagsNone, NULL, 0 );
	}
	else
	{
		dlog( inLevel, "    lpBlob:                  <null>\n" );
	}
}
#endif


//===========================================================================================================================
//	InHostsTable
//===========================================================================================================================

DEBUG_LOCAL BOOL
InHostsTable( const char * name )
{
	HostsFileInfo	*	node;
	BOOL				ret = FALSE;
	OSStatus			err;
	
	check( name );

	if ( gHostsFileInfo == NULL )
	{
		TCHAR				systemDirectory[MAX_PATH];
		TCHAR				hFileName[MAX_PATH];
		HostsFile		*	hFile;

		GetSystemDirectory( systemDirectory, sizeof( systemDirectory ) );
		sprintf( hFileName, "%s\\drivers\\etc\\hosts", systemDirectory );
		err = HostsFileOpen( &hFile, hFileName );
		require_noerr( err, exit );

		while ( HostsFileNext( hFile, &node ) == 0 )
		{
			if ( IsLocalName( node ) )
			{
				node->m_next = gHostsFileInfo;
				gHostsFileInfo = node;
			}
			else
			{
				HostsFileInfoFree( node );
			}
		}

		HostsFileClose( hFile );
	}

	for ( node = gHostsFileInfo; node; node = node->m_next )
	{
		if ( IsSameName( node, name ) )
		{
			ret = TRUE;
			break;
		}
	}

exit:

	return ret;
}


//===========================================================================================================================
//	IsLocalName
//===========================================================================================================================

DEBUG_LOCAL BOOL
IsLocalName( HostsFileInfo * node )
{
	BOOL ret = TRUE;

	check( node );

	if ( strstr( node->m_host.h_name, ".local" ) == NULL )
	{
		int i;

		for ( i = 0; node->m_host.h_aliases[i]; i++ )
		{
			if ( strstr( node->m_host.h_aliases[i], ".local" ) )
			{
				goto exit;
			}
		}

		ret = FALSE;
	}

exit:

	return ret;
}


//===========================================================================================================================
//	IsSameName
//===========================================================================================================================

DEBUG_LOCAL BOOL
IsSameName( HostsFileInfo * node, const char * name )
{
	BOOL ret = TRUE;

	check( node );
	check( name );

	if ( strcmp( node->m_host.h_name, name ) != 0 )
	{
		int i;

		for ( i = 0; node->m_host.h_aliases[i]; i++ )
		{
			if ( strcmp( node->m_host.h_aliases[i], name ) == 0 )
			{
				goto exit;
			}
		}

		ret = FALSE;
	}

exit:

	return ret;
}


//===========================================================================================================================
//	HostsFileOpen
//===========================================================================================================================

DEBUG_LOCAL OSStatus
HostsFileOpen( HostsFile ** self, const char * fname )
{
	OSStatus err = kNoErr;

	*self = (HostsFile*) malloc( sizeof( HostsFile ) );
	require_action( *self, exit, err = kNoMemoryErr );
	memset( *self, 0, sizeof( HostsFile ) );

	(*self)->m_bufferSize = BUFFER_INITIAL_SIZE;
	(*self)->m_buffer = (char*) malloc( (*self)->m_bufferSize );
	require_action( (*self)->m_buffer, exit, err = kNoMemoryErr );

	// check malloc

	(*self)->m_fp = fopen( fname, "r" );
	require_action( (*self)->m_fp, exit, err = kUnknownErr );

exit:

	if ( err && *self )
	{
		HostsFileClose( *self );
		*self = NULL;
	}
		
	return err;
}


//===========================================================================================================================
//	HostsFileClose
//===========================================================================================================================

DEBUG_LOCAL OSStatus
HostsFileClose( HostsFile * self )
{
	check( self );

	if ( self->m_buffer )
	{
		free( self->m_buffer );
		self->m_buffer = NULL;
	}

	if ( self->m_fp )
	{
		fclose( self->m_fp );
		self->m_fp = NULL;
	}

	free( self );

	return kNoErr;
} 


//===========================================================================================================================
//	HostsFileInfoFree
//===========================================================================================================================

DEBUG_LOCAL void
HostsFileInfoFree( HostsFileInfo * info )
{
	while ( info )
	{
		HostsFileInfo * next = info->m_next;

		if ( info->m_host.h_addr_list )
		{
			if ( info->m_host.h_addr_list[0] )
			{
				free( info->m_host.h_addr_list[0] );
				info->m_host.h_addr_list[0] = NULL;
			}

			free( info->m_host.h_addr_list );
			info->m_host.h_addr_list = NULL;
		}

		if ( info->m_host.h_aliases )
		{
			int i;

			for ( i = 0; info->m_host.h_aliases[i]; i++ )
			{
				free( info->m_host.h_aliases[i] );
			}

			free( info->m_host.h_aliases );
		}

		if ( info->m_host.h_name )
		{
			free( info->m_host.h_name );
			info->m_host.h_name = NULL;
		}
			
		free( info );

		info = next;
	}
}


//===========================================================================================================================
//	HostsFileNext
//===========================================================================================================================

DEBUG_LOCAL OSStatus
HostsFileNext( HostsFile * self, HostsFileInfo ** hInfo )
{
	struct sockaddr_in6	addr_6;
	struct sockaddr_in	addr_4;
	int					numAliases = ALIASES_INITIAL_SIZE;
	char			*	line;
	char			*	tok;
	int					dwSize;
	int					idx;
	int					i;
	short				family;
	OSStatus			err = kNoErr;

	check( self );
	check( self->m_fp );
	check( hInfo );

	idx	= 0;

	*hInfo = (HostsFileInfo*) malloc( sizeof( HostsFileInfo ) );
	require_action( *hInfo, exit, err = kNoMemoryErr );
	memset( *hInfo, 0, sizeof( HostsFileInfo ) );

	for ( ; ; )
	{
		line = fgets( self->m_buffer + idx, self->m_bufferSize - idx, self->m_fp );
		
		if ( line == NULL )
		{
			err = 1;
			goto exit;
		}

		// If there's no eol and no eof, then we didn't get the whole line

		if ( !strchr( line, '\n' ) && !feof( self->m_fp ) )
		{
			int			bufferSize;
			char	*	buffer;

			/* Try and allocate space for longer line */

			bufferSize	= self->m_bufferSize * 2;
			buffer		= (char*) realloc( self->m_buffer, bufferSize );
			require_action( buffer, exit, err = kNoMemoryErr );
			self->m_bufferSize	= bufferSize;
			self->m_buffer		= buffer;
			idx					= (int) strlen( self->m_buffer );

			continue;
		}

		line	= self->m_buffer;
		idx		= 0;

		if (*line == '#')
		{
			continue;
		}

		// Get rid of either comments or eol characters

		if (( tok = strpbrk(line, "#\n")) != NULL )
		{
			*tok = '\0';
		}

		// Make sure there is some whitespace on this line

		if (( tok = strpbrk(line, " \t")) == NULL )
		{
			continue;
		}

		// Create two strings, where p == the IP Address and tok is the name list

		*tok++ = '\0';

		while ( *tok == ' ' || *tok == '\t')
		{
			tok++;
		}

		// Now we have the name

		(*hInfo)->m_host.h_name = (char*) malloc( strlen( tok ) + 1 );
		require_action( (*hInfo)->m_host.h_name, exit, err = kNoMemoryErr );
		strcpy( (*hInfo)->m_host.h_name, tok );

		// Now create the address (IPv6/IPv4)

		addr_6.sin6_family	= family = AF_INET6;
		dwSize				= sizeof( addr_6 );

		if ( WSAStringToAddress( line, AF_INET6, NULL, ( struct sockaddr*) &addr_6, &dwSize ) != 0 )
		{
			addr_4.sin_family = family = AF_INET;
			dwSize = sizeof( addr_4 );

			if (WSAStringToAddress( line, AF_INET, NULL, ( struct sockaddr*) &addr_4, &dwSize ) != 0 )
			{
				continue;
			}
		}

		(*hInfo)->m_host.h_addr_list = (char**) malloc( sizeof( char**) * 2 );
		require_action( (*hInfo)->m_host.h_addr_list, exit, err = kNoMemoryErr );

		if ( family == AF_INET6 )
		{
			(*hInfo)->m_host.h_length		= (short) sizeof( addr_6.sin6_addr );
			(*hInfo)->m_host.h_addr_list[0] = (char*) malloc( (*hInfo)->m_host.h_length );
			require_action( (*hInfo)->m_host.h_addr_list[0], exit, err = kNoMemoryErr );
			memmove( (*hInfo)->m_host.h_addr_list[0], &addr_6.sin6_addr, sizeof( addr_6.sin6_addr ) );
			
		}
		else
		{
			(*hInfo)->m_host.h_length		= (short) sizeof( addr_4.sin_addr );
			(*hInfo)->m_host.h_addr_list[0] = (char*) malloc( (*hInfo)->m_host.h_length );
			require_action( (*hInfo)->m_host.h_addr_list[0], exit, err = kNoMemoryErr );
			memmove( (*hInfo)->m_host.h_addr_list[0], &addr_4.sin_addr, sizeof( addr_4.sin_addr ) );
		}

		(*hInfo)->m_host.h_addr_list[1] = NULL;
		(*hInfo)->m_host.h_addrtype		= family;

		// Now get the aliases

		if ((tok = strpbrk(tok, " \t")) != NULL)
		{
			*tok++ = '\0';
		}

		i = 0;

		(*hInfo)->m_host.h_aliases		= (char**) malloc( sizeof(char**) * numAliases );
		require_action( (*hInfo)->m_host.h_aliases, exit, err = kNoMemoryErr );
		(*hInfo)->m_host.h_aliases[0]	= NULL;

		while ( tok && *tok )
		{
			// Skip over the whitespace, waiting for the start of the next alias name

			if (*tok == ' ' || *tok == '\t')
			{
				tok++;
				continue;
			}

			// Check to make sure we don't exhaust the alias buffer

			if ( i >= ( numAliases - 1 ) )
			{
				numAliases = numAliases * 2;
				(*hInfo)->m_host.h_aliases = (char**) realloc( (*hInfo)->m_host.h_aliases, numAliases * sizeof( char** ) );
				require_action( (*hInfo)->m_host.h_aliases, exit, err = kNoMemoryErr );
			}

			(*hInfo)->m_host.h_aliases[i] = (char*) malloc( strlen( tok ) + 1 );
			require_action( (*hInfo)->m_host.h_aliases[i], exit, err = kNoMemoryErr );

			strcpy( (*hInfo)->m_host.h_aliases[i], tok );

			if (( tok = strpbrk( tok, " \t")) != NULL )
			{
				*tok++ = '\0';
			}

			(*hInfo)->m_host.h_aliases[++i] = NULL;
		}

		break;
	}

exit:

	if ( err && ( *hInfo ) )
	{
		HostsFileInfoFree( *hInfo );
		*hInfo = NULL;
	}

	return err;
}


#ifdef ENABLE_REVERSE_LOOKUP
//===========================================================================================================================
//	IsReverseLookup
//===========================================================================================================================

DEBUG_LOCAL OSStatus
IsReverseLookup( LPCWSTR name, size_t size )
{
	LPCWSTR		p;
	OSStatus	err = kNoErr;

	// IPv6LL Reverse-mapping domains are {8,9,A,B}.E.F.ip6.arpa
	require_action_quiet( size > sizeof_string( ".0.8.e.f.ip6.arpa" ), exit, err = WSASERVICE_NOT_FOUND );
 
	p = name + ( size - 1 );
	p = ( *p == '.' ) ? ( p - sizeof_string( ".0.8.e.f.ip6.arpa" ) ) : ( ( p - sizeof_string( ".0.8.e.f.ip6.arpa" ) ) + 1 );
	
	if	( ( ( p[ 0 ] != '.' )							||
		( ( p[ 1 ] != '0' ) )							||
		( ( p[ 2 ] != '.' ) )							||
		( ( p[ 3 ] != '8' ) )							||
		( ( p[ 4 ] != '.' ) )							||
		( ( p[ 5 ] != 'E' ) && ( p[ 5 ] != 'e' ) )		||
		( ( p[ 6 ] != '.' ) )							||
		( ( p[ 7 ] != 'F' ) && ( p[ 7 ] != 'f' ) )		||
		( ( p[ 8 ] != '.' ) )							||
		( ( p[ 9 ] != 'I' ) && ( p[ 9 ] != 'i' ) )		||
		( ( p[ 10 ] != 'P' ) && ( p[ 10 ] != 'p' ) )	||	
		( ( p[ 11 ] != '6' ) )							||
		( ( p[ 12 ] != '.' ) )							||
		( ( p[ 13 ] != 'A' ) && ( p[ 13 ] != 'a' ) )	||
		( ( p[ 14 ] != 'R' ) && ( p[ 14 ] != 'r' ) )	||
		( ( p[ 15 ] != 'P' ) && ( p[ 15 ] != 'p' ) )	||
		( ( p[ 16 ] != 'A' ) && ( p[ 16 ] != 'a' ) ) ) )
	{
		require_action_quiet( size > sizeof_string( ".254.169.in-addr.arpa" ), exit, err = WSASERVICE_NOT_FOUND );
 
		p = name + ( size - 1 );
		p = ( *p == '.' ) ? ( p - sizeof_string( ".254.169.in-addr.arpa" ) ) : ( ( p - sizeof_string( ".254.169.in-addr.arpa" ) ) + 1 );
	
		require_action_quiet( ( ( p[ 0 ] == '.' )						 &&
								( ( p[ 1 ] == '2' ) )							&&
								( ( p[ 2 ] == '5' ) )							&&
								( ( p[ 3 ] == '4' ) )							&&
								( ( p[ 4 ] == '.' ) )							&&
								( ( p[ 5 ] == '1' ) )							&&
								( ( p[ 6 ] == '6' ) )							&&
								( ( p[ 7 ] == '9' ) )							&&
								( ( p[ 8 ] == '.' ) )							&&
								( ( p[ 9 ] == 'I' ) || ( p[ 9 ] == 'i' ) )		&&
								( ( p[ 10 ] == 'N' ) || ( p[ 10 ] == 'n' ) )	&&	
								( ( p[ 11 ] == '-' ) )							&&
								( ( p[ 12 ] == 'A' ) || ( p[ 12 ] == 'a' ) )	&&
								( ( p[ 13 ] == 'D' ) || ( p[ 13 ] == 'd' ) )	&&
								( ( p[ 14 ] == 'D' ) || ( p[ 14 ] == 'd' ) )	&&
								( ( p[ 15 ] == 'R' ) || ( p[ 15 ] == 'r' ) )	&&
								( ( p[ 16 ] == '.' ) )							&&
								( ( p[ 17 ] == 'A' ) || ( p[ 17 ] == 'a' ) )	&&
								( ( p[ 18 ] == 'R' ) || ( p[ 18 ] == 'r' ) )	&&
								( ( p[ 19 ] == 'P' ) || ( p[ 19 ] == 'p' ) )	&&
								( ( p[ 20 ] == 'A' ) || ( p[ 20 ] == 'a' ) ) ),
								exit, err = WSASERVICE_NOT_FOUND );
	}

	// It's a reverse lookup

	check( err == kNoErr );

exit:

	return err;
}
#endif

//===========================================================================================================================
//	GetScopeId
//===========================================================================================================================

DEBUG_LOCAL DWORD
GetScopeId( DWORD ifIndex )
{
	DWORD						err;
	int							i;
	DWORD						flags;
	struct ifaddrs *			head;
	struct ifaddrs **			next;
	IP_ADAPTER_ADDRESSES *		iaaList;
	ULONG						iaaListSize;
	IP_ADAPTER_ADDRESSES *		iaa;
	DWORD						scopeId = 0;
	
	head	= NULL;
	next	= &head;
	iaaList	= NULL;
	
	require( gGetAdaptersAddressesFunctionPtr, exit );

	// Get the list of interfaces. The first call gets the size and the second call gets the actual data.
	// This loops to handle the case where the interface changes in the window after getting the size, but before the
	// second call completes. A limit of 100 retries is enforced to prevent infinite loops if something else is wrong.
	
	flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
	i = 0;
	for( ;; )
	{
		iaaListSize = 0;
		err = gGetAdaptersAddressesFunctionPtr( AF_UNSPEC, flags, NULL, NULL, &iaaListSize );
		check( err == ERROR_BUFFER_OVERFLOW );
		check( iaaListSize >= sizeof( IP_ADAPTER_ADDRESSES ) );
		
		iaaList = (IP_ADAPTER_ADDRESSES *) malloc( iaaListSize );
		require_action( iaaList, exit, err = ERROR_NOT_ENOUGH_MEMORY );
		
		err = gGetAdaptersAddressesFunctionPtr( AF_UNSPEC, flags, NULL, iaaList, &iaaListSize );
		if( err == ERROR_SUCCESS ) break;
		
		free( iaaList );
		iaaList = NULL;
		++i;
		require( i < 100, exit );
		dlog( kDebugLevelWarning, "%s: retrying GetAdaptersAddresses after %d failure(s) (%d %m)\n", __ROUTINE__, i, err, err );
	}
	
	for( iaa = iaaList; iaa; iaa = iaa->Next )
	{
		DWORD ipv6IfIndex;

		if ( iaa->IfIndex > 0xFFFFFF )
		{
			continue;
		}
		if ( iaa->Ipv6IfIndex > 0xFF )
		{
			continue;
		}

		// For IPv4 interfaces, there seems to be a bug in iphlpapi.dll that causes the 
		// following code to crash when iterating through the prefix list.  This seems
		// to occur when iaa->Ipv6IfIndex != 0 when IPv6 is not installed on the host.
		// This shouldn't happen according to Microsoft docs which states:
		//
		//     "Ipv6IfIndex contains 0 if IPv6 is not available on the interface."
		//
		// So the data structure seems to be corrupted when we return from
		// GetAdaptersAddresses(). The bug seems to occur when iaa->Length <
		// sizeof(IP_ADAPTER_ADDRESSES), so when that happens, we'll manually
		// modify iaa to have the correct values.

		if ( iaa->Length >= sizeof( IP_ADAPTER_ADDRESSES ) )
		{
			ipv6IfIndex = iaa->Ipv6IfIndex;
		}
		else
		{
			ipv6IfIndex	= 0;
		}

		// Skip psuedo and tunnel interfaces.
		
		if( ( ipv6IfIndex == 1 ) || ( iaa->IfType == IF_TYPE_TUNNEL ) )
		{
			continue;
		}

		if ( iaa->IfIndex == ifIndex )
		{
			scopeId = iaa->Ipv6IfIndex;
			break;
		}
	} 

exit:

	if( iaaList )
	{
		free( iaaList );
	}

	return scopeId;
}