/* -*- 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 #include #include #include "ClientCommon.h" #include "CommonServices.h" #include "DebugServices.h" #include #include #include #include #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 ); // // Don't resolve multi-label name // 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 ); // // // 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: \n" ); } if( inQuerySet->lpServiceClassId ) { dlog( inLevel, " lpServiceClassId: %U\n", inQuerySet->lpServiceClassId ); } else { dlog( inLevel, " lpServiceClassId: \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: \n" ); } if( inQuerySet->lpszComment ) { dlog( inLevel, " lpszComment: %S\n", inQuerySet->lpszComment ); } else { dlog( inLevel, " lpszComment: \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: \n" ); } if( inQuerySet->lpszContext ) { dlog( inLevel, " lpszContext: %S\n", inQuerySet->lpszContext ); } else { dlog( inLevel, " lpszContext: \n" ); } dlog( inLevel, " dwNumberOfProtocols: %d\n", inQuerySet->dwNumberOfProtocols ); dlog( inLevel, " lpafpProtocols: %s\n", inQuerySet->lpafpProtocols ? "" : "" ); 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: \n" ); } dlog( inLevel, " dwNumberOfCsAddrs: %d\n", inQuerySet->dwNumberOfCsAddrs ); dlog( inLevel, " lpcsaBuffer: %s\n", inQuerySet->lpcsaBuffer ? "" : "" ); 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: \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: \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: \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; }