/* * Copyright (c) 2007-2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * October 24, 2007 Allan Nathanson * - initial revision */ #include #include #include #include #define SC_LOG_HANDLE __log_SCMonitor #define SC_LOG_HANDLE_TYPE static #include #include #include #include #include #include #include "UserEventAgentInterface.h" #define MY_BUNDLE_ID "com.apple.SystemConfiguration.SCMonitor" #define MY_ICON_PATH "/System/Library/PreferencePanes/Network.prefPane/Contents/Resources/Network.icns" #define NETWORK_PREF_APP "/System/Library/PreferencePanes/Network.prefPane" #define NETWORK_PREF_CMD "New Interface" /* * The following keys/values control the actions taken when a new interface * has been detected and whether we interact with the user. * * The keys/values can be provided globally (in SCMonitor.plugin's Info.plist * file) or per-inteface in the IORegistry. * * For the "New Interface Detected Action" key we define the following [CFString] * values : * * "None" No action, ignore this interface. * "Prompt" Post a "new interface detected" notification to the user. * "Configure" Automatically configure this interface without any * intervention. * * Note: automatic configuration may not be possible if the logged in user * is NOT "root" (eUID==0) or if the authorization right that governs * SCHelper write operations (kSCPreferencesAuthorizationRight_write) * is not currently available. * * An [older] "User Intervention" key is also supported. That CFBoolean * key, if present and TRUE, implies "Configure" configuration of the * interface without intervention. */ typedef struct { UserEventAgentInterfaceStruct *_UserEventAgentInterface; CFUUIDRef _factoryID; UInt32 _refCount; Boolean debug; CFStringRef configuration_action; CFRunLoopSourceRef monitorRls; IONotificationPortRef notifyPort; io_iterator_t notifyIterator; CFMutableArrayRef notifyNodes; // interfaces that we already know about CFMutableSetRef interfaces_known; // interfaces that should be auto-configured (no user notification) CFMutableArrayRef interfaces_configure; // interfaces that require user notification CFMutableArrayRef interfaces_prompt; CFUserNotificationRef userNotification; CFRunLoopSourceRef userRls; AuthorizationRef authorization; } MyType; static CFMutableDictionaryRef notify_to_instance = NULL; #pragma mark - #pragma mark Logging /* * Logging */ static os_log_t __log_SCMonitor(void) { static os_log_t log = NULL; if (log == NULL) { log = os_log_create("com.apple.SystemConfiguration", "SCMonitor"); } return log; } #pragma mark - #pragma mark Authorization static AuthorizationRef getAuthorization(MyType *myInstance) { if (myInstance->authorization == NULL) { AuthorizationFlags flags = kAuthorizationFlagDefaults; OSStatus status; status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, flags, &myInstance->authorization); if (status != errAuthorizationSuccess) { SC_log(LOG_ERR, "AuthorizationCreate() failed: status = %d", (int)status); } } return myInstance->authorization; } static Boolean hasAuthorization(MyType *myInstance) { AuthorizationRef authorization; Boolean isAdmin = FALSE; authorization = getAuthorization(myInstance); if (authorization != NULL) { AuthorizationFlags flags = kAuthorizationFlagDefaults; AuthorizationItem items[1]; AuthorizationRights rights; OSStatus status; items[0].name = kSCPreferencesAuthorizationRight_write; items[0].value = NULL; items[0].valueLength = 0; items[0].flags = 0; rights.count = sizeof(items) / sizeof(items[0]); rights.items = items; status = AuthorizationCopyRights(authorization, &rights, kAuthorizationEmptyEnvironment, flags, NULL); isAdmin = (status == errAuthorizationSuccess); } return isAdmin; } static void freeAuthorization(MyType *myInstance) { if (myInstance->authorization != NULL) { AuthorizationFree(myInstance->authorization, kAuthorizationFlagDefaults); // AuthorizationFree(myInstance->authorization, kAuthorizationFlagDestroyRights); myInstance->authorization = NULL; } return; } #pragma mark - #pragma mark New interface notification / configuration static void open_NetworkPrefPane(MyType *myInstance) { #pragma unused(myInstance) AEDesc aeDesc = { typeNull, NULL }; CFArrayRef prefArray; CFURLRef prefURL; LSLaunchURLSpec prefSpec; OSStatus status; prefURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR(NETWORK_PREF_APP), kCFURLPOSIXPathStyle, FALSE); prefArray = CFArrayCreate(NULL, (const void **)&prefURL, 1, &kCFTypeArrayCallBacks); CFRelease(prefURL); status = AECreateDesc('ptru', (const void *)NETWORK_PREF_CMD, strlen(NETWORK_PREF_CMD), &aeDesc); if (status != noErr) { SC_log(LOG_ERR, "AECreateDesc() failed: %d", (int)status); } prefSpec.appURL = NULL; prefSpec.itemURLs = prefArray; prefSpec.passThruParams = &aeDesc; prefSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents; prefSpec.asyncRefCon = NULL; status = LSOpenFromURLSpec(&prefSpec, NULL); if (status != noErr) { SC_log(LOG_ERR, "LSOpenFromURLSpec() failed: %d", (int)status); } CFRelease(prefArray); if (aeDesc.descriptorType != typeNull) AEDisposeDesc(&aeDesc); return; } static void notify_remove(MyType *myInstance, Boolean cancel) { if (myInstance->interfaces_configure != NULL) { CFRelease(myInstance->interfaces_configure); myInstance->interfaces_configure = NULL; } if (myInstance->interfaces_prompt != NULL) { CFRelease(myInstance->interfaces_prompt); myInstance->interfaces_prompt = NULL; } if (myInstance->userRls != NULL) { CFRunLoopSourceInvalidate(myInstance->userRls); CFRelease(myInstance->userRls); myInstance->userRls = NULL; } if (myInstance->userNotification != NULL) { if (cancel) { SInt32 status; status = CFUserNotificationCancel(myInstance->userNotification); if (status != 0) { SC_log(LOG_ERR, "CFUserNotificationCancel() failed, status=%d", (int)status); } } CFRelease(myInstance->userNotification); myInstance->userNotification = NULL; } return; } static void notify_reply(CFUserNotificationRef userNotification, CFOptionFlags response_flags) { MyType *myInstance = NULL; // get instance for notification if (notify_to_instance != NULL) { myInstance = (MyType *)CFDictionaryGetValue(notify_to_instance, userNotification); if (myInstance != NULL) { CFDictionaryRemoveValue(notify_to_instance, userNotification); if (CFDictionaryGetCount(notify_to_instance) == 0) { CFRelease(notify_to_instance); notify_to_instance = NULL; } } } if (myInstance == NULL) { SC_log(LOG_ERR, "can't find user notification"); return; } // process response switch (response_flags & 0x3) { case kCFUserNotificationDefaultResponse: // user asked to configure interface open_NetworkPrefPane(myInstance); break; default: // user cancelled break; } notify_remove(myInstance, FALSE); return; } static void notify_add(MyType *myInstance) { CFBundleRef bundle; CFMutableDictionaryRef dict = NULL; SInt32 error = 0; CFIndex i; CFIndex n = CFArrayGetCount(myInstance->interfaces_prompt); CFURLRef url = NULL; if (myInstance->userNotification != NULL) { CFMutableArrayRef save = NULL; if (n > 0) { CFRetain(myInstance->interfaces_prompt); save = myInstance->interfaces_prompt; } notify_remove(myInstance, TRUE); myInstance->interfaces_prompt = save; if (n == 0) { return; } } dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // set localization URL bundle = CFBundleGetBundleWithIdentifier(CFSTR(MY_BUNDLE_ID)); if (bundle != NULL) { url = CFBundleCopyBundleURL(bundle); } #ifdef MAIN if (url == NULL) { url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)"/System/Library/UserEventPlugins/SCMonitor.plugin", strlen("/System/Library/UserEventPlugins/SCMonitor.plugin"), FALSE); if (bundle == NULL) { bundle = CFBundleCreate(NULL, url); } } #endif // MAIN if (url != NULL) { // set URL CFDictionarySetValue(dict, kCFUserNotificationLocalizationURLKey, url); CFRelease(url); } else { SC_log(LOG_ERR, "can't find bundle"); goto done; } // set icon URL url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)MY_ICON_PATH, strlen(MY_ICON_PATH), FALSE); if (url != NULL) { CFDictionarySetValue(dict, kCFUserNotificationIconURLKey, url); CFRelease(url); } // header CFDictionarySetValue(dict, kCFUserNotificationAlertHeaderKey, (n == 1) ? CFSTR("HEADER_1") : CFSTR("HEADER_N")); // message if (n == 1) { CFStringRef format; SCNetworkInterfaceRef interface; CFStringRef message; CFStringRef name; #define MESSAGE_1 "The “%@” network interface has not been set up. To set up this interface, use Network Preferences." format = CFBundleCopyLocalizedString(bundle, CFSTR("MESSAGE_1"), CFSTR(MESSAGE_1), NULL); interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, 0); name = SCNetworkInterfaceGetLocalizedDisplayName(interface); message = CFStringCreateWithFormat(NULL, NULL, format, name); CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message); CFRelease(message); CFRelease(format); } else { CFMutableArrayRef message; message = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); CFArrayAppendValue(message, CFSTR("MESSAGE_SN")); for (i = 0; i < n; i++) { SCNetworkInterfaceRef interface; CFStringRef name; CFStringRef str; interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, i); name = SCNetworkInterfaceGetLocalizedDisplayName(interface); str = CFStringCreateWithFormat(NULL, NULL, CFSTR("\r\t%@"), name); CFArrayAppendValue(message, str); CFRelease(str); } CFArrayAppendValue(message, CFSTR("MESSAGE_EN")); CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message); CFRelease(message); } // button titles CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("OPEN_NP")); CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFSTR("CANCEL")); // create and post notification myInstance->userNotification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationNoteAlertLevel, &error, dict); if (myInstance->userNotification == NULL) { SC_log(LOG_ERR, "CFUserNotificationCreate() failed: %d", (int)error); goto done; } // establish callback myInstance->userRls = CFUserNotificationCreateRunLoopSource(NULL, myInstance->userNotification, notify_reply, 0); if (myInstance->userRls == NULL) { SC_log(LOG_ERR, "CFUserNotificationCreateRunLoopSource() failed"); CFRelease(myInstance->userNotification); myInstance->userNotification = NULL; goto done; } CFRunLoopAddSource(CFRunLoopGetCurrent(), myInstance->userRls, kCFRunLoopDefaultMode); // add instance for notification if (notify_to_instance == NULL) { notify_to_instance = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL); // no retain/release/... for values } CFDictionarySetValue(notify_to_instance, myInstance->userNotification, myInstance); done : if (dict != NULL) CFRelease(dict); return; } static void notify_configure(MyType *myInstance) { CFIndex i; CFIndex n = CFArrayGetCount(myInstance->interfaces_configure); Boolean ok; SCPreferencesRef prefs = NULL; SCNetworkSetRef set = NULL; if (geteuid() == 0) { prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL); } else { AuthorizationRef authorization; authorization = getAuthorization(myInstance); if (authorization == NULL) { return; } prefs = SCPreferencesCreateWithAuthorization(NULL, CFSTR("SCMonitor"), NULL, authorization); } set = SCNetworkSetCopyCurrent(prefs); if (set == NULL) { // if no "current" set, create new/default ("Automatic") set set = _SCNetworkSetCreateDefault(prefs); if (set == NULL) { goto done; } SC_log(LOG_DEBUG, "added new \"default\" set"); } for (i = 0; i < n; i++) { SCNetworkInterfaceRef interface; interface = CFArrayGetValueAtIndex(myInstance->interfaces_configure, i); ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface); if (ok) { CFStringRef name; name = SCNetworkInterfaceGetLocalizedDisplayName(interface); SC_log(LOG_NOTICE, "add/update service for %@", name); } } ok = SCPreferencesCommitChanges(prefs); if (!ok) { SC_log(LOG_ERR, "SCPreferencesCommitChanges() failed: %s", SCErrorString(SCError())); goto done; } ok = SCPreferencesApplyChanges(prefs); if (!ok) { SC_log(LOG_ERR, "SCPreferencesApplyChanges() failed: %s", SCErrorString(SCError())); goto done; } done : if (set != NULL) { CFRelease(set); set = NULL; } if (prefs != NULL) { CFRelease(prefs); prefs = NULL; } CFRelease(myInstance->interfaces_configure); myInstance->interfaces_configure = NULL; return; } #pragma mark - static Boolean onConsole() { CFArrayRef console_sessions; Boolean on = FALSE; io_registry_entry_t root; uid_t uid = geteuid(); root = IORegistryGetRootEntry(kIOMasterPortDefault); console_sessions = IORegistryEntryCreateCFProperty(root, CFSTR(kIOConsoleUsersKey), NULL, 0); if (console_sessions != NULL) { CFIndex n; n = isA_CFArray(console_sessions) ? CFArrayGetCount(console_sessions) : 0; for (CFIndex i = 0; i < n; i++) { CFBooleanRef bVal; CFDictionaryRef session; uint64_t sessionUID; CFNumberRef val; session = CFArrayGetValueAtIndex(console_sessions, i); if (!isA_CFDictionary(session)) { // if not dictionary continue; } if (!CFDictionaryGetValueIfPresent(session, CFSTR(kIOConsoleSessionUIDKey), (const void **)&val) || !isA_CFNumber(val) || !CFNumberGetValue(val, kCFNumberSInt64Type, (void *)&sessionUID) || (uid != sessionUID)) { // if not my session continue; } if (CFDictionaryGetValueIfPresent(session, CFSTR(kIOConsoleSessionOnConsoleKey), (const void **)&bVal) && isA_CFBoolean(bVal) && CFBooleanGetValue(bVal)) { // if "on console" session on = TRUE; } break; } CFRelease(console_sessions); } IOObjectRelease(root); return on; } #pragma mark - // configure ONLY IF authorized #define kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized CFSTR("Configure-Authorized") static void updateInterfaceList(MyType *myInstance) { Boolean changed = FALSE; CFIndex i; CFArrayRef interfaces; CFMutableSetRef interfaces_old = NULL; CFIndex n; SCPreferencesRef prefs; SCNetworkSetRef set = NULL; if (!onConsole()) { return; } prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL); if (prefs == NULL) { return; } set = SCNetworkSetCopyCurrent(prefs); if (set == NULL) { // if no "current" set, create new/default ("Automatic") set set = _SCNetworkSetCreateDefault(prefs); if (set == NULL) { goto done; } } interfaces_old = CFSetCreateMutableCopy(NULL, 0, myInstance->interfaces_known); interfaces = _SCNetworkInterfaceCopyAllWithPreferences(prefs); if (interfaces != NULL) { n = CFArrayGetCount(interfaces); for (i = 0; i < n; i++) { SCNetworkInterfaceRef interface; Boolean ok; interface = CFArrayGetValueAtIndex(interfaces, i); // track new vs. old (removed) interfaces CFSetRemoveValue(interfaces_old, interface); if (CFSetContainsValue(myInstance->interfaces_known, interface)) { // if we already know about this interface continue; } CFSetAddValue(myInstance->interfaces_known, interface); changed = TRUE; ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface); if (ok) { CFStringRef action; // this is a *new* interface action = _SCNetworkInterfaceGetConfigurationAction(interface); if (action == NULL) { // if no per-interface action, use [global] default action = myInstance->configuration_action; } if ((action == NULL) || (!CFEqual(action, kSCNetworkInterfaceConfigurationActionValueNone) && !CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigure))) { if (_SCNetworkInterfaceIsBuiltin(interface)) { // if built-in interface action = kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized; } else { action = kSCNetworkInterfaceConfigurationActionValuePrompt; } } if (CFEqual(action, kSCNetworkInterfaceConfigurationActionValueNone)) { continue; } else if (CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigure)) { // configure automatically (without user intervention) if (myInstance->interfaces_configure == NULL) { myInstance->interfaces_configure = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } CFArrayAppendValue(myInstance->interfaces_configure, interface); } else if (hasAuthorization(myInstance)) { // if we already have the "admin" (kSCPreferencesAuthorizationRight_write) // right, configure automatically (without user intervention) if (myInstance->interfaces_configure == NULL) { myInstance->interfaces_configure = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } CFArrayAppendValue(myInstance->interfaces_configure, interface); } else if (!CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized)) { // notify user if (myInstance->interfaces_prompt == NULL) { myInstance->interfaces_prompt = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } CFArrayAppendValue(myInstance->interfaces_prompt, interface); } } } CFRelease(interfaces); } // remove any posted notifications for network interfaces that have been removed n = CFSetGetCount(interfaces_old); if (n > 0) { const void * paths_q[32]; const void ** paths = paths_q; if (n > (CFIndex)(sizeof(paths_q) / sizeof(CFTypeRef))) paths = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0); CFSetGetValues(interfaces_old, paths); for (i = 0; i < n; i++) { if (myInstance->interfaces_prompt != NULL) { CFIndex j; j = CFArrayGetCount(myInstance->interfaces_prompt); while (j > 0) { SCNetworkInterfaceRef interface; j--; interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, j); if (CFEqual(interface, paths[i])) { // if we have previously posted a notification // for this no-longer-present interface CFArrayRemoveValueAtIndex(myInstance->interfaces_prompt, j); changed = TRUE; } } } CFSetRemoveValue(myInstance->interfaces_known, paths[i]); } if (paths != paths_q) CFAllocatorDeallocate(NULL, paths); } done : if (changed) { if (myInstance->interfaces_configure != NULL) { // if we have network services to configure automatically notify_configure(myInstance); } if (myInstance->interfaces_prompt != NULL) { // if we have network services that require user intervention // post notification for new interfaces notify_add(myInstance); } } if (interfaces_old != NULL) CFRelease(interfaces_old); if (set != NULL) CFRelease(set); CFRelease(prefs); return; } #pragma mark - #pragma mark Watch for new [network] interfaces static void update_lan(SCDynamicStoreRef store, CFArrayRef changes, void * arg) { #pragma unused(store) #pragma unused(changes) MyType *myInstance = (MyType *)arg; updateInterfaceList(myInstance); return; } static void watcher_add_lan(MyType *myInstance) { SCDynamicStoreContext context = { 0, (void *)myInstance, NULL, NULL, NULL }; CFStringRef key; CFArrayRef keys; SCDynamicStoreRef store; store = SCDynamicStoreCreate(NULL, CFSTR("SCMonitor"), update_lan, &context); if (store == NULL) { SC_log(LOG_ERR, "SCDynamicStoreCreate() failed: %s", SCErrorString(SCError())); return; } key = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState); // watch for changes to the list of network interfaces keys = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks); SCDynamicStoreSetNotificationKeys(store, keys, NULL); CFRelease(keys); myInstance->monitorRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0); CFRunLoopAddSource(CFRunLoopGetCurrent(), myInstance->monitorRls, kCFRunLoopDefaultMode); // check if we already have the "admin" (kSCPreferencesAuthorizationRight_write) // right. If so, we can automatically configure (without user intervention) any // "new" network interfaces that are present at login (e.g. a USB ethernet // dongle that was plugged in before login). if (!hasAuthorization(myInstance)) { CFDictionaryRef dict; // ... and if we don't have the right then we populate the list of // known interfaces with those already named so that we avoid any // login prompts (that the user might have no choice but to dismiss) dict = SCDynamicStoreCopyValue(store, key); if (dict != NULL) { if (isA_CFDictionary(dict)) { CFIndex i; CFArrayRef interfaces; CFIndex n; interfaces = CFDictionaryGetValue(dict, kSCPropNetInterfaces); n = isA_CFArray(interfaces) ? CFArrayGetCount(interfaces) : 0; for (i = 0; i < n; i++) { CFStringRef bsdName; bsdName = CFArrayGetValueAtIndex(interfaces, i); if (isA_CFString(bsdName)) { SCNetworkInterfaceRef interface; interface = _SCNetworkInterfaceCreateWithBSDName(NULL, bsdName, kIncludeNoVirtualInterfaces); if (interface != NULL) { CFSetAddValue(myInstance->interfaces_known, interface); CFRelease(interface); } } } } CFRelease(dict); } } CFRelease(key); CFRelease(store); return; } static void watcher_remove_lan(MyType *myInstance) { if (myInstance->monitorRls != NULL) { CFRunLoopSourceInvalidate(myInstance->monitorRls); CFRelease(myInstance->monitorRls); myInstance->monitorRls = NULL; } return; } #pragma mark - typedef struct { io_registry_entry_t interface; io_registry_entry_t interface_node; MyType *myInstance; io_object_t notification; } MyNode; static void add_node_watcher(MyType *myInstance, io_registry_entry_t node, io_registry_entry_t interface); static void update_node(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) { #pragma unused(messageArgument) CFIndex i; CFDataRef myData = (CFDataRef)refCon; MyType *myInstance; MyNode *myNode; /* ALIGN: CF aligns to at least >8 bytes */ myNode = (MyNode *)(void *)CFDataGetBytePtr(myData); myInstance = myNode->myInstance; switch (messageType) { case kIOMessageServicePropertyChange : { Boolean initializing = FALSE; SCNetworkInterfaceRef interface; CFTypeRef val; if (myNode->interface == MACH_PORT_NULL) { // if we are not watching the "Initializing" property return; } val = IORegistryEntryCreateCFProperty(service, kSCNetworkInterfaceInitializingKey, NULL, 0); if (val != NULL) { initializing = (isA_CFBoolean(val) && CFBooleanGetValue(val)); CFRelease(val); if (initializing) { // if initialization not complete, keep watching return; } } // node is ready interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(myNode->interface); if (interface != NULL) { CFRelease(interface); // watch interface (to see when/if it's removed) add_node_watcher(myInstance, myNode->interface, MACH_PORT_NULL); } break; } case kIOMessageServiceIsTerminated : break; default : return; } // remove no-longer-needed notification if (myNode->interface != myNode->interface_node) { IOObjectRelease(myNode->interface_node); } if (myNode->interface != MACH_PORT_NULL) { IOObjectRelease(myNode->interface); } IOObjectRelease(myNode->notification); i = CFArrayGetFirstIndexOfValue(myInstance->notifyNodes, CFRangeMake(0, CFArrayGetCount(myInstance->notifyNodes)), myData); if (i != kCFNotFound) { CFArrayRemoveValueAtIndex(myInstance->notifyNodes, i); if (CFArrayGetCount(myInstance->notifyNodes) == 0) { CFRelease(myInstance->notifyNodes); myInstance->notifyNodes = NULL; } } updateInterfaceList(myInstance); return; } static void add_node_watcher(MyType *myInstance, io_registry_entry_t node, io_registry_entry_t interface) { kern_return_t kr; CFMutableDataRef myData; MyNode *myNode; // wait for initialization to complete myData = CFDataCreateMutable(NULL, sizeof(MyNode)); CFDataSetLength(myData, sizeof(MyNode)); /* ALIGN: CF aligns to at least >8 bytes */ myNode = (MyNode *)(void *)CFDataGetBytePtr(myData); memset(myNode, 0, sizeof(MyNode)); myNode->interface = interface; if (myNode->interface != MACH_PORT_NULL) { IOObjectRetain(myNode->interface); } myNode->interface_node = (interface == MACH_PORT_NULL) ? node : interface; if (myNode->interface != myNode->interface_node) { IOObjectRetain(myNode->interface_node); } myNode->myInstance = myInstance; myNode->notification = MACH_PORT_NULL; kr = IOServiceAddInterestNotification(myInstance->notifyPort, // IONotificationPortRef node, // io_service_t kIOGeneralInterest, // interestType update_node, // IOServiceInterestCallback (void *)myData, // refCon &myNode->notification); // notification if (kr == KERN_SUCCESS) { if (myInstance->notifyNodes == NULL) { myInstance->notifyNodes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } CFArrayAppendValue(myInstance->notifyNodes, myData); } else { SC_log(LOG_ERR, "add_init_watcher IOServiceAddInterestNotification() failed, kr = 0x%x", kr); } CFRelease(myData); } static void add_init_watcher(MyType *myInstance, io_registry_entry_t interface) { kern_return_t kr; io_registry_entry_t node = interface; CFTypeRef val = NULL; while (node != MACH_PORT_NULL) { io_registry_entry_t parent; val = IORegistryEntryCreateCFProperty(node, kSCNetworkInterfaceHiddenPortKey, NULL, 0); if (val != NULL) { CFRelease(val); val = NULL; break; } val = IORegistryEntryCreateCFProperty(node, kSCNetworkInterfaceInitializingKey, NULL, 0); if (val != NULL) { break; } parent = MACH_PORT_NULL; kr = IORegistryEntryGetParentEntry(node, kIOServicePlane, &parent); switch (kr) { case kIOReturnSuccess : // if we have a parent node case kIOReturnNoDevice : // if we have hit the root node break; default : SC_log(LOG_ERR, "add_init_watcher IORegistryEntryGetParentEntry() failed, kr = 0x%x", kr); break; } if (node != interface) { IOObjectRelease(node); } node = parent; } if (val != NULL) { if (isA_CFBoolean(val) && CFBooleanGetValue(val)) { // watch the "Initializing" node add_node_watcher(myInstance, node, interface); } CFRelease(val); } if ((node != MACH_PORT_NULL) && (node != interface)) { IOObjectRelease(node); } return; } static void update_serial(void *refcon, io_iterator_t iter) { MyType *myInstance = (MyType *)refcon; io_registry_entry_t obj; while ((obj = IOIteratorNext(iter)) != MACH_PORT_NULL) { SCNetworkInterfaceRef interface; interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(obj); if (interface != NULL) { CFRelease(interface); // watch interface (to see when/if it's removed) add_node_watcher(myInstance, obj, MACH_PORT_NULL); } else { // check interface, watch if initializing add_init_watcher(myInstance, obj); } IOObjectRelease(obj); } return; } static void update_serial_nodes(void *refcon, io_iterator_t iter) { MyType *myInstance = (MyType *)refcon; update_serial(refcon, iter); updateInterfaceList(myInstance); } static void watcher_add_serial(MyType *myInstance) { kern_return_t kr; myInstance->notifyPort = IONotificationPortCreate(kIOMasterPortDefault); if (myInstance->notifyPort == NULL) { SC_log(LOG_ERR, "IONotificationPortCreate failed"); return; } // watch for the introduction of new network serial devices kr = IOServiceAddMatchingNotification(myInstance->notifyPort, kIOFirstMatchNotification, IOServiceMatching("IOSerialBSDClient"), &update_serial_nodes, (void *)myInstance, // refCon &myInstance->notifyIterator); // notification if (kr != KERN_SUCCESS) { SC_log(LOG_ERR, "SCMonitor : IOServiceAddMatchingNotification returned 0x%x", kr); return; } myInstance->notifyNodes = NULL; // Get the current list of matches and arm the notification for // future interface arrivals. update_serial((void *)myInstance, myInstance->notifyIterator); if (myInstance->notifyNodes != NULL) { // if we have any serial nodes, check if we already have the // "admin" (kSCPreferencesAuthorizationRight_write) right. If // so, we can automatically configure (without user intervention) // any "new" network interfaces that are present at login (e.g. a // USB modem that was plugged in before login). if (!hasAuthorization(myInstance)) { CFIndex i; CFIndex n = CFArrayGetCount(myInstance->notifyNodes); // ... and if we don't have the right then we populate the list of // known interfaces with those already named so that we avoid any // login prompts (that the user might have no choice but to dismiss) for (i = 0; i < n; i++) { SCNetworkInterfaceRef interface; CFDataRef myData; MyNode *myNode; myData = CFArrayGetValueAtIndex(myInstance->notifyNodes, i); /* ALIGN: CF aligns to at least >8 bytes */ myNode = (MyNode *)(void *)CFDataGetBytePtr(myData); interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(myNode->interface_node); if (interface != NULL) { CFSetAddValue(myInstance->interfaces_known, interface); CFRelease(interface); } } } } // and keep watching CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(myInstance->notifyPort), kCFRunLoopDefaultMode); return; } static void watcher_remove_serial(MyType *myInstance) { if (myInstance->notifyNodes != NULL) { CFIndex i; CFIndex n = CFArrayGetCount(myInstance->notifyNodes); for (i = 0; i < n; i++) { CFDataRef myData; MyNode *myNode; myData = CFArrayGetValueAtIndex(myInstance->notifyNodes, i); /* ALIGN: CF aligns to at least >8 bytes */ myNode = (MyNode *)(void *)CFDataGetBytePtr(myData); if (myNode->interface != myNode->interface_node) { IOObjectRelease(myNode->interface_node); } if (myNode->interface != MACH_PORT_NULL) { IOObjectRelease(myNode->interface); } IOObjectRelease(myNode->notification); } CFRelease(myInstance->notifyNodes); myInstance->notifyNodes = NULL; } if (myInstance->notifyIterator != MACH_PORT_NULL) { IOObjectRelease(myInstance->notifyIterator); myInstance->notifyIterator = MACH_PORT_NULL; } if (myInstance->notifyPort != MACH_PORT_NULL) { IONotificationPortDestroy(myInstance->notifyPort); myInstance->notifyPort = NULL; } return; } #pragma mark - static void watcher_add(MyType *myInstance) { CFBundleRef bundle; bundle = CFBundleGetBundleWithIdentifier(CFSTR(MY_BUNDLE_ID)); if (bundle != NULL) { CFStringRef action; CFBooleanRef bVal; CFDictionaryRef info; info = CFBundleGetInfoDictionary(bundle); bVal = CFDictionaryGetValue(info, CFSTR("Debug")); bVal = isA_CFBoolean(bVal); if (bVal != NULL) { myInstance->debug = CFBooleanGetValue(bVal); } action = CFDictionaryGetValue(info, kSCNetworkInterfaceConfigurationActionKey); action = isA_CFString(action); if (action != NULL) { myInstance->configuration_action = action; } else { CFBooleanRef user_intervention; user_intervention = CFDictionaryGetValue(info, CFSTR("User Intervention")); if (isA_CFBoolean(user_intervention) && !CFBooleanGetValue(user_intervention)) { myInstance->configuration_action = kSCNetworkInterfaceConfigurationActionValueConfigure; } } } // initialize the list of known interfaces myInstance->interfaces_known = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); // add LAN interfaces watcher_add_lan(myInstance); // add SERIAL interfaces watcher_add_serial(myInstance); // auto-configure (as needed) updateInterfaceList(myInstance); return; } static void watcher_remove(MyType *myInstance) { watcher_remove_lan(myInstance); watcher_remove_serial(myInstance); if (myInstance->interfaces_known != NULL) { CFRelease(myInstance->interfaces_known); myInstance->interfaces_known = NULL; } return; } #pragma mark - #pragma mark UserEventAgent stubs static HRESULT myQueryInterface(void *myInstance, REFIID iid, LPVOID *ppv) { CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid); // Test the requested ID against the valid interfaces. if (CFEqual(interfaceID, kUserEventAgentInterfaceID)) { ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance); *ppv = myInstance; CFRelease(interfaceID); return S_OK; } if (CFEqual(interfaceID, IUnknownUUID)) { ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance); *ppv = myInstance; CFRelease(interfaceID); return S_OK; } // Requested interface unknown, bail with error. *ppv = NULL; CFRelease(interfaceID); return E_NOINTERFACE; } static ULONG myAddRef(void *myInstance) { ((MyType *) myInstance)->_refCount++; return ((MyType *) myInstance)->_refCount; } static ULONG myRelease(void *myInstance) { ((MyType *) myInstance)->_refCount--; if (((MyType *) myInstance)->_refCount == 0) { CFUUIDRef factoryID = ((MyType *) myInstance)->_factoryID; if (factoryID != NULL) { CFPlugInRemoveInstanceForFactory(factoryID); CFRelease(factoryID); watcher_remove((MyType *)myInstance); notify_remove((MyType *)myInstance, TRUE); freeAuthorization((MyType *)myInstance); } free(myInstance); return 0; } return ((MyType *) myInstance)->_refCount; } static void myInstall(void *myInstance) { watcher_add((MyType *)myInstance); return; } static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = { NULL, // Required padding for COM myQueryInterface, // These three are the required COM functions myAddRef, myRelease, myInstall // Interface implementation }; void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID) { #pragma unused(allocator) MyType *newOne = NULL; if (CFEqual(typeID, kUserEventAgentTypeID)) { newOne = (MyType *)malloc(sizeof(MyType)); memset(newOne, 0, sizeof(*newOne)); newOne->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl; newOne->_factoryID = (CFUUIDRef)CFRetain(kUserEventAgentFactoryID); CFPlugInAddInstanceForFactory(kUserEventAgentFactoryID); newOne->_refCount = 1; } return newOne; } #ifdef MAIN int main(int argc, char **argv) { MyType *newOne = (MyType *)malloc(sizeof(MyType)); _sc_log = FALSE; _sc_verbose = (argc > 1) ? TRUE : FALSE; memset(newOne, 0, sizeof(*newOne)); myInstall(newOne); CFRunLoopRun(); exit(0); return (0); } #endif // MAIN