diff options
Diffstat (limited to 'mDNSResponder/mDNSWindows/SystemService/Service.c')
-rw-r--r-- | mDNSResponder/mDNSWindows/SystemService/Service.c | 2616 |
1 files changed, 2616 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSWindows/SystemService/Service.c b/mDNSResponder/mDNSWindows/SystemService/Service.c new file mode 100644 index 00000000..d175326f --- /dev/null +++ b/mDNSResponder/mDNSWindows/SystemService/Service.c @@ -0,0 +1,2616 @@ +/* -*- 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 <crtdbg.h> +#include <stdarg.h> +#include <stddef.h> + +#include "Poll.h" +#include "CommonServices.h" +#include "DebugServices.h" +#include "RegNames.h" + +#include "uds_daemon.h" +#include "GenLinkedList.h" +#include "Service.h" +#include "EventLog.h" + +#include "Resource.h" + +#include "mDNSEmbeddedAPI.h" +#include "uDNS.h" +#include "mDNSWin32.h" +#include "mDNSDebug.h" + +#include "Firewall.h" + +#if( !TARGET_OS_WINDOWS_CE ) + #include <mswsock.h> + #include <process.h> + #include <ipExport.h> + #include <ws2def.h> + #include <ws2ipdef.h> + #include <iphlpapi.h> + #include <netioapi.h> + #include <iptypes.h> + #include <powrprof.h> +#endif + +#ifndef HeapEnableTerminationOnCorruption +# define HeapEnableTerminationOnCorruption (HEAP_INFORMATION_CLASS)1 +#endif + +#if 0 +#pragma mark == Constants == +#endif + +//=========================================================================================================================== +// Constants +//=========================================================================================================================== + +#define DEBUG_NAME "[mDNSWin32] " +#define kServiceFirewallName L"Bonjour" +#define kServiceDependencies TEXT("Tcpip\0\0") +#define kDNSServiceCacheEntryCountDefault 512 +#define kRetryFirewallPeriod 30 * 1000 +#define kDefValueSize MAX_PATH + 1 +#define kZeroIndex 0 +#define kDefaultRouteMetric 399 +#define kSecondsTo100NSUnits ( 10 * 1000 * 1000 ) +#define kSPSMaintenanceWakePeriod -30 +#define kWaitToRetry (60 * 5) + +#define RR_CACHE_SIZE 500 +static CacheEntity gRRCache[RR_CACHE_SIZE]; +#if 0 +#pragma mark == Structures == +#endif + +#if 0 +#pragma mark == Prototypes == +#endif + +//=========================================================================================================================== +// Prototypes +//=========================================================================================================================== +static void Usage( void ); +static BOOL WINAPI ConsoleControlHandler( DWORD inControlEvent ); +static OSStatus InstallService( LPCTSTR inName, LPCTSTR inDisplayName, LPCTSTR inDescription, LPCTSTR inPath ); +static OSStatus RemoveService( LPCTSTR inName ); +static OSStatus SetServiceParameters(); +static OSStatus GetServiceParameters(); +static OSStatus CheckFirewall(); +static OSStatus SetServiceInfo( SC_HANDLE inSCM, LPCTSTR inServiceName, LPCTSTR inDescription ); +static void ReportStatus( int inType, const char *inFormat, ... ); + +static void WINAPI ServiceMain( DWORD argc, LPTSTR argv[] ); +static OSStatus ServiceSetupEventLogging( void ); +static DWORD WINAPI ServiceControlHandler( DWORD inControl, DWORD inEventType, LPVOID inEventData, LPVOID inContext ); + +static OSStatus ServiceRun( int argc, LPTSTR argv[] ); +static void ServiceStop( void ); + +static OSStatus ServiceSpecificInitialize( int argc, LPTSTR argv[] ); +static OSStatus ServiceSpecificRun( int argc, LPTSTR argv[] ); +static OSStatus ServiceSpecificStop( void ); +static void ServiceSpecificFinalize( int argc, LPTSTR argv[] ); +static mStatus SetupServiceEvents(); +static mStatus TearDownServiceEvents(); +static mStatus SetupNotifications(); +static mStatus TearDownNotifications(); +static void CALLBACK StopNotification( HANDLE event, void * context ); +static void CALLBACK PowerSuspendNotification( HANDLE event, void * context ); +static void CALLBACK PowerResumeNotification( HANDLE event, void * context ); +static void CALLBACK InterfaceListNotification( SOCKET socket, LPWSANETWORKEVENTS event, void *context ); +static void CALLBACK ComputerDescriptionNotification( HANDLE event, void *context ); +static void CALLBACK TCPChangedNotification( HANDLE event, void *context ); +static void CALLBACK DDNSChangedNotification( HANDLE event, void *context ); +static void CALLBACK FileSharingChangedNotification( HANDLE event, void *context ); +static void CALLBACK FirewallChangedNotification( HANDLE event, void *context ); +static void CALLBACK AdvertisedServicesChangedNotification( HANDLE event, void *context ); +static void CALLBACK SPSWakeupNotification( HANDLE event, void *context ); +static void CALLBACK SPSSleepNotification( HANDLE event, void *context ); +static void CALLBACK UDSAcceptNotification( SOCKET sock, LPWSANETWORKEVENTS event, void *context ); +static void CALLBACK UDSReadNotification( SOCKET sock, LPWSANETWORKEVENTS event, void *context ); +static void CoreCallback(mDNS * const inMDNS, mStatus result); +static mDNSu8 SystemWakeForNetworkAccess( LARGE_INTEGER * timeout ); +static OSStatus GetRouteDestination(DWORD * ifIndex, DWORD * address); +static OSStatus SetLLRoute( mDNS * const inMDNS ); +static bool HaveRoute( PMIB_IPFORWARDROW rowExtant, unsigned long addr, unsigned long metric ); +static bool IsValidAddress( const char * addr ); +static bool IsNortelVPN( IP_ADAPTER_INFO * pAdapter ); +static bool IsJuniperVPN( IP_ADAPTER_INFO * pAdapter ); +static bool IsCiscoVPN( IP_ADAPTER_INFO * pAdapter ); +static const char * strnistr( const char * string, const char * subString, size_t max ); + +#if defined(UNICODE) +# define StrLen(X) wcslen(X) +# define StrCmp(X,Y) wcscmp(X,Y) +#else +# define StrLen(X) strlen(X) +# define StrCmp(X,Y) strcmp(X,Y) +#endif + + +#define kLLNetworkAddr "169.254.0.0" +#define kLLNetworkAddrMask "255.255.0.0" + + +#include "mDNSEmbeddedAPI.h" + +#if 0 +#pragma mark == Globals == +#endif + +//=========================================================================================================================== +// Globals +//=========================================================================================================================== +#define gMDNSRecord mDNSStorage +DEBUG_LOCAL mDNS_PlatformSupport gPlatformStorage; +DEBUG_LOCAL BOOL gServiceQuietMode = FALSE; +DEBUG_LOCAL SERVICE_TABLE_ENTRY gServiceDispatchTable[] = +{ + { kServiceName, ServiceMain }, + { NULL, NULL } +}; +DEBUG_LOCAL HANDLE gStopEvent = NULL; +DEBUG_LOCAL HANDLE gPowerSuspendEvent = NULL; +DEBUG_LOCAL HANDLE gPowerSuspendAckEvent = NULL; +DEBUG_LOCAL HANDLE gPowerResumeEvent = NULL; +DEBUG_LOCAL SOCKET gInterfaceListChangedSocket = INVALID_SOCKET; +DEBUG_LOCAL HKEY gDescKey = NULL; +DEBUG_LOCAL HANDLE gDescChangedEvent = NULL; // Computer description changed event +DEBUG_LOCAL HKEY gTcpipKey = NULL; +DEBUG_LOCAL HANDLE gTcpipChangedEvent = NULL; // TCP/IP config changed +DEBUG_LOCAL HKEY gDdnsKey = NULL; +DEBUG_LOCAL HANDLE gDdnsChangedEvent = NULL; // DynDNS config changed +DEBUG_LOCAL HKEY gFileSharingKey = NULL; +DEBUG_LOCAL HANDLE gFileSharingChangedEvent = NULL; // File Sharing changed +DEBUG_LOCAL HKEY gFirewallKey = NULL; +DEBUG_LOCAL HANDLE gFirewallChangedEvent = NULL; // Firewall changed +DEBUG_LOCAL HKEY gAdvertisedServicesKey = NULL; +DEBUG_LOCAL HANDLE gAdvertisedServicesChangedEvent = NULL; // Advertised services changed +DEBUG_LOCAL SERVICE_STATUS gServiceStatus; +DEBUG_LOCAL SERVICE_STATUS_HANDLE gServiceStatusHandle = NULL; +DEBUG_LOCAL HANDLE gServiceEventSource = NULL; +DEBUG_LOCAL bool gServiceAllowRemote = false; +DEBUG_LOCAL int gServiceCacheEntryCount = 0; // 0 means to use the DNS-SD default. +DEBUG_LOCAL bool gServiceManageLLRouting = true; +DEBUG_LOCAL HANDLE gSPSWakeupEvent = NULL; +DEBUG_LOCAL HANDLE gSPSSleepEvent = NULL; +DEBUG_LOCAL SocketRef gUDSSocket = 0; +DEBUG_LOCAL udsEventCallback gUDSCallback = NULL; +DEBUG_LOCAL BOOL gRetryFirewall = FALSE; + +typedef DWORD ( WINAPI * GetIpInterfaceEntryFunctionPtr )( PMIB_IPINTERFACE_ROW ); +mDNSlocal HMODULE gIPHelperLibraryInstance = NULL; +mDNSlocal GetIpInterfaceEntryFunctionPtr gGetIpInterfaceEntryFunctionPtr = NULL; + + +#if 0 +#pragma mark - +#endif + +//=========================================================================================================================== +// Main +//=========================================================================================================================== +int Main( int argc, LPTSTR argv[] ) +{ + OSStatus err; + BOOL ok; + BOOL start; + int i; + + HeapSetInformation( NULL, HeapEnableTerminationOnCorruption, NULL, 0 ); + + debug_initialize( kDebugOutputTypeMetaConsole ); + debug_set_property( kDebugPropertyTagPrintLevel, kDebugLevelVerbose ); + + // Default to automatically starting the service dispatcher if no extra arguments are specified. + + start = ( argc <= 1 ); + + // Parse arguments. + + for( i = 1; i < argc; ++i ) + { + if( StrCmp( argv[ i ], TEXT("-install") ) == 0 ) // Install + { + TCHAR desc[ 256 ]; + + desc[ 0 ] = 0; + LoadString( GetModuleHandle( NULL ), IDS_SERVICE_DESCRIPTION, desc, sizeof( desc ) ); + err = InstallService( kServiceName, kServiceName, desc, argv[0] ); + if( err ) + { + ReportStatus( EVENTLOG_ERROR_TYPE, "install service failed (%d)\n", err ); + goto exit; + } + } + else if( StrCmp( argv[ i ], TEXT("-remove") ) == 0 ) // Remove + { + err = RemoveService( kServiceName ); + if( err ) + { + ReportStatus( EVENTLOG_ERROR_TYPE, "remove service failed (%d)\n", err ); + goto exit; + } + } + else if( StrCmp( argv[ i ], TEXT("-start") ) == 0 ) // Start + { + start = TRUE; + } + else if( StrCmp( argv[ i ], TEXT("-server") ) == 0 ) // Server + { + err = RunDirect( argc, argv ); + if( err ) + { + ReportStatus( EVENTLOG_ERROR_TYPE, "run service directly failed (%d)\n", err ); + } + goto exit; + } + else if( StrCmp( argv[ i ], TEXT("-q") ) == 0 ) // Quiet Mode (toggle) + { + gServiceQuietMode = !gServiceQuietMode; + } + else if( ( StrCmp( argv[ i ], TEXT("-help") ) == 0 ) || // Help + ( StrCmp( argv[ i ], TEXT("-h") ) == 0 ) ) + { + Usage(); + err = 0; + break; + } + else + { + Usage(); + err = kParamErr; + break; + } + } + + // Start the service dispatcher if requested. This does not return until all services have terminated. If any + // global initialization is needed, it should be done before starting the service dispatcher, but only if it + // will take less than 30 seconds. Otherwise, use a separate thread for it and start the dispatcher immediately. + + if( start ) + { + ok = StartServiceCtrlDispatcher( gServiceDispatchTable ); + err = translate_errno( ok, (OSStatus) GetLastError(), kInUseErr ); + if( err != kNoErr ) + { + ReportStatus( EVENTLOG_ERROR_TYPE, "start service dispatcher failed (%d)\n", err ); + goto exit; + } + } + err = 0; + +exit: + dlog( kDebugLevelTrace, DEBUG_NAME "exited (%d %m)\n", err, err ); + _CrtDumpMemoryLeaks(); + return( (int) err ); +} + +//=========================================================================================================================== +// Usage +//=========================================================================================================================== + +static void Usage( void ) +{ + fprintf( stderr, "\n" ); + fprintf( stderr, "mDNSResponder 1.0d1\n" ); + fprintf( stderr, "\n" ); + fprintf( stderr, " <no args> Runs the service normally\n" ); + fprintf( stderr, " -install Creates the service and starts it\n" ); + fprintf( stderr, " -remove Stops the service and deletes it\n" ); + fprintf( stderr, " -start Starts the service dispatcher after processing all other arguments\n" ); + fprintf( stderr, " -server Runs the service directly as a server (for debugging)\n" ); + fprintf( stderr, " -q Toggles Quiet Mode (no events or output)\n" ); + fprintf( stderr, " -remote Allow remote connections\n" ); + fprintf( stderr, " -cache n Number of mDNS cache entries (defaults to %d)\n", kDNSServiceCacheEntryCountDefault ); + fprintf( stderr, " -h[elp] Display Help/Usage\n" ); + fprintf( stderr, "\n" ); +} + +//=========================================================================================================================== +// ConsoleControlHandler +//=========================================================================================================================== + +static BOOL WINAPI ConsoleControlHandler( DWORD inControlEvent ) +{ + BOOL handled; + OSStatus err; + + handled = FALSE; + switch( inControlEvent ) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + err = ServiceSpecificStop(); + require_noerr( err, exit ); + + handled = TRUE; + break; + + default: + break; + } + +exit: + return( handled ); +} + +//=========================================================================================================================== +// InstallService +//=========================================================================================================================== + +static OSStatus InstallService( LPCTSTR inName, LPCTSTR inDisplayName, LPCTSTR inDescription, LPCTSTR inPath ) +{ + OSStatus err; + SC_HANDLE scm; + SC_HANDLE service; + BOOL ok; + TCHAR fullPath[ MAX_PATH ]; + TCHAR * namePtr; + DWORD size; + + scm = NULL; + service = NULL; + + // Get a full path to the executable since a relative path may have been specified. + + size = GetFullPathName( inPath, MAX_PATH, fullPath, &namePtr ); + err = translate_errno( size > 0, (OSStatus) GetLastError(), kPathErr ); + require_noerr( err, exit ); + + // Create the service and start it. + + scm = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + err = translate_errno( scm, (OSStatus) GetLastError(), kOpenErr ); + require_noerr( err, exit ); + + service = CreateService( scm, inName, inDisplayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_SHARE_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, fullPath, NULL, NULL, kServiceDependencies, + NULL, NULL ); + err = translate_errno( service, (OSStatus) GetLastError(), kDuplicateErr ); + require_noerr( err, exit ); + + err = SetServiceParameters(); + check_noerr( err ); + + if( inDescription ) + { + err = SetServiceInfo( scm, inName, inDescription ); + check_noerr( err ); + } + + ok = StartService( service, 0, NULL ); + err = translate_errno( ok, (OSStatus) GetLastError(), kInUseErr ); + require_noerr( err, exit ); + + ReportStatus( EVENTLOG_SUCCESS, "installed service\n" ); + err = kNoErr; + +exit: + if( service ) + { + CloseServiceHandle( service ); + } + if( scm ) + { + CloseServiceHandle( scm ); + } + return( err ); +} + +//=========================================================================================================================== +// RemoveService +//=========================================================================================================================== + +static OSStatus RemoveService( LPCTSTR inName ) +{ + OSStatus err; + SC_HANDLE scm; + SC_HANDLE service; + BOOL ok; + SERVICE_STATUS status; + + scm = NULL; + service = NULL; + + // Open a connection to the service. + + scm = OpenSCManager( 0, 0, SC_MANAGER_ALL_ACCESS ); + err = translate_errno( scm, (OSStatus) GetLastError(), kOpenErr ); + require_noerr( err, exit ); + + service = OpenService( scm, inName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE ); + err = translate_errno( service, (OSStatus) GetLastError(), kNotFoundErr ); + require_noerr( err, exit ); + + // Stop the service, if it is not already stopped, then delete it. + + ok = QueryServiceStatus( service, &status ); + err = translate_errno( ok, (OSStatus) GetLastError(), kAuthenticationErr ); + require_noerr( err, exit ); + + if( status.dwCurrentState != SERVICE_STOPPED ) + { + ok = ControlService( service, SERVICE_CONTROL_STOP, &status ); + check_translated_errno( ok, (OSStatus) GetLastError(), kAuthenticationErr ); + } + + ok = DeleteService( service ); + err = translate_errno( ok, (OSStatus) GetLastError(), kDeletedErr ); + require_noerr( err, exit ); + + ReportStatus( EVENTLOG_SUCCESS, "Removed service\n" ); + err = ERROR_SUCCESS; + +exit: + if( service ) + { + CloseServiceHandle( service ); + } + if( scm ) + { + CloseServiceHandle( scm ); + } + return( err ); +} + + + +//=========================================================================================================================== +// SetServiceParameters +//=========================================================================================================================== + +static OSStatus SetServiceParameters() +{ + DWORD value; + DWORD valueLen = sizeof(DWORD); + DWORD type; + OSStatus err; + HKEY key; + + key = NULL; + + // + // Add/Open Parameters section under service entry in registry + // + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode, &key ); + require_noerr( err, exit ); + + // + // If the value isn't already there, then we create it + // + err = RegQueryValueEx(key, kServiceManageLLRouting, 0, &type, (LPBYTE) &value, &valueLen); + + if (err != ERROR_SUCCESS) + { + value = 1; + + err = RegSetValueEx( key, kServiceManageLLRouting, 0, REG_DWORD, (const LPBYTE) &value, sizeof(DWORD) ); + require_noerr( err, exit ); + } + +exit: + + if ( key ) + { + RegCloseKey( key ); + } + + return( err ); +} + + + +//=========================================================================================================================== +// GetServiceParameters +//=========================================================================================================================== + +static OSStatus GetServiceParameters() +{ + DWORD value; + DWORD valueLen; + DWORD type; + OSStatus err; + HKEY key; + + key = NULL; + + // + // Add/Open Parameters section under service entry in registry + // + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode, &key ); + require_noerr( err, exit ); + + valueLen = sizeof(DWORD); + err = RegQueryValueEx(key, kServiceManageLLRouting, 0, &type, (LPBYTE) &value, &valueLen); + if (err == ERROR_SUCCESS) + { + gServiceManageLLRouting = (value) ? true : false; + } + + valueLen = sizeof(DWORD); + err = RegQueryValueEx(key, kServiceCacheEntryCount, 0, &type, (LPBYTE) &value, &valueLen); + if (err == ERROR_SUCCESS) + { + gServiceCacheEntryCount = value; + } + +exit: + + if ( key ) + { + RegCloseKey( key ); + } + + return( err ); +} + + +//=========================================================================================================================== +// CheckFirewall +//=========================================================================================================================== + +static OSStatus CheckFirewall() +{ + DWORD value; + DWORD valueLen; + DWORD type; + ENUM_SERVICE_STATUS * lpService = NULL; + SC_HANDLE sc = NULL; + HKEY key = NULL; + BOOL ok; + DWORD bytesNeeded = 0; + DWORD srvCount; + DWORD resumeHandle = 0; + DWORD srvType; + DWORD srvState; + DWORD dwBytes = 0; + DWORD i; + BOOL isRunning = FALSE; + OSStatus err = kUnknownErr; + + // Check to see if the firewall service is running. If it isn't, then + // we want to return immediately + + sc = OpenSCManager( NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE ); + err = translate_errno( sc, GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + srvType = SERVICE_WIN32; + srvState = SERVICE_STATE_ALL; + + for ( ;; ) + { + // Call EnumServicesStatus using the handle returned by OpenSCManager + + ok = EnumServicesStatus ( sc, srvType, srvState, lpService, dwBytes, &bytesNeeded, &srvCount, &resumeHandle ); + + if ( ok || ( GetLastError() != ERROR_MORE_DATA ) ) + { + break; + } + + if ( lpService ) + { + free( lpService ); + } + + dwBytes = bytesNeeded; + + lpService = ( ENUM_SERVICE_STATUS* ) malloc( dwBytes ); + require_action( lpService, exit, err = mStatus_NoMemoryErr ); + } + + err = translate_errno( ok, GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + for ( i = 0; i < srvCount; i++ ) + { + if ( wcscmp( lpService[i].lpServiceName, L"SharedAccess" ) == 0 ) + { + if ( lpService[i].ServiceStatus.dwCurrentState == SERVICE_RUNNING ) + { + isRunning = TRUE; + } + + break; + } + } + + require_action( isRunning, exit, err = kUnknownErr ); + + // Check to see if we've managed the firewall. + // This package might have been installed, then + // the OS was upgraded to SP2 or above. If that's + // the case, then we need to manipulate the firewall + // so networking works correctly. + + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode, &key ); + require_noerr( err, exit ); + + valueLen = sizeof(DWORD); + err = RegQueryValueEx(key, kServiceManageFirewall, 0, &type, (LPBYTE) &value, &valueLen); + + if ((err != ERROR_SUCCESS) || (value == 0)) + { + wchar_t fullPath[ MAX_PATH ]; + DWORD size; + + // Get a full path to the executable + + size = GetModuleFileNameW( NULL, fullPath, MAX_PATH ); + err = translate_errno( size > 0, (OSStatus) GetLastError(), kPathErr ); + require_noerr( err, exit ); + + err = mDNSAddToFirewall(fullPath, kServiceFirewallName); + require_noerr( err, exit ); + + value = 1; + err = RegSetValueEx( key, kServiceManageFirewall, 0, REG_DWORD, (const LPBYTE) &value, sizeof( DWORD ) ); + require_noerr( err, exit ); + } + +exit: + + if ( key ) + { + RegCloseKey( key ); + } + + if ( lpService ) + { + free( lpService ); + } + + if ( sc ) + { + CloseServiceHandle ( sc ); + } + + return( err ); +} + + + +//=========================================================================================================================== +// SetServiceInfo +//=========================================================================================================================== + +static OSStatus SetServiceInfo( SC_HANDLE inSCM, LPCTSTR inServiceName, LPCTSTR inDescription ) +{ + OSStatus err; + SC_LOCK lock; + SC_HANDLE service; + SERVICE_DESCRIPTION description; + SERVICE_FAILURE_ACTIONS actions; + SC_ACTION action; + BOOL ok; + + check( inServiceName ); + check( inDescription ); + + lock = NULL; + service = NULL; + + // Open the database (if not provided) and lock it to prevent other access while re-configuring. + + if( !inSCM ) + { + inSCM = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + err = translate_errno( inSCM, (OSStatus) GetLastError(), kOpenErr ); + require_noerr( err, exit ); + } + + lock = LockServiceDatabase( inSCM ); + err = translate_errno( lock, (OSStatus) GetLastError(), kInUseErr ); + require_noerr( err, exit ); + + // Open a handle to the service. + + service = OpenService( inSCM, inServiceName, SERVICE_CHANGE_CONFIG|SERVICE_START ); + err = translate_errno( service, (OSStatus) GetLastError(), kNotFoundErr ); + require_noerr( err, exit ); + + // Change the description. + + description.lpDescription = (LPTSTR) inDescription; + ok = ChangeServiceConfig2( service, SERVICE_CONFIG_DESCRIPTION, &description ); + err = translate_errno( ok, (OSStatus) GetLastError(), kParamErr ); + require_noerr( err, exit ); + + actions.dwResetPeriod = INFINITE; + actions.lpRebootMsg = NULL; + actions.lpCommand = NULL; + actions.cActions = 1; + actions.lpsaActions = &action; + action.Delay = 500; + action.Type = SC_ACTION_RESTART; + + ok = ChangeServiceConfig2( service, SERVICE_CONFIG_FAILURE_ACTIONS, &actions ); + err = translate_errno( ok, (OSStatus) GetLastError(), kParamErr ); + require_noerr( err, exit ); + + err = ERROR_SUCCESS; + +exit: + // Close the service and release the lock. + + if( service ) + { + CloseServiceHandle( service ); + } + if( lock ) + { + UnlockServiceDatabase( lock ); + } + return( err ); +} + +//=========================================================================================================================== +// ReportStatus +//=========================================================================================================================== + +static void ReportStatus( int inType, const char *inFormat, ... ) +{ + if( !gServiceQuietMode ) + { + va_list args; + + va_start( args, inFormat ); + if( gServiceEventSource ) + { + char s[ 1024 ]; + BOOL ok; + const char * array[ 1 ]; + + vsprintf( s, inFormat, args ); + array[ 0 ] = s; + ok = ReportEventA( gServiceEventSource, (WORD) inType, 0, MDNSRESPONDER_LOG, NULL, 1, 0, array, NULL ); + check_translated_errno( ok, GetLastError(), kUnknownErr ); + } + else + { + int n; + + n = vfprintf( stderr, inFormat, args ); + check( n >= 0 ); + } + va_end( args ); + } +} + +//=========================================================================================================================== +// RunDirect +//=========================================================================================================================== + +int RunDirect( int argc, LPTSTR argv[] ) +{ + OSStatus err; + BOOL initialized; + BOOL ok; + + initialized = FALSE; + + err = SetupServiceEvents(); + require_noerr( err, exit ); + + // Install a Console Control Handler to handle things like control-c signals. + + ok = SetConsoleCtrlHandler( ConsoleControlHandler, TRUE ); + err = translate_errno( ok, (OSStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + err = ServiceSpecificInitialize( argc, argv ); + require_noerr( err, exit ); + initialized = TRUE; + + // Run the service. This does not return until the service quits or is stopped. + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Running service directly\n" ); + + err = ServiceSpecificRun( argc, argv ); + require_noerr( err, exit ); + + // Clean up. + +exit: + if( initialized ) + { + ServiceSpecificFinalize( argc, argv ); + } + + TearDownServiceEvents(); + + return( err ); +} + +#if 0 +#pragma mark - +#endif + +//=========================================================================================================================== +// ServiceMain +//=========================================================================================================================== + +static void WINAPI ServiceMain( DWORD argc, LPTSTR argv[] ) +{ + OSStatus err; + BOOL ok; + + err = SetupServiceEvents(); + require_noerr( err, exit ); + + err = ServiceSetupEventLogging(); + check_noerr( err ); + + err = GetServiceParameters(); + check_noerr( err ); + + // Initialize the service status and register the service control handler with the name of the service. + + gServiceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; + gServiceStatus.dwCurrentState = 0; + gServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_POWEREVENT; + gServiceStatus.dwWin32ExitCode = NO_ERROR; + gServiceStatus.dwServiceSpecificExitCode = NO_ERROR; + gServiceStatus.dwCheckPoint = 0; + gServiceStatus.dwWaitHint = 0; + + gServiceStatusHandle = RegisterServiceCtrlHandlerEx( argv[ 0 ], ServiceControlHandler, NULL ); + err = translate_errno( gServiceStatusHandle, (OSStatus) GetLastError(), kInUseErr ); + require_noerr( err, exit ); + + // Mark the service as starting. + + gServiceStatus.dwCurrentState = SERVICE_START_PENDING; + gServiceStatus.dwCheckPoint = 0; + gServiceStatus.dwWaitHint = 5000; // 5 seconds + ok = SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); + check_translated_errno( ok, GetLastError(), kParamErr ); + + // Run the service. This does not return until the service quits or is stopped. + + err = ServiceRun( (int) argc, argv ); + if( err != kNoErr ) + { + gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + gServiceStatus.dwServiceSpecificExitCode = (DWORD) err; + } + + // Service-specific work is done so mark the service as stopped. + + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + ok = SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); + check_translated_errno( ok, GetLastError(), kParamErr ); + + // Note: The service status handle should not be closed according to Microsoft documentation. + +exit: + + if( gServiceEventSource ) + { + ok = DeregisterEventSource( gServiceEventSource ); + check_translated_errno( ok, GetLastError(), kUnknownErr ); + gServiceEventSource = NULL; + } + + TearDownServiceEvents(); +} + +//=========================================================================================================================== +// ServiceSetupEventLogging +//=========================================================================================================================== + +static OSStatus ServiceSetupEventLogging( void ) +{ + OSStatus err; + HKEY key; + LPCTSTR s; + DWORD typesSupported; + TCHAR path[ MAX_PATH ]; + DWORD n; + + key = NULL; + + // Add/Open source name as a sub-key under the Application key in the EventLog registry key. + + s = TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\") kServiceName; + err = RegCreateKey( HKEY_LOCAL_MACHINE, s, &key ); + require_noerr( err, exit ); + + // Add the name to the EventMessageFile subkey. + + path[ 0 ] = '\0'; + GetModuleFileName( NULL, path, MAX_PATH ); + n = (DWORD) ( ( StrLen( path ) + 1 ) * sizeof( TCHAR ) ); + err = RegSetValueEx( key, TEXT("EventMessageFile"), 0, REG_EXPAND_SZ, (const LPBYTE) path, n ); + require_noerr( err, exit ); + + // Set the supported event types in the TypesSupported subkey. + + typesSupported = 0 + | EVENTLOG_SUCCESS + | EVENTLOG_ERROR_TYPE + | EVENTLOG_WARNING_TYPE + | EVENTLOG_INFORMATION_TYPE + | EVENTLOG_AUDIT_SUCCESS + | EVENTLOG_AUDIT_FAILURE; + err = RegSetValueEx( key, TEXT("TypesSupported"), 0, REG_DWORD, (const LPBYTE) &typesSupported, sizeof( DWORD ) ); + require_noerr( err, exit ); + + // Set up the event source. + + gServiceEventSource = RegisterEventSource( NULL, kServiceName ); + err = translate_errno( gServiceEventSource, (OSStatus) GetLastError(), kParamErr ); + require_noerr( err, exit ); + +exit: + if( key ) + { + RegCloseKey( key ); + } + return( err ); +} + + +//=========================================================================================================================== +// ServiceControlHandler +//=========================================================================================================================== + +static DWORD WINAPI ServiceControlHandler( DWORD inControl, DWORD inEventType, LPVOID inEventData, LPVOID inContext ) +{ + BOOL setStatus; + OSStatus err; + BOOL ok; + + DEBUG_UNUSED( inEventData ); + DEBUG_UNUSED( inContext ); + + setStatus = TRUE; + switch( inControl ) + { + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + + dlog( kDebugLevelInfo, DEBUG_NAME "ServiceControlHandler: SERVICE_CONTROL_STOP|SERVICE_CONTROL_SHUTDOWN\n" ); + + ServiceStop(); + setStatus = FALSE; + break; + + case SERVICE_CONTROL_POWEREVENT: + + if (inEventType == PBT_APMSUSPEND) + { + dlog( kDebugLevelInfo, DEBUG_NAME "ServiceControlHandler: PBT_APMSUSPEND\n" ); + + if ( gPowerSuspendEvent ) + { + ok = SetEvent( gPowerSuspendEvent ); + err = translate_errno( ok, (OSStatus) GetLastError(), kUnknownErr ); + check_noerr( err ); + + switch ( WaitForSingleObject( gPowerSuspendAckEvent, 5 * 1000 ) ) + { + case WAIT_OBJECT_0: + { + // No error + } + break; + + case WAIT_TIMEOUT: + { + dlog( kDebugLevelError, DEBUG_NAME "Timed out waiting for acknowledgement of machine sleep\n" ); + ReportStatus( EVENTLOG_ERROR_TYPE, "Timed out waiting for acknowledgement of machine sleep" ); + } + break; + + default: + { + dlog( kDebugLevelError, DEBUG_NAME "Error waiting for acknowledgement of machine sleep: %d", GetLastError() ); + ReportStatus( EVENTLOG_ERROR_TYPE, "Error waiting for acknowledgement of machine sleep: %d", GetLastError() ); + } + break; + } + } + } + else if (inEventType == PBT_APMRESUMESUSPEND) + { + dlog( kDebugLevelInfo, DEBUG_NAME "ServiceControlHandler: PBT_APMRESUMESUSPEND\n" ); + + if ( gPowerResumeEvent ) + { + ok = SetEvent( gPowerResumeEvent ); + err = translate_errno( ok, (OSStatus) GetLastError(), kUnknownErr ); + check_noerr( err ); + } + } + + break; + + default: + dlog( kDebugLevelNotice, DEBUG_NAME "ServiceControlHandler: event (0x%08X)\n", inControl ); + break; + } + + if( setStatus && gServiceStatusHandle ) + { + ok = SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); + check_translated_errno( ok, GetLastError(), kUnknownErr ); + } + + return NO_ERROR; +} + +//=========================================================================================================================== +// ServiceRun +//=========================================================================================================================== + +static OSStatus ServiceRun( int argc, LPTSTR argv[] ) +{ + OSStatus err; + BOOL initialized; + BOOL ok; + + DEBUG_UNUSED( argc ); + DEBUG_UNUSED( argv ); + + initialized = FALSE; + + // <rdar://problem/5727548> Make the service as running before we call ServiceSpecificInitialize. We've + // had reports that some machines with McAfee firewall installed cause a problem with iTunes installation. + // We think that the firewall product is interferring with code in ServiceSpecificInitialize. So as a + // simple workaround, we'll mark us as running *before* we call ServiceSpecificInitialize. This will unblock + // any installers that are waiting for our state to change. + + gServiceStatus.dwCurrentState = SERVICE_RUNNING; + ok = SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); + check_translated_errno( ok, GetLastError(), kParamErr ); + + // Initialize the service-specific stuff + + while ( 1 ) + { + DWORD ret; + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service initializing" ); + + err = ServiceSpecificInitialize( argc, argv ); + + if ( !err ) + { + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service initialized" ); + break; + } + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service initialization failed with err %d. Waiting %d seconds to retry...", err, kWaitToRetry ); + + ret = WaitForSingleObject( gStopEvent, 1000 * kWaitToRetry ); + + if ( ret == WAIT_OBJECT_0 ) + { + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service received a stop event" ); + goto exit; + } + else if ( ret == WAIT_OBJECT_0 + 1 ) + { + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service received a power suspend event" ); + } + else if ( ret == WAIT_OBJECT_0 + 2 ) + { + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service received a power resume event" ); + } + else if ( ret != WAIT_TIMEOUT ) + { + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service received an error in WaitForSingleObject() : %d, %d", ret, GetLastError() ); + goto exit; + } + } + + initialized = TRUE; + + err = CheckFirewall(); + check_noerr( err ); + + if ( err ) + { + gRetryFirewall = TRUE; + } + + // Run the service-specific stuff. This does not return until the service quits or is stopped. + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service started\n" ); + + err = ServiceSpecificRun( argc, argv ); + require_noerr( err, exit ); + +exit: + + // Service stopped. Clean up and we're done. + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Service stopped (%d)\n", err ); + + if( initialized ) + { + ServiceSpecificFinalize( argc, argv ); + } + + return( err ); +} + +//=========================================================================================================================== +// ServiceStop +//=========================================================================================================================== + +static void ServiceStop( void ) +{ + BOOL ok; + OSStatus err; + + // Signal the event to cause the service to exit. + + if( gServiceStatusHandle ) + { + gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + ok = SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); + check_translated_errno( ok, GetLastError(), kParamErr ); + } + + err = ServiceSpecificStop(); + check_noerr( err ); +} + + +#if 0 +#pragma mark - +#pragma mark == Service Specific == +#endif + +//=========================================================================================================================== +// ServiceSpecificInitialize +//=========================================================================================================================== + +static OSStatus ServiceSpecificInitialize( int argc, LPTSTR argv[] ) +{ + OSStatus err; + + DEBUG_UNUSED( argc ); + DEBUG_UNUSED( argv ); + + mDNSPlatformMemZero( &gMDNSRecord, sizeof gMDNSRecord); + mDNSPlatformMemZero( &gPlatformStorage, sizeof gPlatformStorage); + + gPlatformStorage.reportStatusFunc = ReportStatus; + + err = mDNS_Init( &gMDNSRecord, &gPlatformStorage, gRRCache, RR_CACHE_SIZE, mDNS_Init_AdvertiseLocalAddresses, CoreCallback, mDNS_Init_NoInitCallbackContext); + require_noerr( err, exit); + + err = SetupNotifications(); + check_noerr( err ); + + err = udsserver_init(mDNSNULL, 0); + require_noerr( err, exit); + + SetLLRoute( &gMDNSRecord ); + +exit: + if( err != kNoErr ) + { + ServiceSpecificFinalize( argc, argv ); + } + return( err ); +} + +//=========================================================================================================================== +// ServiceSpecificRun +//=========================================================================================================================== + +static OSStatus ServiceSpecificRun( int argc, LPTSTR argv[] ) +{ + mDNSBool done = mDNSfalse; + mStatus err = mStatus_NoError; + + DEBUG_UNUSED( argc ); + DEBUG_UNUSED( argv ); + + err = SetupInterfaceList( &gMDNSRecord ); + check( !err ); + + err = uDNS_SetupDNSConfig( &gMDNSRecord ); + check( !err ); + + while( !done ) + { + static mDNSs32 RepeatedBusy = 0; + mDNSs32 nextTimerEvent; + mStatus err; + + // Give the mDNS core a chance to do its work and determine next event time. + + nextTimerEvent = udsserver_idle( mDNS_Execute( &gMDNSRecord ) - mDNS_TimeNow( &gMDNSRecord ) ); + + if ( nextTimerEvent < 0) nextTimerEvent = 0; + else if ( nextTimerEvent > (0x7FFFFFFF / 1000)) nextTimerEvent = 0x7FFFFFFF / mDNSPlatformOneSecond; + else nextTimerEvent = ( nextTimerEvent * 1000) / mDNSPlatformOneSecond; + + // Debugging sanity check, to guard against CPU spins + + if ( nextTimerEvent > 0 ) + { + RepeatedBusy = 0; + } + else + { + nextTimerEvent = 1; + + if ( ++RepeatedBusy >= mDNSPlatformOneSecond ) + { + ShowTaskSchedulingError( &gMDNSRecord ); + RepeatedBusy = 0; + } + } + + if ( gMDNSRecord.ShutdownTime ) + { + mDNSs32 now = mDNS_TimeNow( &gMDNSRecord ); + + if ( mDNS_ExitNow( &gMDNSRecord, now ) ) + { + mDNS_FinalExit( &gMDNSRecord ); + done = TRUE; + break; + } + + if ( nextTimerEvent - gMDNSRecord.ShutdownTime >= 0 ) + { + nextTimerEvent = gMDNSRecord.ShutdownTime; + } + } + + err = mDNSPoll( nextTimerEvent ); + + if ( err ) + { + Sleep( 3 * 1000 ); + + err = SetupInterfaceList( &gMDNSRecord ); + check( !err ); + + err = uDNS_SetupDNSConfig( &gMDNSRecord ); + check( !err ); + + break; + } + } + + return ( err ); +} + + +//=========================================================================================================================== +// ServiceSpecificStop +//=========================================================================================================================== + +static OSStatus ServiceSpecificStop( void ) +{ + OSStatus err; + BOOL ok; + + ok = SetEvent(gStopEvent); + err = translate_errno( ok, (OSStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + +exit: + + return( err ); +} + +//=========================================================================================================================== +// ServiceSpecificFinalize +//=========================================================================================================================== + +static void ServiceSpecificFinalize( int argc, LPTSTR argv[] ) +{ + DEBUG_UNUSED( argc ); + DEBUG_UNUSED( argv ); + + // + // clean up the notifications + // + TearDownNotifications(); + + // + // clean up loaded library + // + + if( gIPHelperLibraryInstance ) + { + gGetIpInterfaceEntryFunctionPtr = NULL; + + FreeLibrary( gIPHelperLibraryInstance ); + gIPHelperLibraryInstance = NULL; + } +} + + +//=========================================================================================================================== +// SetupServiceEvents +//=========================================================================================================================== + +mDNSlocal mStatus SetupServiceEvents() +{ + mStatus err; + + // Stop Event + + gStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gStopEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + +exit: + + if ( err ) + { + TearDownServiceEvents(); + } + + return err; +} + + +//=========================================================================================================================== +// TearDownServiceNotifications +//=========================================================================================================================== + +mDNSlocal mStatus TearDownServiceEvents() +{ + if ( gStopEvent ) + { + CloseHandle( gStopEvent ); + gStopEvent = NULL; + } + + return mStatus_NoError; +} + + +//=========================================================================================================================== +// SetupNotifications +//=========================================================================================================================== + +mDNSlocal mStatus SetupNotifications() +{ + mStatus err; + SocketRef sock; + unsigned long param; + int inBuffer; + int outBuffer; + DWORD outSize; + + require_action( gStopEvent, exit, err = kUnknownErr ); + err = mDNSPollRegisterEvent( gStopEvent, StopNotification, NULL ); + require_noerr( err, exit ); + + // Power Suspend + + gPowerSuspendEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gPowerSuspendEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gPowerSuspendEvent, PowerSuspendNotification, NULL ); + require_noerr( err, exit ); + + // Power Suspend Ack + + gPowerSuspendAckEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + err = translate_errno( gPowerSuspendAckEvent, ( mStatus ) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + // Power Resume + + gPowerResumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gPowerResumeEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gPowerResumeEvent, PowerResumeNotification, NULL ); + require_noerr( err, exit ); + + // Register to listen for address list changes. + + sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + err = translate_errno( IsValidSocket( sock ), errno_compat(), kUnknownErr ); + require_noerr( err, exit ); + gInterfaceListChangedSocket = sock; + + // Make the socket non-blocking so the WSAIoctl returns immediately with WSAEWOULDBLOCK. It will set the event + // when a change to the interface list is detected. + + param = 1; + err = ioctlsocket( sock, FIONBIO, ¶m ); + err = translate_errno( err == 0, errno_compat(), kUnknownErr ); + require_noerr( err, exit ); + + inBuffer = 0; + outBuffer = 0; + err = WSAIoctl( sock, SIO_ADDRESS_LIST_CHANGE, &inBuffer, 0, &outBuffer, 0, &outSize, NULL, NULL ); + if( err < 0 ) + { + check( errno_compat() == WSAEWOULDBLOCK ); + } + + err = mDNSPollRegisterSocket( sock, FD_ADDRESS_LIST_CHANGE, InterfaceListNotification, NULL ); + require_noerr( err, exit ); + + gDescChangedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + err = translate_errno( gDescChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + err = RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\parameters"), 0, KEY_READ, &gDescKey); + check_translated_errno( err == 0, errno_compat(), kNameErr ); + + if ( gDescKey != NULL ) + { + err = RegNotifyChangeKeyValue( gDescKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET, gDescChangedEvent, TRUE); + require_noerr( err, exit ); + } + + err = mDNSPollRegisterEvent( gDescChangedEvent, ComputerDescriptionNotification, NULL ); + require_noerr( err, exit ); + + // This will catch all changes to tcp/ip networking, including changes to the domain search list + + gTcpipChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gTcpipChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = RegCreateKey( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"), &gTcpipKey ); + require_noerr( err, exit ); + err = RegNotifyChangeKeyValue( gTcpipKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gTcpipChangedEvent, TRUE); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gTcpipChangedEvent, TCPChangedNotification, NULL ); + require_noerr( err, exit ); + + // This will catch all changes to ddns configuration + + gDdnsChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gDdnsChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode TEXT("\\DynDNS\\Setup"), &gDdnsKey ); + require_noerr( err, exit ); + err = RegNotifyChangeKeyValue( gDdnsKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gDdnsChangedEvent, TRUE); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gDdnsChangedEvent, DDNSChangedNotification, NULL ); + require_noerr( err, exit ); + + // This will catch all changes to file sharing + + gFileSharingChangedEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + err = translate_errno( gFileSharingChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + err = RegCreateKey( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\Shares"), &gFileSharingKey ); + + // Just to make sure that initialization doesn't fail on some old OS + // that doesn't have this key, we'll only add the notification if + // the key exists. + + if ( !err ) + { + err = RegNotifyChangeKeyValue( gFileSharingKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gFileSharingChangedEvent, TRUE); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gFileSharingChangedEvent, FileSharingChangedNotification, NULL ); + require_noerr( err, exit ); + } + else + { + err = mStatus_NoError; + } + + // This will catch changes to the Windows firewall + + gFirewallChangedEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + err = translate_errno( gFirewallChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + // Just to make sure that initialization doesn't fail on some old OS + // that doesn't have this key, we'll only add the notification if + // the key exists. + + err = RegCreateKey( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\FirewallRules"), &gFirewallKey ); + + if ( !err ) + { + err = RegNotifyChangeKeyValue( gFirewallKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gFirewallChangedEvent, TRUE); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gFirewallChangedEvent, FirewallChangedNotification, NULL ); + require_noerr( err, exit ); + } + else + { + err = mStatus_NoError; + } + + // This will catch all changes to advertised services configuration + + gAdvertisedServicesChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + err = translate_errno( gAdvertisedServicesChangedEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode TEXT("\\Services"), &gAdvertisedServicesKey ); + require_noerr( err, exit ); + err = RegNotifyChangeKeyValue( gAdvertisedServicesKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gAdvertisedServicesChangedEvent, TRUE); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gAdvertisedServicesChangedEvent, AdvertisedServicesChangedNotification, NULL ); + require_noerr( err, exit ); + + // SPSWakeup timer + + gSPSWakeupEvent = CreateWaitableTimer( NULL, FALSE, NULL ); + err = translate_errno( gSPSWakeupEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gSPSWakeupEvent, SPSWakeupNotification, NULL ); + require_noerr( err, exit ); + + // SPSSleep timer + + gSPSSleepEvent = CreateWaitableTimer( NULL, FALSE, NULL ); + err = translate_errno( gSPSSleepEvent, (mStatus) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + err = mDNSPollRegisterEvent( gSPSSleepEvent, SPSSleepNotification, NULL ); + require_noerr( err, exit ); + +exit: + if( err ) + { + TearDownNotifications(); + } + return( err ); +} + +//=========================================================================================================================== +// TearDownNotifications +//=========================================================================================================================== + +mDNSlocal mStatus TearDownNotifications() +{ + if( IsValidSocket( gInterfaceListChangedSocket ) ) + { + mDNSPollUnregisterSocket( gInterfaceListChangedSocket ); + + close_compat( gInterfaceListChangedSocket ); + gInterfaceListChangedSocket = kInvalidSocketRef; + } + + if ( gDescChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gDescChangedEvent ); + CloseHandle( gDescChangedEvent ); + gDescChangedEvent = NULL; + } + + if ( gDescKey != NULL ) + { + RegCloseKey( gDescKey ); + gDescKey = NULL; + } + + if ( gTcpipChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gTcpipChangedEvent ); + CloseHandle( gTcpipChangedEvent ); + gTcpipChangedEvent = NULL; + } + + if ( gDdnsChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gDdnsChangedEvent ); + CloseHandle( gDdnsChangedEvent ); + gDdnsChangedEvent = NULL; + } + + if ( gDdnsKey != NULL ) + { + RegCloseKey( gDdnsKey ); + gDdnsKey = NULL; + } + + if ( gFileSharingChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gFileSharingChangedEvent ); + CloseHandle( gFileSharingChangedEvent ); + gFileSharingChangedEvent = NULL; + } + + if ( gFileSharingKey != NULL ) + { + RegCloseKey( gFileSharingKey ); + gFileSharingKey = NULL; + } + + if ( gFirewallChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gFirewallChangedEvent ); + CloseHandle( gFirewallChangedEvent ); + gFirewallChangedEvent = NULL; + } + + if ( gFirewallKey != NULL ) + { + RegCloseKey( gFirewallKey ); + gFirewallKey = NULL; + } + + if ( gAdvertisedServicesChangedEvent != NULL ) + { + mDNSPollUnregisterEvent( gAdvertisedServicesChangedEvent ); + CloseHandle( gAdvertisedServicesChangedEvent ); + gAdvertisedServicesChangedEvent = NULL; + } + + if ( gAdvertisedServicesKey != NULL ) + { + RegCloseKey( gAdvertisedServicesKey ); + gAdvertisedServicesKey = NULL; + } + + if ( gSPSWakeupEvent ) + { + mDNSPollUnregisterEvent( gSPSWakeupEvent ); + CloseHandle( gSPSWakeupEvent ); + gSPSWakeupEvent = NULL; + } + + if ( gSPSSleepEvent ) + { + mDNSPollUnregisterEvent( gSPSSleepEvent ); + CloseHandle( gSPSSleepEvent ); + gSPSSleepEvent = NULL; + } + + if ( gPowerResumeEvent ) + { + mDNSPollUnregisterEvent( gPowerResumeEvent ); + CloseHandle( gPowerResumeEvent ); + gPowerResumeEvent = NULL; + } + + if ( gPowerSuspendAckEvent ) + { + CloseHandle( gPowerSuspendAckEvent ); + gPowerSuspendAckEvent = NULL; + } + + if ( gPowerSuspendEvent ) + { + mDNSPollUnregisterEvent( gPowerSuspendEvent ); + CloseHandle( gPowerSuspendEvent ); + gPowerSuspendEvent = NULL; + } + + if ( gStopEvent ) + { + mDNSPollUnregisterEvent( gStopEvent ); + } + + return( mStatus_NoError ); +} + + +mDNSlocal void CALLBACK +StopNotification( HANDLE event, void *context ) +{ + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + dlog( kDebugLevelVerbose, DEBUG_NAME "stopping...\n" ); + udsserver_exit(); + mDNS_StartExit( &gMDNSRecord ); +} + + +mDNSlocal void CALLBACK +PowerSuspendNotification( HANDLE event, void * context ) +{ + LARGE_INTEGER timeout; + BOOL ok; + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + dlog( kDebugLevelInfo, DEBUG_NAME "PowerSuspendNotification\n" ); + + gMDNSRecord.SystemWakeOnLANEnabled = SystemWakeForNetworkAccess( &timeout ); + + if ( gMDNSRecord.SystemWakeOnLANEnabled ) + { + ok = SetWaitableTimer( gSPSWakeupEvent, &timeout, 0, NULL, NULL, TRUE ); + check( ok ); + } + + mDNSCoreMachineSleep(&gMDNSRecord, TRUE); + + ok = SetEvent( gPowerSuspendAckEvent ); + + if ( !ok ) + { + dlog( kDebugLevelError, DEBUG_NAME "PowerSuspendNotification: error while setting acknowledgement: %d", GetLastError() ); + ReportStatus( EVENTLOG_ERROR_TYPE, "PowerSuspendNotification: error while setting acknowledgement: %d", GetLastError() ); + } +} + + +mDNSlocal void CALLBACK +PowerResumeNotification( HANDLE event, void * context ) +{ + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + dlog( kDebugLevelInfo, DEBUG_NAME "PowerResumeNotification\n" ); + + if ( gSPSWakeupEvent ) + { + CancelWaitableTimer( gSPSWakeupEvent ); + } + + if ( gSPSSleepEvent ) + { + CancelWaitableTimer( gSPSSleepEvent ); + } + + mDNSCoreMachineSleep(&gMDNSRecord, FALSE); +} + + + +mDNSlocal void CALLBACK +InterfaceListNotification( SOCKET socket, LPWSANETWORKEVENTS event, void *context ) +{ + int inBuffer; + int outBuffer; + DWORD outSize; + int err; + + DEBUG_UNUSED( socket ); + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + // It would be nice to come up with a more elegant solution to this, but it seems that + // GetAdaptersAddresses doesn't always stay in sync after network changed events. So as + // as a simple workaround, we'll pause for a couple of seconds before processing the change. + + // We arrived at 2 secs by trial and error. We could reproduce the problem after sleeping + // for 500 msec and 750 msec, but couldn't after sleeping for 1 sec. We added another + // second on top of that to account for machine load or some other exigency. + + Sleep( 2000 ); + + // Interface list changed event. Break out of the inner loop to re-setup the wait list. + + InterfaceListDidChange( &gMDNSRecord ); + + // reset the event handler + inBuffer = 0; + outBuffer = 0; + err = WSAIoctl( gInterfaceListChangedSocket, SIO_ADDRESS_LIST_CHANGE, &inBuffer, 0, &outBuffer, 0, &outSize, NULL, NULL ); + if( err < 0 ) + { + check( errno_compat() == WSAEWOULDBLOCK ); + } +} + + +mDNSlocal void CALLBACK +ComputerDescriptionNotification( HANDLE event, void *context ) +{ + // The computer description might have changed + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + ComputerDescriptionDidChange( &gMDNSRecord ); + udsserver_handle_configchange( &gMDNSRecord ); + + // and reset the event handler + if ( ( gDescKey != NULL ) && ( gDescChangedEvent != NULL ) ) + { + int err = RegNotifyChangeKeyValue( gDescKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET, gDescChangedEvent, TRUE); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +TCPChangedNotification( HANDLE event, void *context ) +{ + // The TCP/IP might have changed + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + TCPIPConfigDidChange( &gMDNSRecord ); + udsserver_handle_configchange( &gMDNSRecord ); + + // and reset the event handler + + if ( ( gTcpipKey != NULL ) && ( gTcpipChangedEvent ) ) + { + int err = RegNotifyChangeKeyValue( gTcpipKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gTcpipChangedEvent, TRUE ); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +DDNSChangedNotification( HANDLE event, void *context ) +{ + // The DynDNS config might have changed + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + DynDNSConfigDidChange( &gMDNSRecord ); + udsserver_handle_configchange( &gMDNSRecord ); + + // and reset the event handler + + if ((gDdnsKey != NULL) && (gDdnsChangedEvent)) + { + int err = RegNotifyChangeKeyValue(gDdnsKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gDdnsChangedEvent, TRUE); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +FileSharingChangedNotification( HANDLE event, void *context ) +{ + // File sharing changed + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + FileSharingDidChange( &gMDNSRecord ); + + // and reset the event handler + + if ((gFileSharingKey != NULL) && (gFileSharingChangedEvent)) + { + int err = RegNotifyChangeKeyValue(gFileSharingKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gFileSharingChangedEvent, TRUE); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +FirewallChangedNotification( HANDLE event, void *context ) +{ + // Firewall configuration changed + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + FirewallDidChange( &gMDNSRecord ); + + // and reset the event handler + + if ((gFirewallKey != NULL) && (gFirewallChangedEvent)) + { + int err = RegNotifyChangeKeyValue(gFirewallKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gFirewallChangedEvent, TRUE); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +AdvertisedServicesChangedNotification( HANDLE event, void *context ) +{ + // Ultimately we'll want to manage multiple services, but right now the only service + // we'll be managing is SMB. + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + FileSharingDidChange( &gMDNSRecord ); + + // and reset the event handler + + if ( ( gAdvertisedServicesKey != NULL ) && ( gAdvertisedServicesChangedEvent ) ) + { + int err = RegNotifyChangeKeyValue(gAdvertisedServicesKey, TRUE, REG_NOTIFY_CHANGE_NAME|REG_NOTIFY_CHANGE_LAST_SET, gAdvertisedServicesChangedEvent, TRUE); + check_noerr( err ); + } +} + + +mDNSlocal void CALLBACK +SPSWakeupNotification( HANDLE event, void *context ) +{ + LARGE_INTEGER timeout; + + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Maintenance wake" ); + + timeout.QuadPart = kSPSMaintenanceWakePeriod; + timeout.QuadPart *= kSecondsTo100NSUnits; + + SetWaitableTimer( gSPSSleepEvent, &timeout, 0, NULL, NULL, TRUE ); +} + + +mDNSlocal void CALLBACK +SPSSleepNotification( HANDLE event, void *context ) +{ + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + ReportStatus( EVENTLOG_INFORMATION_TYPE, "Returning to sleep after maintenance wake" ); + + // Calling SetSuspendState() doesn't invoke our sleep handlers, so we'll + // call HandlePowerSuspend() explicity. This will reset the + // maintenance wake timers. + + PowerSuspendNotification( gPowerSuspendEvent, NULL ); + SetSuspendState( FALSE, FALSE, FALSE ); +} + + +//=========================================================================================================================== +// CoreCallback +//=========================================================================================================================== + +static void +CoreCallback(mDNS * const inMDNS, mStatus status) +{ + if (status == mStatus_ConfigChanged) + { + SetLLRoute( inMDNS ); + } +} + + +//=========================================================================================================================== +// UDSAcceptNotification +//=========================================================================================================================== + +mDNSlocal void CALLBACK +UDSAcceptNotification( SOCKET sock, LPWSANETWORKEVENTS event, void *context ) +{ + ( void ) sock; + ( void ) event; + ( void ) context; + + if ( gUDSCallback ) + { + gUDSCallback( ( int ) gUDSSocket, 0, context ); + } +} + + +//=========================================================================================================================== +// UDSReadNotification +//=========================================================================================================================== + +mDNSlocal void CALLBACK +UDSReadNotification( SOCKET sock, LPWSANETWORKEVENTS event, void *context ) +{ + TCPSocket *tcpSock = ( TCPSocket* ) context; + + ( void ) sock; + ( void ) event; + + if ( tcpSock ) + { + tcpSock->userCallback( ( int ) tcpSock->fd, 0, tcpSock->userContext ); + } +} + + +//=========================================================================================================================== +// udsSupportAddFDToEventLoop +//=========================================================================================================================== + +mStatus +udsSupportAddFDToEventLoop( SocketRef fd, udsEventCallback callback, void *context, void **platform_data) +{ + mStatus err = mStatus_NoError; + + // We are using some knowledge of what is being passed to us here. If the fd is a listen socket, + // then the "context" parameter is NULL. If it is an actual read/write socket, then the "context" + // parameter is not null. + + if ( context ) + { + TCPSocket * sock; + + sock = malloc( sizeof( TCPSocket ) ); + require_action( sock, exit, err = mStatus_NoMemoryErr ); + mDNSPlatformMemZero( sock, sizeof( TCPSocket ) ); + + sock->fd = (SOCKET) fd; + sock->userCallback = callback; + sock->userContext = context; + sock->m = &gMDNSRecord; + + *platform_data = sock; + + err = mDNSPollRegisterSocket( sock->fd, FD_READ | FD_CLOSE, UDSReadNotification, sock ); + require_noerr( err, exit ); + } + else + { + gUDSSocket = fd; + gUDSCallback = callback; + + err = mDNSPollRegisterSocket( gUDSSocket, FD_ACCEPT | FD_CLOSE, UDSAcceptNotification, NULL ); + require_noerr( err, exit ); + } + +exit: + + return err; +} + + +int +udsSupportReadFD( SocketRef fd, char *buf, int len, int flags, void *platform_data ) +{ + TCPSocket * sock; + mDNSBool closed; + int ret; + + ( void ) flags; + + sock = ( TCPSocket* ) platform_data; + require_action( sock, exit, ret = -1 ); + require_action( sock->fd == fd, exit, ret = -1 ); + + ret = mDNSPlatformReadTCP( sock, buf, len, &closed ); + + if ( closed ) + { + ret = 0; + } + else if ( !ret && ( WSAGetLastError() == WSAEWOULDBLOCK ) ) + { + // mDNSPlatformReadTCP will return 0 if it gets WSAEWOULDBLOCK, but + // that caller of this routine interprets that as close connection. + // We'll fix that by returning -1 in that case. + + ret = -1; + } + +exit: + + return ret; +} + + +mStatus +udsSupportRemoveFDFromEventLoop( SocketRef fd, void *platform_data) // Note: This also CLOSES the socket +{ + mStatus err = kNoErr; + + mDNSPollUnregisterSocket( fd ); + + if ( platform_data != NULL ) + { + TCPSocket * sock; + + dlog( kDebugLevelInfo, DEBUG_NAME "session closed\n" ); + sock = ( TCPSocket* ) platform_data; + check( sock->fd == fd ); + mDNSPlatformTCPCloseConnection( sock ); + } + + return err; +} + + +mDNSexport void RecordUpdatedNiceLabel(mDNS *const m, mDNSs32 delay) + { + (void)m; + (void)delay; + // No-op, for now + } + + +//=========================================================================================================================== +// SystemWakeForNetworkAccess +//=========================================================================================================================== + +mDNSu8 +SystemWakeForNetworkAccess( LARGE_INTEGER * timeout ) +{ + HKEY key = NULL; + DWORD dwSize; + DWORD enabled; + mDNSu8 ok; + SYSTEM_POWER_STATUS powerStatus; + time_t startTime; + time_t nextWakeupTime; + int delta; + DWORD err; + + dlog( kDebugLevelInfo, DEBUG_NAME "SystemWakeForNetworkAccess\n" ); + + // Make sure we have a timer + + require_action( gSPSWakeupEvent != NULL, exit, ok = FALSE ); + require_action( gSPSSleepEvent != NULL, exit, ok = FALSE ); + + // Make sure the user enabled bonjour sleep proxy client + + err = RegCreateKey( HKEY_LOCAL_MACHINE, kServiceParametersNode L"\\Power Management", &key ); + require_action( !err, exit, ok = FALSE ); + dwSize = sizeof( DWORD ); + err = RegQueryValueEx( key, L"Enabled", NULL, NULL, (LPBYTE) &enabled, &dwSize ); + require_action( !err, exit, ok = FALSE ); + require_action( enabled, exit, ok = FALSE ); + + // Make sure machine is on AC power + + ok = ( mDNSu8 ) GetSystemPowerStatus( &powerStatus ); + require_action( ok, exit, ok = FALSE ); + require_action( powerStatus.ACLineStatus == AC_LINE_ONLINE, exit, ok = FALSE ); + + // Now make sure we have a network interface that does wake-on-lan + + ok = ( mDNSu8 ) IsWOMPEnabled( &gMDNSRecord ); + require_action( ok, exit, ok = FALSE ); + + // Now make sure we have advertised services. Doesn't make sense to + // enable sleep proxy if we have no multicast services that could + // potentially wake us up. + + ok = ( mDNSu8 ) mDNSCoreHaveAdvertisedMulticastServices( &gMDNSRecord ); + require_action( ok, exit, ok = FALSE ); + + // Calculate next wake up time + + startTime = time( NULL ); // Seconds since midnight January 1, 1970 + nextWakeupTime = startTime + ( 120 * 60 ); // 2 hours later + + if ( gMDNSRecord.p->nextDHCPLeaseExpires < nextWakeupTime ) + { + nextWakeupTime = gMDNSRecord.p->nextDHCPLeaseExpires; + } + + // Finally calculate the next relative wakeup time + + delta = ( int )( ( ( double )( nextWakeupTime - startTime ) ) * 0.9 ); + ReportStatus( EVENTLOG_INFORMATION_TYPE, "enabling sleep proxy client with next maintenance wake in %d seconds", delta ); + + // Convert seconds to 100 nanosecond units expected by SetWaitableTimer + + timeout->QuadPart = -delta; + timeout->QuadPart *= kSecondsTo100NSUnits; + + ok = TRUE; + +exit: + + if ( key ) + { + RegCloseKey( key ); + } + + return ok; +} + + +//=========================================================================================================================== +// HaveRoute +//=========================================================================================================================== + +static bool +HaveRoute( PMIB_IPFORWARDROW rowExtant, unsigned long addr, unsigned long metric ) +{ + PMIB_IPFORWARDTABLE pIpForwardTable = NULL; + DWORD dwSize = 0; + BOOL bOrder = FALSE; + OSStatus err; + bool found = false; + unsigned long int i; + + // + // Find out how big our buffer needs to be. + // + err = GetIpForwardTable(NULL, &dwSize, bOrder); + require_action( err == ERROR_INSUFFICIENT_BUFFER, exit, err = kUnknownErr ); + + // + // Allocate the memory for the table + // + pIpForwardTable = (PMIB_IPFORWARDTABLE) malloc( dwSize ); + require_action( pIpForwardTable, exit, err = kNoMemoryErr ); + + // + // Now get the table. + // + err = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); + require_noerr( err, exit ); + + // + // Search for the row in the table we want. + // + for ( i = 0; i < pIpForwardTable->dwNumEntries; i++) + { + if ( ( pIpForwardTable->table[i].dwForwardDest == addr ) && ( !metric || ( pIpForwardTable->table[i].dwForwardMetric1 == metric ) ) ) + { + memcpy( rowExtant, &(pIpForwardTable->table[i]), sizeof(*rowExtant) ); + found = true; + break; + } + } + +exit: + + if ( pIpForwardTable != NULL ) + { + free(pIpForwardTable); + } + + return found; +} + + +//=========================================================================================================================== +// IsValidAddress +//=========================================================================================================================== + +static bool +IsValidAddress( const char * addr ) +{ + return ( addr && ( strcmp( addr, "0.0.0.0" ) != 0 ) ) ? true : false; +} + + +//=========================================================================================================================== +// GetAdditionalMetric +//=========================================================================================================================== + +static ULONG +GetAdditionalMetric( DWORD ifIndex ) +{ + ULONG metric = 0; + + if( !gIPHelperLibraryInstance ) + { + gIPHelperLibraryInstance = LoadLibrary( TEXT( "Iphlpapi" ) ); + + gGetIpInterfaceEntryFunctionPtr = + (GetIpInterfaceEntryFunctionPtr) GetProcAddress( gIPHelperLibraryInstance, "GetIpInterfaceEntry" ); + + if( !gGetIpInterfaceEntryFunctionPtr ) + { + BOOL ok; + + ok = FreeLibrary( gIPHelperLibraryInstance ); + check_translated_errno( ok, GetLastError(), kUnknownErr ); + gIPHelperLibraryInstance = NULL; + } + } + + if ( gGetIpInterfaceEntryFunctionPtr ) + { + MIB_IPINTERFACE_ROW row; + DWORD err; + + ZeroMemory( &row, sizeof( MIB_IPINTERFACE_ROW ) ); + row.Family = AF_INET; + row.InterfaceIndex = ifIndex; + err = gGetIpInterfaceEntryFunctionPtr( &row ); + require_noerr( err, exit ); + metric = row.Metric + 256; + } + +exit: + + return metric; +} + + +//=========================================================================================================================== +// SetLLRoute +//=========================================================================================================================== + +static OSStatus +SetLLRoute( mDNS * const inMDNS ) +{ + OSStatus err = kNoErr; + + DEBUG_UNUSED( inMDNS ); + + // + // <rdar://problem/4096464> Don't call SetLLRoute on loopback + // <rdar://problem/6885843> Default route on Windows 7 breaks network connectivity + // + // Don't mess w/ the routing table on Vista and later OSes, as + // they have a permanent route to link-local addresses. Otherwise, + // set a route to link local addresses (169.254.0.0) + // + if ( ( inMDNS->p->osMajorVersion < 6 ) && gServiceManageLLRouting && !gPlatformStorage.registeredLoopback4 ) + { + DWORD ifIndex; + MIB_IPFORWARDROW rowExtant; + bool addRoute; + MIB_IPFORWARDROW row; + + ZeroMemory(&row, sizeof(row)); + + err = GetRouteDestination(&ifIndex, &row.dwForwardNextHop); + require_noerr( err, exit ); + row.dwForwardDest = inet_addr(kLLNetworkAddr); + row.dwForwardIfIndex = ifIndex; + row.dwForwardMask = inet_addr(kLLNetworkAddrMask); + row.dwForwardType = 3; + row.dwForwardProto = MIB_IPPROTO_NETMGMT; + row.dwForwardAge = 0; + row.dwForwardPolicy = 0; + row.dwForwardMetric1 = 20 + GetAdditionalMetric( ifIndex ); + row.dwForwardMetric2 = (DWORD) - 1; + row.dwForwardMetric3 = (DWORD) - 1; + row.dwForwardMetric4 = (DWORD) - 1; + row.dwForwardMetric5 = (DWORD) - 1; + + addRoute = true; + + // + // check to make sure we don't already have a route + // + if ( HaveRoute( &rowExtant, inet_addr( kLLNetworkAddr ), 0 ) ) + { + // + // set the age to 0 so that we can do a memcmp. + // + rowExtant.dwForwardAge = 0; + + // + // check to see if this route is the same as our route + // + if (memcmp(&row, &rowExtant, sizeof(row)) != 0) + { + // + // if it isn't then delete this entry + // + DeleteIpForwardEntry(&rowExtant); + } + else + { + // + // else it is, so we don't want to create another route + // + addRoute = false; + } + } + + if (addRoute && row.dwForwardNextHop) + { + err = CreateIpForwardEntry(&row); + check_noerr( err ); + } + } + +exit: + + return ( err ); +} + + +//=========================================================================================================================== +// GetRouteDestination +//=========================================================================================================================== + +static OSStatus +GetRouteDestination(DWORD * ifIndex, DWORD * address) +{ + struct in_addr ia; + IP_ADAPTER_INFO * pAdapterInfo = NULL; + IP_ADAPTER_INFO * pAdapter = NULL; + ULONG bufLen; + mDNSBool done = mDNSfalse; + OSStatus err; + + // + // GetBestInterface will fail if there is no default gateway + // configured. If that happens, we will just take the first + // interface in the list. MSDN support says there is no surefire + // way to manually determine what the best interface might + // be for a particular network address. + // + ia.s_addr = inet_addr(kLLNetworkAddr); + err = GetBestInterface(*(IPAddr*) &ia, ifIndex); + + if (err) + { + *ifIndex = 0; + } + + // + // Make an initial call to GetAdaptersInfo to get + // the necessary size into the bufLen variable + // + err = GetAdaptersInfo( NULL, &bufLen); + require_action( err == ERROR_BUFFER_OVERFLOW, exit, err = kUnknownErr ); + + pAdapterInfo = (IP_ADAPTER_INFO*) malloc( bufLen ); + require_action( pAdapterInfo, exit, err = kNoMemoryErr ); + + err = GetAdaptersInfo( pAdapterInfo, &bufLen); + require_noerr( err, exit ); + + pAdapter = pAdapterInfo; + err = kUnknownErr; + + // <rdar://problem/3718122> + // <rdar://problem/5652098> + // + // Look for the Nortel VPN virtual interface, along with Juniper virtual interface. + // + // If these interfaces are active (i.e., has a non-zero IP Address), + // then we want to disable routing table modifications. + + while (pAdapter) + { + if ( ( IsNortelVPN( pAdapter ) || IsJuniperVPN( pAdapter ) || IsCiscoVPN( pAdapter ) ) && + ( inet_addr( pAdapter->IpAddressList.IpAddress.String ) != 0 ) ) + { + dlog( kDebugLevelTrace, DEBUG_NAME "disabling routing table management due to VPN incompatibility" ); + goto exit; + } + + pAdapter = pAdapter->Next; + } + + while ( !done ) + { + pAdapter = pAdapterInfo; + err = kUnknownErr; + + while (pAdapter) + { + // If we don't have an interface selected, choose the first one that is of type ethernet and + // has a valid IP Address + + if ((pAdapter->Type == MIB_IF_TYPE_ETHERNET) && ( IsValidAddress( pAdapter->IpAddressList.IpAddress.String ) ) && (!(*ifIndex) || (pAdapter->Index == (*ifIndex)))) + { + *address = inet_addr( pAdapter->IpAddressList.IpAddress.String ); + *ifIndex = pAdapter->Index; + err = kNoErr; + break; + } + + pAdapter = pAdapter->Next; + } + + // If we found the right interface, or we weren't trying to find a specific interface then we're done + + if ( !err || !( *ifIndex) ) + { + done = mDNStrue; + } + + // Otherwise, try again by wildcarding the interface + + else + { + *ifIndex = 0; + } + } + +exit: + + if ( pAdapterInfo != NULL ) + { + free( pAdapterInfo ); + } + + return( err ); +} + + +static bool +IsNortelVPN( IP_ADAPTER_INFO * pAdapter ) +{ + return ((pAdapter->Type == MIB_IF_TYPE_ETHERNET) && + (pAdapter->AddressLength == 6) && + (pAdapter->Address[0] == 0x44) && + (pAdapter->Address[1] == 0x45) && + (pAdapter->Address[2] == 0x53) && + (pAdapter->Address[3] == 0x54) && + (pAdapter->Address[4] == 0x42) && + (pAdapter->Address[5] == 0x00)) ? true : false; +} + + +static bool +IsJuniperVPN( IP_ADAPTER_INFO * pAdapter ) +{ + return ( strnistr( pAdapter->Description, "Juniper", sizeof( pAdapter->Description ) ) != NULL ) ? true : false; +} + + +static bool +IsCiscoVPN( IP_ADAPTER_INFO * pAdapter ) +{ + return ((pAdapter->Type == MIB_IF_TYPE_ETHERNET) && + (pAdapter->AddressLength == 6) && + (pAdapter->Address[0] == 0x00) && + (pAdapter->Address[1] == 0x05) && + (pAdapter->Address[2] == 0x9a) && + (pAdapter->Address[3] == 0x3c) && + (pAdapter->Address[4] == 0x7a) && + (pAdapter->Address[5] == 0x00)) ? true : false; +} + + +static const char * +strnistr( const char * string, const char * subString, size_t max ) +{ + size_t subStringLen; + size_t offset; + size_t maxOffset; + size_t stringLen; + const char * pPos; + + if ( ( string == NULL ) || ( subString == NULL ) ) + { + return string; + } + + stringLen = ( max > strlen( string ) ) ? strlen( string ) : max; + + if ( stringLen == 0 ) + { + return NULL; + } + + subStringLen = strlen( subString ); + + if ( subStringLen == 0 ) + { + return string; + } + + if ( subStringLen > stringLen ) + { + return NULL; + } + + maxOffset = stringLen - subStringLen; + pPos = string; + + for ( offset = 0; offset <= maxOffset; offset++ ) + { + if ( _strnicmp( pPos, subString, subStringLen ) == 0 ) + { + return pPos; + } + + pPos++; + } + + return NULL; +} + |