| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2010 Apple 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 <CoreFoundation/CoreFoundation.h> |
| #include <CoreFoundation/CFXPCBridge.h> |
| #include <dns_sd.h> |
| #include <UserEventAgentInterface.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <asl.h> |
| #include <xpc/xpc.h> |
| |
| |
| #pragma mark - |
| #pragma mark Types |
| #pragma mark - |
| static const char* sPluginIdentifier = "com.apple.bonjour.events"; |
| |
| // PLIST Keys |
| static const CFStringRef sServiceNameKey = CFSTR("ServiceName"); |
| static const CFStringRef sServiceTypeKey = CFSTR("ServiceType"); |
| static const CFStringRef sServiceDomainKey = CFSTR("ServiceDomain"); |
| |
| static const CFStringRef sOnServiceAddKey = CFSTR("OnServiceAdd"); |
| static const CFStringRef sOnServiceRemoveKey = CFSTR("OnServiceRemove"); |
| |
| static const CFStringRef sLaunchdTokenKey = CFSTR("LaunchdToken"); |
| static const CFStringRef sLaunchdDictKey = CFSTR("LaunchdDict"); |
| |
| |
| /************************************************ |
| * Launch Event Dictionary (input from launchd) |
| * Passed To: ManageEventsCallback |
| *----------------------------------------------- |
| * Typing in this dictionary is not enforced |
| * above us. So this may not be true. Type check |
| * all input before using it. |
| *----------------------------------------------- |
| * sServiceNameKey - CFString (Optional) |
| * sServiceTypeKey - CFString |
| * sServiceDomainKey - CFString |
| * |
| * One or more of the following. |
| *----------------------------------- |
| * sOnServiceAddKey - CFBoolean |
| * sOnServiceRemoveKey - CFBoolean |
| * sWhileServiceExistsKey - CFBoolean |
| ************************************************/ |
| |
| /************************************************ |
| * Browser Dictionary |
| *----------------------------------------------- |
| * sServiceDomainKey - CFString |
| * sServiceTypeKey - CFString |
| ************************************************/ |
| |
| /************************************************ |
| * Event Dictionary |
| *----------------------------------------------- |
| * sServiceNameKey - CFString (Optional) |
| * sLaunchdTokenKey - CFNumber |
| ************************************************/ |
| |
| typedef struct { |
| UserEventAgentInterfaceStruct* _UserEventAgentInterface; |
| CFUUIDRef _factoryID; |
| UInt32 _refCount; |
| |
| void* _pluginContext; |
| |
| CFMutableDictionaryRef _tokenToBrowserMap; // Maps a token to a browser that can be used to scan the remaining dictionaries. |
| CFMutableDictionaryRef _browsers; // A Dictionary of Browser Dictionaries where the resposible browser is the key. |
| CFMutableDictionaryRef _onAddEvents; // A Dictionary of Event Dictionaries that describe events to trigger on a service appearing. |
| CFMutableDictionaryRef _onRemoveEvents; // A Dictionary of Event Dictionaries that describe events to trigger on a service disappearing. |
| } BonjourUserEventsPlugin; |
| |
| typedef struct { |
| CFIndex refCount; |
| DNSServiceRef browserRef; |
| } NetBrowserInfo; |
| |
| #pragma mark - |
| #pragma mark Prototypes |
| #pragma mark - |
| // COM Stuff |
| static HRESULT QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv); |
| static ULONG AddRef(void* instance); |
| static ULONG Release(void* instance); |
| |
| static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID); |
| static void Dealloc(BonjourUserEventsPlugin* plugin); |
| |
| void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID); |
| |
| // Plugin Management |
| static void Install(void* instance); |
| static void ManageEventsCallback( |
| UserEventAgentLaunchdAction action, |
| CFNumberRef token, |
| CFTypeRef eventMatchDict, |
| void * vContext); |
| |
| |
| // Plugin Guts |
| void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters); |
| void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchToken); |
| |
| NetBrowserInfo* CreateBrowser(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain); |
| NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef); |
| void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key); |
| void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken); |
| |
| // Net Service Browser Stuff |
| void ServiceBrowserCallback (DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* serviceName, const char* regtype, const char* replyDomain, void* context); |
| void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary); |
| |
| // Convence Stuff |
| const char* CStringFromCFString(CFStringRef string); |
| |
| // NetBrowserInfo "Object" |
| NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context); |
| const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info); |
| void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info); |
| Boolean NetBrowserInfoEqual(const void *value1, const void *value2); |
| CFHashCode NetBrowserInfoHash(const void *value); |
| CFStringRef NetBrowserInfoCopyDescription(const void *value); |
| |
| static const CFDictionaryKeyCallBacks kNetBrowserInfoDictionaryKeyCallbacks = { |
| 0, |
| NetBrowserInfoRetain, |
| NetBrowserInfoRelease, |
| NetBrowserInfoCopyDescription, |
| NetBrowserInfoEqual, |
| NetBrowserInfoHash |
| }; |
| |
| static const CFDictionaryValueCallBacks kNetBrowserInfoDictionaryValueCallbacks = { |
| 0, |
| NetBrowserInfoRetain, |
| NetBrowserInfoRelease, |
| NetBrowserInfoCopyDescription, |
| NetBrowserInfoEqual |
| }; |
| |
| // COM type definition goop. |
| static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = { |
| NULL, // Required padding for COM |
| QueryInterface, // Query Interface |
| AddRef, // AddRef() |
| Release, // Release() |
| Install // Install |
| }; |
| |
| #pragma mark - |
| #pragma mark COM Management |
| #pragma mark - |
| |
| /***************************************************************************** |
| *****************************************************************************/ |
| static HRESULT QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv) |
| { |
| CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid); |
| |
| // Test the requested ID against the valid interfaces. |
| if(CFEqual(interfaceID, kUserEventAgentInterfaceID)) |
| { |
| ((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance); |
| *ppv = myInstance; |
| CFRelease(interfaceID); |
| return S_OK; |
| } |
| else if(CFEqual(interfaceID, IUnknownUUID)) |
| { |
| ((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance); |
| *ppv = myInstance; |
| CFRelease(interfaceID); |
| return S_OK; |
| } |
| else // Requested interface unknown, bail with error. |
| { |
| *ppv = NULL; |
| CFRelease(interfaceID); |
| return E_NOINTERFACE; |
| } |
| } |
| |
| /***************************************************************************** |
| *****************************************************************************/ |
| static ULONG AddRef(void* instance) |
| { |
| BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance; |
| return ++plugin->_refCount; |
| } |
| |
| /***************************************************************************** |
| *****************************************************************************/ |
| static ULONG Release(void* instance) |
| { |
| BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance; |
| |
| if (plugin->_refCount != 0) |
| --plugin->_refCount; |
| |
| if (plugin->_refCount == 0) |
| { |
| Dealloc(instance); |
| return 0; |
| } |
| |
| return plugin->_refCount; |
| } |
| |
| /***************************************************************************** |
| * Alloc |
| * - |
| * Functionas as both +[alloc] and -[init] for the plugin. Add any |
| * initalization of member variables here. |
| *****************************************************************************/ |
| static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID) |
| { |
| BonjourUserEventsPlugin* plugin = malloc(sizeof(BonjourUserEventsPlugin)); |
| |
| plugin->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl; |
| plugin->_pluginContext = NULL; |
| |
| if (factoryID) |
| { |
| plugin->_factoryID = (CFUUIDRef)CFRetain(factoryID); |
| CFPlugInAddInstanceForFactory(factoryID); |
| } |
| |
| plugin->_refCount = 1; |
| plugin->_tokenToBrowserMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kNetBrowserInfoDictionaryValueCallbacks); |
| plugin->_browsers = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks); |
| plugin->_onAddEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks); |
| plugin->_onRemoveEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks); |
| |
| return plugin; |
| } |
| |
| /***************************************************************************** |
| * Dealloc |
| * - |
| * Much like Obj-C dealloc this method is responsible for releasing any object |
| * this plugin is holding. Unlike ObjC, you call directly free() instead of |
| * [super dalloc]. |
| *****************************************************************************/ |
| static void Dealloc(BonjourUserEventsPlugin* plugin) |
| { |
| CFUUIDRef factoryID = plugin->_factoryID; |
| |
| if (factoryID) |
| { |
| CFPlugInRemoveInstanceForFactory(factoryID); |
| CFRelease(factoryID); |
| } |
| |
| if (plugin->_tokenToBrowserMap) |
| CFRelease(plugin->_tokenToBrowserMap); |
| |
| if (plugin->_browsers) |
| CFRelease(plugin->_browsers); |
| |
| if (plugin->_onAddEvents) |
| CFRelease(plugin->_onAddEvents); |
| |
| if (plugin->_onRemoveEvents) |
| CFRelease(plugin->_onRemoveEvents); |
| |
| free(plugin); |
| } |
| |
| /******************************************************************************* |
| *******************************************************************************/ |
| void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID) |
| { |
| (void)allocator; |
| BonjourUserEventsPlugin * result = NULL; |
| |
| if (typeID && CFEqual(typeID, kUserEventAgentTypeID)) { |
| result = Alloc(kUserEventAgentFactoryID); |
| } |
| |
| return (void *)result; |
| } |
| |
| #pragma mark - |
| #pragma mark Plugin Management |
| #pragma mark - |
| /***************************************************************************** |
| * Install |
| * - |
| * This is invoked once when the plugin is loaded to do initial setup and |
| * allow us to register with launchd. If UserEventAgent crashes, the plugin |
| * will need to be reloaded, and hence this will get invoked again. |
| *****************************************************************************/ |
| static void Install(void *instance) |
| { |
| BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance; |
| |
| plugin->_pluginContext = UserEventAgentRegisterForLaunchEvents(sPluginIdentifier, &ManageEventsCallback, plugin); |
| |
| if (!plugin->_pluginContext) |
| { |
| fprintf(stderr, "%s:%s failed to register for launch events.\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| } |
| |
| /***************************************************************************** |
| * ManageEventsCallback |
| * - |
| * This is invoked when launchd loads a event dictionary and needs to inform |
| * us what a daemon / agent is looking for. |
| *****************************************************************************/ |
| static void ManageEventsCallback(UserEventAgentLaunchdAction action, CFNumberRef token, CFTypeRef eventMatchDict, void* vContext) |
| { |
| if (action == kUserEventAgentLaunchdAdd) |
| { |
| if (!eventMatchDict) |
| { |
| fprintf(stderr, "%s:%s empty dictionary\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| if (CFGetTypeID(eventMatchDict) != CFDictionaryGetTypeID()) |
| { |
| fprintf(stderr, "%s:%s given non-dict for event dictionary, action %d\n", sPluginIdentifier, __FUNCTION__, action); |
| return; |
| } |
| // Launchd wants us to add a launch event for this token and matching dictionary. |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling AddEventToPlugin", sPluginIdentifier, __FUNCTION__); |
| AddEventToPlugin((BonjourUserEventsPlugin*)vContext, token, (CFDictionaryRef)eventMatchDict); |
| } |
| else if (action == kUserEventAgentLaunchdRemove) |
| { |
| // Launchd wants us to remove the event hook we setup for this token / matching dictionary. |
| // Note: eventMatchDict can be NULL for Remove. |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling RemoveEventToPlugin", sPluginIdentifier, __FUNCTION__); |
| RemoveEventFromPlugin((BonjourUserEventsPlugin*)vContext, token); |
| } |
| else |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s unknown callback event\n", sPluginIdentifier, __FUNCTION__); |
| } |
| } |
| |
| |
| #pragma mark - |
| #pragma mark Plugin Guts |
| #pragma mark - |
| |
| /***************************************************************************** |
| * AddEventToPlugin |
| * - |
| * This method is invoked when launchd wishes the plugin to setup a launch |
| * event matching the parameters in the dictionary. |
| *****************************************************************************/ |
| void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters) |
| { |
| CFStringRef domain = CFDictionaryGetValue(eventParameters, sServiceDomainKey); |
| CFStringRef type = CFDictionaryGetValue(eventParameters, sServiceTypeKey); |
| CFStringRef name = CFDictionaryGetValue(eventParameters, sServiceNameKey); |
| CFBooleanRef cfOnAdd = CFDictionaryGetValue(eventParameters, sOnServiceAddKey); |
| CFBooleanRef cfOnRemove = CFDictionaryGetValue(eventParameters, sOnServiceRemoveKey); |
| |
| Boolean onAdd = false; |
| Boolean onRemove = false; |
| |
| if (cfOnAdd && CFGetTypeID(cfOnAdd) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnAdd)) |
| onAdd = true; |
| |
| if (cfOnRemove && CFGetTypeID(cfOnRemove) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnRemove)) |
| onRemove = true; |
| |
| // A type is required. If none is specified, BAIL |
| if (!type || CFGetTypeID(type) != CFStringGetTypeID()) |
| { |
| fprintf(stderr, "%s:%s: a LaunchEvent is missing a service type.\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| // If we aren't suppose to launch on services appearing or disappearing, this service does nothing. Ignore. |
| if (!onAdd && !onRemove) |
| { |
| fprintf(stderr, "%s:%s a LaunchEvent is missing both onAdd and onRemove events\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| // If no domain is specified, assume local. |
| if (!domain) |
| { |
| domain = CFSTR("local"); |
| } |
| else if (CFGetTypeID(domain) != CFStringGetTypeID() ) // If the domain is not a string, fail |
| { |
| fprintf(stderr, "%s:%s a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| // If we have a name filter, but it's not a string. This event is broken, bail. |
| if (name && CFGetTypeID(name) != CFStringGetTypeID()) |
| { |
| fprintf(stderr, "%s:%s a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| // Get us a browser |
| NetBrowserInfo* browser = CreateBrowser(plugin, type, domain); |
| |
| if (!browser) |
| { |
| fprintf(stderr, "%s:%s cannot create browser\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| // Create Event Dictionary |
| CFMutableDictionaryRef eventDictionary = CFDictionaryCreateMutable(NULL, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| |
| // We store both the Token and the Dictionary. UserEventAgentSetLaunchEventState needs |
| // the token and UserEventAgentSetFireEvent needs both the token and the dictionary |
| CFDictionarySetValue(eventDictionary, sLaunchdTokenKey, launchdToken); |
| CFDictionarySetValue(eventDictionary, sLaunchdDictKey, eventParameters); |
| |
| if (name) |
| CFDictionarySetValue(eventDictionary, sServiceNameKey, name); |
| |
| // Add to the correct dictionary. |
| if (onAdd) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Adding browser to AddEvents", sPluginIdentifier, __FUNCTION__); |
| AddEventDictionary(eventDictionary, plugin->_onAddEvents, browser); |
| } |
| |
| if (onRemove) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Adding browser to RemoveEvents", sPluginIdentifier, __FUNCTION__); |
| AddEventDictionary(eventDictionary, plugin->_onRemoveEvents, browser); |
| } |
| |
| // Add Token Mapping |
| CFDictionarySetValue(plugin->_tokenToBrowserMap, launchdToken, browser); |
| |
| // Release Memory |
| CFRelease(eventDictionary); |
| } |
| |
| /***************************************************************************** |
| * RemoveEventFromPlugin |
| * - |
| * This method is invoked when launchd wishes the plugin to setup a launch |
| * event matching the parameters in the dictionary. |
| *****************************************************************************/ |
| void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken) |
| { |
| NetBrowserInfo* browser = (NetBrowserInfo*)CFDictionaryGetValue(plugin->_tokenToBrowserMap, launchdToken); |
| Boolean othersUsingBrowser = false; |
| |
| if (!browser) |
| { |
| long long value = 0; |
| CFNumberGetValue(launchdToken, kCFNumberLongLongType, &value); |
| fprintf(stderr, "%s:%s Launchd asked us to remove a token we did not register! ==Token:%lld== \n", sPluginIdentifier, __FUNCTION__, value); |
| return; |
| } |
| |
| CFMutableArrayRef onAddEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onAddEvents, browser); |
| CFMutableArrayRef onRemoveEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onRemoveEvents, browser); |
| |
| if (onAddEvents) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Calling RemoveEventFromArray for OnAddEvents", sPluginIdentifier, __FUNCTION__); |
| RemoveEventFromArray(onAddEvents, launchdToken); |
| |
| // Is the array now empty, clean up |
| if (CFArrayGetCount(onAddEvents) == 0) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing the browser from AddEvents", sPluginIdentifier, __FUNCTION__); |
| CFDictionaryRemoveValue(plugin->_onAddEvents, browser); |
| } |
| } |
| |
| if (onRemoveEvents) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Calling RemoveEventFromArray for OnRemoveEvents", sPluginIdentifier, __FUNCTION__); |
| RemoveEventFromArray(onRemoveEvents, launchdToken); |
| |
| // Is the array now empty, clean up |
| if (CFArrayGetCount(onRemoveEvents) == 0) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing the browser from RemoveEvents", sPluginIdentifier, __FUNCTION__); |
| CFDictionaryRemoveValue(plugin->_onRemoveEvents, browser); |
| } |
| } |
| |
| // Remove ourselves from the token dictionary. |
| CFDictionaryRemoveValue(plugin->_tokenToBrowserMap, launchdToken); |
| |
| // Check to see if anyone else is using this browser. |
| CFIndex i; |
| CFIndex count = CFDictionaryGetCount(plugin->_tokenToBrowserMap); |
| NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*)); |
| |
| // Fetch the values of the token dictionary |
| CFDictionaryGetKeysAndValues(plugin->_tokenToBrowserMap, NULL, (const void**)browsers); |
| |
| for (i = 0; i < count; ++i) |
| { |
| if (NetBrowserInfoEqual(browsers[i], browser)) |
| { |
| othersUsingBrowser = true; |
| break; |
| } |
| } |
| |
| // If no one else is useing our browser, clean up! |
| if (!othersUsingBrowser) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing browser %p from _browsers", sPluginIdentifier, __FUNCTION__, browser); |
| CFDictionaryRemoveValue(plugin->_browsers, browser); // This triggers release and dealloc of the browser |
| } |
| else |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Decrementing browsers %p count", sPluginIdentifier, __FUNCTION__, browser); |
| // Decrement my reference count (it was incremented when it was added to _browsers in CreateBrowser) |
| NetBrowserInfoRelease(NULL, browser); |
| } |
| |
| free(browsers); |
| } |
| |
| |
| /***************************************************************************** |
| * CreateBrowser |
| * - |
| * This method returns a NetBrowserInfo that is looking for a type of |
| * service in a domain. If no browser exists, it will create one and return it. |
| *****************************************************************************/ |
| NetBrowserInfo* CreateBrowser(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain) |
| { |
| CFIndex i; |
| CFIndex count = CFDictionaryGetCount(plugin->_browsers); |
| NetBrowserInfo* browser = NULL; |
| CFDictionaryRef* dicts = malloc(count * sizeof(CFDictionaryRef)); |
| NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*)); |
| |
| // Fetch the values of the browser dictionary |
| CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, (const void**)dicts); |
| |
| |
| // Loop thru the browsers list and see if we can find a matching one. |
| for (i = 0; i < count; ++i) |
| { |
| CFDictionaryRef browserDict = dicts[i]; |
| |
| CFStringRef browserType = CFDictionaryGetValue(browserDict, sServiceTypeKey); |
| CFStringRef browserDomain = CFDictionaryGetValue(browserDict, sServiceDomainKey); |
| |
| // If we have a matching browser, break |
| if ((CFStringCompare(browserType, type, kCFCompareCaseInsensitive) == kCFCompareEqualTo) && |
| (CFStringCompare(browserDomain, domain, kCFCompareCaseInsensitive) == kCFCompareEqualTo)) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: found a duplicate browser\n", sPluginIdentifier, __FUNCTION__); |
| browser = browsers[i]; |
| NetBrowserInfoRetain(NULL, browser); |
| break; |
| } |
| } |
| |
| // No match found, lets create one! |
| if (!browser) |
| { |
| |
| browser = NetBrowserInfoCreate(type, domain, plugin); |
| |
| if (!browser) |
| { |
| fprintf(stderr, "%s:%s failed to search for %s.%s", sPluginIdentifier, __FUNCTION__, CStringFromCFString(type), CStringFromCFString(domain)); |
| free(dicts); |
| free(browsers); |
| return NULL; |
| } |
| |
| // Service browser created, lets add this to ourselves to the dictionary. |
| CFMutableDictionaryRef browserDict = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| |
| CFDictionarySetValue(browserDict, sServiceTypeKey, type); |
| CFDictionarySetValue(browserDict, sServiceDomainKey, domain); |
| |
| // Add the dictionary to the browsers dictionary. |
| CFDictionarySetValue(plugin->_browsers, browser, browserDict); |
| |
| NetBrowserInfoRelease(NULL, browser); |
| |
| // Release Memory |
| CFRelease(browserDict); |
| } |
| |
| free(dicts); |
| free(browsers); |
| |
| return browser; |
| } |
| |
| /***************************************************************************** |
| * BrowserForSDRef |
| * - |
| * This method returns a NetBrowserInfo that matches the calling SDRef passed |
| * in via the callback. |
| *****************************************************************************/ |
| NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef) |
| { |
| CFIndex i; |
| CFIndex count = CFDictionaryGetCount(plugin->_browsers); |
| NetBrowserInfo* browser = NULL; |
| NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*)); |
| |
| // Fetch the values of the browser dictionary |
| CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, NULL); |
| |
| // Loop thru the browsers list and see if we can find a matching one. |
| for (i = 0; i < count; ++i) |
| { |
| NetBrowserInfo* currentBrowser = browsers[i]; |
| |
| if (currentBrowser->browserRef == sdRef) |
| { |
| browser = currentBrowser; |
| break; |
| } |
| } |
| |
| |
| free(browsers); |
| |
| return browser; |
| } |
| |
| /***************************************************************************** |
| * AddEventDictionary |
| * - |
| * Adds a event to a browser's event dictionary |
| *****************************************************************************/ |
| |
| void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key) |
| { |
| CFMutableArrayRef eventsForBrowser = (CFMutableArrayRef)CFDictionaryGetValue(allEventsDictionary, key); |
| |
| if (!eventsForBrowser) // We have no events for this browser yet, lets add him. |
| { |
| eventsForBrowser = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); |
| CFDictionarySetValue(allEventsDictionary, key, eventsForBrowser); |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s creating a new array", sPluginIdentifier, __FUNCTION__); |
| } |
| else |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s Incrementing refcount", sPluginIdentifier, __FUNCTION__); |
| CFRetain(eventsForBrowser); |
| } |
| |
| CFArrayAppendValue(eventsForBrowser, eventDict); |
| CFRelease(eventsForBrowser); |
| } |
| |
| /***************************************************************************** |
| * RemoveEventFromArray |
| * - |
| * Searches a Array of Event Dictionaries to find one with a matching launchd |
| * token and remove it. |
| *****************************************************************************/ |
| |
| void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken) |
| { |
| CFIndex i; |
| CFIndex count = CFArrayGetCount(array); |
| |
| // Loop thru looking for us. |
| for (i = 0; i < count; ) |
| { |
| CFDictionaryRef eventDict = CFArrayGetValueAtIndex(array, i); |
| CFNumberRef token = CFDictionaryGetValue(eventDict, sLaunchdTokenKey); |
| |
| if (CFEqual(token, launchdToken)) // This is the same event? |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s found token", sPluginIdentifier, __FUNCTION__); |
| CFArrayRemoveValueAtIndex(array, i); // Remove the event, |
| break; // The token should only exist once, so it makes no sense to continue. |
| } |
| else |
| { |
| ++i; // If it's not us, advance. |
| } |
| } |
| if (i == count) asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s did not find token", sPluginIdentifier, __FUNCTION__); |
| } |
| |
| #pragma mark - |
| #pragma mark Net Service Browser Stuff |
| #pragma mark - |
| |
| /***************************************************************************** |
| * ServiceBrowserCallback |
| * - |
| * This method is the heart of the plugin. It's the runloop callback annoucing |
| * the appearence and disappearance of network services. |
| *****************************************************************************/ |
| |
| void ServiceBrowserCallback (DNSServiceRef sdRef, |
| DNSServiceFlags flags, |
| uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, |
| const char* serviceName, |
| const char* regtype, |
| const char* replyDomain, |
| void* context ) |
| { |
| (void)interfaceIndex; |
| (void)regtype; |
| (void)replyDomain; |
| BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)context; |
| NetBrowserInfo* browser = BrowserForSDRef(plugin, sdRef); |
| |
| if (!browser) // Missing browser? |
| { |
| fprintf(stderr, "%s:%s ServiceBrowserCallback: missing browser\n", sPluginIdentifier, __FUNCTION__); |
| return; |
| } |
| |
| if (errorCode != kDNSServiceErr_NoError) |
| { |
| fprintf(stderr, "%s:%s ServiceBrowserCallback: errcode set %d\n", sPluginIdentifier, __FUNCTION__, errorCode); |
| return; |
| } |
| |
| CFStringRef cfServiceName = CFStringCreateWithCString(NULL, serviceName, kCFStringEncodingUTF8); |
| |
| if (flags & kDNSServiceFlagsAdd) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling HandleTemporaryEventsForService Add\n", sPluginIdentifier, __FUNCTION__); |
| HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onAddEvents); |
| } |
| else |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling HandleTemporaryEventsForService Remove\n", sPluginIdentifier, __FUNCTION__); |
| HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onRemoveEvents); |
| } |
| |
| CFRelease(cfServiceName); |
| } |
| |
| /***************************************************************************** |
| * HandleTemporaryEventsForService |
| * - |
| * This method handles the firing of one shot events. Aka. Events that are |
| * signaled when a service appears / disappears. They have a temporarly |
| * signaled state. |
| *****************************************************************************/ |
| void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary) |
| { |
| CFArrayRef events = (CFArrayRef)CFDictionaryGetValue(eventsDictionary, browser); // Get events for the browser we passed in. |
| CFIndex i; |
| CFIndex count; |
| |
| if (!events) // Somehow we have a orphan browser... |
| return; |
| |
| count = CFArrayGetCount(events); |
| |
| // Go thru the events and run filters, notifity if they pass. |
| for (i = 0; i < count; ++i) |
| { |
| CFDictionaryRef eventDict = (CFDictionaryRef)CFArrayGetValueAtIndex(events, i); |
| CFStringRef eventServiceName = (CFStringRef)CFDictionaryGetValue(eventDict, sServiceNameKey); |
| CFNumberRef token = (CFNumberRef) CFDictionaryGetValue(eventDict, sLaunchdTokenKey); |
| CFDictionaryRef dict = (CFDictionaryRef) CFDictionaryGetValue(eventDict, sLaunchdDictKey); |
| |
| // Currently we only filter on service name, that makes this as simple as... |
| if (!eventServiceName || CFEqual(serviceName, eventServiceName)) |
| { |
| uint64_t tokenUint64; |
| // Signal Event: This is edge trigger. When the action has been taken, it will not |
| // be remembered anymore. |
| |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s HandleTemporaryEventsForService signal\n", sPluginIdentifier, __FUNCTION__); |
| CFNumberGetValue(token, kCFNumberLongLongType, &tokenUint64); |
| |
| xpc_object_t jobRequest = _CFXPCCreateXPCObjectFromCFObject(dict); |
| |
| UserEventAgentFireEvent(plugin->_pluginContext, tokenUint64, jobRequest); |
| xpc_release(jobRequest); |
| } |
| } |
| } |
| |
| #pragma mark - |
| #pragma mark Convenience |
| #pragma mark - |
| |
| /***************************************************************************** |
| * CStringFromCFString |
| * - |
| * Silly convenence function for dealing with non-critical CFSTR -> cStr |
| * conversions. |
| *****************************************************************************/ |
| |
| const char* CStringFromCFString(CFStringRef string) |
| { |
| const char* defaultString = "??????"; |
| const char* cstring; |
| |
| if (!string) |
| return defaultString; |
| |
| cstring = CFStringGetCStringPtr(string, kCFStringEncodingUTF8); |
| |
| return (cstring) ? cstring : defaultString; |
| |
| } |
| |
| #pragma mark - |
| #pragma mark NetBrowserInfo "Object" |
| #pragma mark - |
| /***************************************************************************** |
| * NetBrowserInfoCreate |
| * - |
| * The method creates a NetBrowserInfo Object and initalizes it. |
| *****************************************************************************/ |
| NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context) |
| { |
| NetBrowserInfo* outObj = NULL; |
| DNSServiceRef browserRef = NULL; |
| char* cServiceType = NULL; |
| char* cDomain = NULL; |
| Boolean success = true; |
| |
| CFIndex serviceSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(serviceType), kCFStringEncodingUTF8); |
| cServiceType = calloc(serviceSize, 1); |
| success = CFStringGetCString(serviceType, cServiceType, serviceSize, kCFStringEncodingUTF8); |
| |
| |
| if (domain) |
| { |
| CFIndex domainSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(domain), kCFStringEncodingUTF8); |
| cDomain = calloc(serviceSize, 1); |
| success = success && CFStringGetCString(domain, cDomain, domainSize, kCFStringEncodingUTF8); |
| } |
| |
| if (!success) |
| { |
| fprintf(stderr, "%s:%s LaunchEvent has badly encoded service type or domain.\n", sPluginIdentifier, __FUNCTION__); |
| free(cServiceType); |
| |
| if (cDomain) |
| free(cDomain); |
| |
| return NULL; |
| } |
| |
| DNSServiceErrorType err = DNSServiceBrowse(&browserRef, 0, 0, cServiceType, cDomain, ServiceBrowserCallback, context); |
| |
| if (err != kDNSServiceErr_NoError) |
| { |
| fprintf(stderr, "%s:%s Failed to create browser for %s, %s\n", sPluginIdentifier, __FUNCTION__, cServiceType, cDomain); |
| free(cServiceType); |
| |
| if (cDomain) |
| free(cDomain); |
| |
| return NULL; |
| } |
| |
| DNSServiceSetDispatchQueue(browserRef, dispatch_get_main_queue()); |
| |
| |
| outObj = malloc(sizeof(NetBrowserInfo)); |
| |
| outObj->refCount = 1; |
| outObj->browserRef = browserRef; |
| |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: created new object %p", sPluginIdentifier, __FUNCTION__, outObj); |
| |
| free(cServiceType); |
| |
| if (cDomain) |
| free(cDomain); |
| |
| return outObj; |
| } |
| |
| /***************************************************************************** |
| * NetBrowserInfoRetain |
| * - |
| * The method retains a NetBrowserInfo object. |
| *****************************************************************************/ |
| const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info) |
| { |
| (void)allocator; |
| NetBrowserInfo* obj = (NetBrowserInfo*)info; |
| |
| if (!obj) |
| return NULL; |
| |
| ++obj->refCount; |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Incremented ref count on %p, count %d", sPluginIdentifier, __FUNCTION__, obj->browserRef, (int)obj->refCount); |
| |
| return obj; |
| } |
| |
| /***************************************************************************** |
| * NetBrowserInfoRelease |
| * - |
| * The method releases a NetBrowserInfo object. |
| *****************************************************************************/ |
| void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info) |
| { |
| (void)allocator; |
| NetBrowserInfo* obj = (NetBrowserInfo*)info; |
| |
| if (!obj) |
| return; |
| |
| if (obj->refCount == 1) |
| { |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: DNSServiceRefDeallocate %p", sPluginIdentifier, __FUNCTION__, obj->browserRef); |
| DNSServiceRefDeallocate(obj->browserRef); |
| free(obj); |
| } |
| else |
| { |
| --obj->refCount; |
| asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Decremented ref count on %p, count %d", sPluginIdentifier, __FUNCTION__, obj->browserRef, (int)obj->refCount); |
| } |
| |
| } |
| |
| /***************************************************************************** |
| * NetBrowserInfoEqual |
| * - |
| * The method is used to compare two NetBrowserInfo objects for equality. |
| *****************************************************************************/ |
| Boolean NetBrowserInfoEqual(const void *value1, const void *value2) |
| { |
| NetBrowserInfo* obj1 = (NetBrowserInfo*)value1; |
| NetBrowserInfo* obj2 = (NetBrowserInfo*)value2; |
| |
| if (obj1->browserRef == obj2->browserRef) |
| return true; |
| |
| return false; |
| } |
| |
| /***************************************************************************** |
| * NetBrowserInfoHash |
| * - |
| * The method is used to make a hash for the object. We can cheat and use the |
| * browser pointer. |
| *****************************************************************************/ |
| CFHashCode NetBrowserInfoHash(const void *value) |
| { |
| return (CFHashCode)((NetBrowserInfo*)value)->browserRef; |
| } |
| |
| |
| /***************************************************************************** |
| * NetBrowserInfoCopyDescription |
| * - |
| * Make CF happy. |
| *****************************************************************************/ |
| CFStringRef NetBrowserInfoCopyDescription(const void *value) |
| { |
| (void)value; |
| return CFStringCreateWithCString(NULL, "NetBrowserInfo: No useful description", kCFStringEncodingUTF8); |
| } |
| |