diff options
Diffstat (limited to 'mDNSResponder/mDNSMacOSX/BonjourEvents.c')
-rw-r--r-- | mDNSResponder/mDNSMacOSX/BonjourEvents.c | 991 |
1 files changed, 991 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSMacOSX/BonjourEvents.c b/mDNSResponder/mDNSMacOSX/BonjourEvents.c new file mode 100644 index 00000000..b9308189 --- /dev/null +++ b/mDNSResponder/mDNSMacOSX/BonjourEvents.c @@ -0,0 +1,991 @@ +/* -*- 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); + if (domainSize) + { + cDomain = calloc(domainSize, 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); +} + |