1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2010 Apple Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <CoreFoundation/CoreFoundation.h>
19#include <CoreFoundation/CFXPCBridge.h>
20#include "dns_sd.h"
21#include <UserEventAgentInterface.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <asl.h>
25#include <xpc/xpc.h>
26
27
28#pragma mark -
29#pragma mark Types
30#pragma mark -
31static const char*          sPluginIdentifier       = "com.apple.bonjour.events";
32
33// PLIST Keys
34static const CFStringRef sServiceNameKey         = CFSTR("ServiceName");
35static const CFStringRef sServiceTypeKey         = CFSTR("ServiceType");
36static const CFStringRef sServiceDomainKey       = CFSTR("ServiceDomain");
37
38static const CFStringRef sOnServiceAddKey        = CFSTR("OnServiceAdd");
39static const CFStringRef sOnServiceRemoveKey     = CFSTR("OnServiceRemove");
40
41static const CFStringRef sLaunchdTokenKey        = CFSTR("LaunchdToken");
42static const CFStringRef sLaunchdDictKey         = CFSTR("LaunchdDict");
43
44
45/************************************************
46* Launch Event Dictionary (input from launchd)
47* Passed To: ManageEventsCallback
48*-----------------------------------------------
49* Typing in this dictionary is not enforced
50* above us. So this may not be true. Type check
51* all input before using it.
52*-----------------------------------------------
53* sServiceNameKey		- CFString (Optional)
54* sServiceTypeKey		- CFString
55* sServiceDomainKey	- CFString
56*
57* One or more of the following.
58*-----------------------------------
59* sOnServiceAddKey			- CFBoolean
60* sOnServiceRemoveKey		- CFBoolean
61* sWhileServiceExistsKey	- CFBoolean
62************************************************/
63
64/************************************************
65* Browser Dictionary
66*-----------------------------------------------
67* sServiceDomainKey - CFString
68* sServiceTypeKey   - CFString
69************************************************/
70
71/************************************************
72* Event Dictionary
73*-----------------------------------------------
74* sServiceNameKey	 - CFString (Optional)
75* sLaunchdTokenKey	 - CFNumber
76************************************************/
77
78typedef struct {
79    UserEventAgentInterfaceStruct*      _UserEventAgentInterface;
80    CFUUIDRef _factoryID;
81    UInt32 _refCount;
82
83    void*                               _pluginContext;
84
85    CFMutableDictionaryRef _tokenToBrowserMap;                  // Maps a token to a browser that can be used to scan the remaining dictionaries.
86    CFMutableDictionaryRef _browsers;                           // A Dictionary of Browser Dictionaries where the resposible browser is the key.
87    CFMutableDictionaryRef _onAddEvents;                        // A Dictionary of Event Dictionaries that describe events to trigger on a service appearing.
88    CFMutableDictionaryRef _onRemoveEvents;                     // A Dictionary of Event Dictionaries that describe events to trigger on a service disappearing.
89} BonjourUserEventsPlugin;
90
91typedef struct {
92    CFIndex refCount;
93    DNSServiceRef browserRef;
94} NetBrowserInfo;
95
96#pragma mark -
97#pragma mark Prototypes
98#pragma mark -
99// COM Stuff
100static HRESULT  QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv);
101static ULONG    AddRef(void* instance);
102static ULONG    Release(void* instance);
103
104static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID);
105static void Dealloc(BonjourUserEventsPlugin* plugin);
106
107void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID);
108
109// Plugin Management
110static void Install(void* instance);
111static void ManageEventsCallback(
112    UserEventAgentLaunchdAction action,
113    CFNumberRef token,
114    CFTypeRef eventMatchDict,
115    void                      * vContext);
116
117
118// Plugin Guts
119void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters);
120void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchToken);
121
122NetBrowserInfo* CreateBrowser(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain);
123NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef);
124void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key);
125void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken);
126
127// Net Service Browser Stuff
128void ServiceBrowserCallback (DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* serviceName, const char* regtype, const char* replyDomain, void* context);
129void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary);
130
131// Convence Stuff
132const char* CStringFromCFString(CFStringRef string);
133
134// NetBrowserInfo "Object"
135NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context);
136const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info);
137void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info);
138Boolean NetBrowserInfoEqual(const void *value1, const void *value2);
139CFHashCode  NetBrowserInfoHash(const void *value);
140CFStringRef NetBrowserInfoCopyDescription(const void *value);
141
142static const CFDictionaryKeyCallBacks kNetBrowserInfoDictionaryKeyCallbacks = {
143    0,
144    NetBrowserInfoRetain,
145    NetBrowserInfoRelease,
146    NetBrowserInfoCopyDescription,
147    NetBrowserInfoEqual,
148    NetBrowserInfoHash
149};
150
151static const CFDictionaryValueCallBacks kNetBrowserInfoDictionaryValueCallbacks = {
152    0,
153    NetBrowserInfoRetain,
154    NetBrowserInfoRelease,
155    NetBrowserInfoCopyDescription,
156    NetBrowserInfoEqual
157};
158
159// COM type definition goop.
160static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
161    NULL,                   // Required padding for COM
162    QueryInterface,         // Query Interface
163    AddRef,                 // AddRef()
164    Release,                // Release()
165    Install                 // Install
166};
167
168#pragma mark -
169#pragma mark COM Management
170#pragma mark -
171
172/*****************************************************************************
173*****************************************************************************/
174static HRESULT QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv)
175{
176    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
177
178    // Test the requested ID against the valid interfaces.
179    if(CFEqual(interfaceID, kUserEventAgentInterfaceID))
180    {
181        ((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
182        *ppv = myInstance;
183        CFRelease(interfaceID);
184        return S_OK;
185    }
186    else if(CFEqual(interfaceID, IUnknownUUID))
187    {
188        ((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
189        *ppv = myInstance;
190        CFRelease(interfaceID);
191        return S_OK;
192    }
193    else //  Requested interface unknown, bail with error.
194    {
195        *ppv = NULL;
196        CFRelease(interfaceID);
197        return E_NOINTERFACE;
198    }
199}
200
201/*****************************************************************************
202*****************************************************************************/
203static ULONG AddRef(void* instance)
204{
205    BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;
206    return ++plugin->_refCount;
207}
208
209/*****************************************************************************
210*****************************************************************************/
211static ULONG Release(void* instance)
212{
213    BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;
214
215    if (plugin->_refCount != 0)
216        --plugin->_refCount;
217
218    if (plugin->_refCount == 0)
219    {
220        Dealloc(instance);
221        return 0;
222    }
223
224    return plugin->_refCount;
225}
226
227/*****************************************************************************
228* Alloc
229* -
230* Functionas as both +[alloc] and -[init] for the plugin. Add any
231* initalization of member variables here.
232*****************************************************************************/
233static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID)
234{
235    BonjourUserEventsPlugin* plugin = malloc(sizeof(BonjourUserEventsPlugin));
236
237    plugin->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
238    plugin->_pluginContext = NULL;
239
240    if (factoryID)
241    {
242        plugin->_factoryID = (CFUUIDRef)CFRetain(factoryID);
243        CFPlugInAddInstanceForFactory(factoryID);
244    }
245
246    plugin->_refCount = 1;
247    plugin->_tokenToBrowserMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kNetBrowserInfoDictionaryValueCallbacks);
248    plugin->_browsers = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
249    plugin->_onAddEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
250    plugin->_onRemoveEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
251
252    return plugin;
253}
254
255/*****************************************************************************
256* Dealloc
257* -
258* Much like Obj-C dealloc this method is responsible for releasing any object
259* this plugin is holding. Unlike ObjC, you call directly free() instead of
260* [super dalloc].
261*****************************************************************************/
262static void Dealloc(BonjourUserEventsPlugin* plugin)
263{
264    CFUUIDRef factoryID = plugin->_factoryID;
265
266    if (factoryID)
267    {
268        CFPlugInRemoveInstanceForFactory(factoryID);
269        CFRelease(factoryID);
270    }
271
272    if (plugin->_tokenToBrowserMap)
273        CFRelease(plugin->_tokenToBrowserMap);
274
275    if (plugin->_browsers)
276        CFRelease(plugin->_browsers);
277
278    if (plugin->_onAddEvents)
279        CFRelease(plugin->_onAddEvents);
280
281    if (plugin->_onRemoveEvents)
282        CFRelease(plugin->_onRemoveEvents);
283
284    free(plugin);
285}
286
287/*******************************************************************************
288*******************************************************************************/
289void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
290{
291    (void)allocator;
292    BonjourUserEventsPlugin * result = NULL;
293
294    if (typeID && CFEqual(typeID, kUserEventAgentTypeID)) {
295        result = Alloc(kUserEventAgentFactoryID);
296    }
297
298    return (void *)result;
299}
300
301#pragma mark -
302#pragma mark Plugin Management
303#pragma mark -
304/*****************************************************************************
305* Install
306* -
307* This is invoked once when the plugin is loaded to do initial setup and
308* allow us to register with launchd. If UserEventAgent crashes, the plugin
309* will need to be reloaded, and hence this will get invoked again.
310*****************************************************************************/
311static void Install(void *instance)
312{
313    BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;
314
315    plugin->_pluginContext = UserEventAgentRegisterForLaunchEvents(sPluginIdentifier, &ManageEventsCallback, plugin);
316
317    if (!plugin->_pluginContext)
318    {
319        fprintf(stderr, "%s:%s failed to register for launch events.\n", sPluginIdentifier, __FUNCTION__);
320        return;
321    }
322
323}
324
325/*****************************************************************************
326* ManageEventsCallback
327* -
328* This is invoked when launchd loads a event dictionary and needs to inform
329* us what a daemon / agent is looking for.
330*****************************************************************************/
331static void ManageEventsCallback(UserEventAgentLaunchdAction action, CFNumberRef token, CFTypeRef eventMatchDict, void* vContext)
332{
333    if (action == kUserEventAgentLaunchdAdd)
334    {
335        if (!eventMatchDict)
336        {
337            fprintf(stderr, "%s:%s empty dictionary\n", sPluginIdentifier, __FUNCTION__);
338            return;
339        }
340        if (CFGetTypeID(eventMatchDict) != CFDictionaryGetTypeID())
341        {
342            fprintf(stderr, "%s:%s given non-dict for event dictionary, action %d\n", sPluginIdentifier, __FUNCTION__, action);
343            return;
344        }
345        // Launchd wants us to add a launch event for this token and matching dictionary.
346        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling AddEventToPlugin", sPluginIdentifier, __FUNCTION__);
347        AddEventToPlugin((BonjourUserEventsPlugin*)vContext, token, (CFDictionaryRef)eventMatchDict);
348    }
349    else if (action == kUserEventAgentLaunchdRemove)
350    {
351        // Launchd wants us to remove the event hook we setup for this token / matching dictionary.
352        // Note: eventMatchDict can be NULL for Remove.
353        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling RemoveEventToPlugin", sPluginIdentifier, __FUNCTION__);
354        RemoveEventFromPlugin((BonjourUserEventsPlugin*)vContext, token);
355    }
356    else
357    {
358        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s unknown callback event\n", sPluginIdentifier, __FUNCTION__);
359    }
360}
361
362
363#pragma mark -
364#pragma mark Plugin Guts
365#pragma mark -
366
367/*****************************************************************************
368* AddEventToPlugin
369* -
370* This method is invoked when launchd wishes the plugin to setup a launch
371* event matching the parameters in the dictionary.
372*****************************************************************************/
373void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters)
374{
375    CFStringRef domain = CFDictionaryGetValue(eventParameters, sServiceDomainKey);
376    CFStringRef type = CFDictionaryGetValue(eventParameters, sServiceTypeKey);
377    CFStringRef name = CFDictionaryGetValue(eventParameters, sServiceNameKey);
378    CFBooleanRef cfOnAdd = CFDictionaryGetValue(eventParameters, sOnServiceAddKey);
379    CFBooleanRef cfOnRemove = CFDictionaryGetValue(eventParameters, sOnServiceRemoveKey);
380
381    Boolean onAdd = false;
382    Boolean onRemove = false;
383
384    if (cfOnAdd && CFGetTypeID(cfOnAdd) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnAdd))
385        onAdd = true;
386
387    if (cfOnRemove && CFGetTypeID(cfOnRemove) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnRemove))
388        onRemove = true;
389
390    // A type is required. If none is specified, BAIL
391    if (!type || CFGetTypeID(type) != CFStringGetTypeID())
392    {
393        fprintf(stderr, "%s:%s: a LaunchEvent is missing a service type.\n", sPluginIdentifier, __FUNCTION__);
394        return;
395    }
396
397    // If we aren't suppose to launch on services appearing or disappearing, this service does nothing. Ignore.
398    if (!onAdd && !onRemove)
399    {
400        fprintf(stderr, "%s:%s a LaunchEvent is missing both onAdd and onRemove events\n", sPluginIdentifier, __FUNCTION__);
401        return;
402    }
403
404    // If no domain is specified, assume local.
405    if (!domain)
406    {
407        domain = CFSTR("local");
408    }
409    else if (CFGetTypeID(domain) != CFStringGetTypeID() ) // If the domain is not a string, fail
410    {
411        fprintf(stderr, "%s:%s a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier, __FUNCTION__);
412        return;
413    }
414
415    // If we have a name filter, but it's not a string. This event is broken, bail.
416    if (name && CFGetTypeID(name) != CFStringGetTypeID())
417    {
418        fprintf(stderr, "%s:%s a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier, __FUNCTION__);
419        return;
420    }
421
422    // Get us a browser
423    NetBrowserInfo* browser = CreateBrowser(plugin, type, domain);
424
425    if (!browser)
426    {
427        fprintf(stderr, "%s:%s cannot create browser\n", sPluginIdentifier, __FUNCTION__);
428        return;
429    }
430
431    // Create Event Dictionary
432    CFMutableDictionaryRef eventDictionary = CFDictionaryCreateMutable(NULL, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
433
434    // We store both the Token and the Dictionary. UserEventAgentSetLaunchEventState needs
435    // the token and UserEventAgentSetFireEvent needs both the token and the dictionary
436    CFDictionarySetValue(eventDictionary, sLaunchdTokenKey, launchdToken);
437    CFDictionarySetValue(eventDictionary, sLaunchdDictKey, eventParameters);
438
439    if (name)
440        CFDictionarySetValue(eventDictionary, sServiceNameKey, name);
441
442    // Add to the correct dictionary.
443    if (onAdd)
444    {
445        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Adding browser to AddEvents", sPluginIdentifier, __FUNCTION__);
446        AddEventDictionary(eventDictionary, plugin->_onAddEvents, browser);
447    }
448
449    if (onRemove)
450    {
451        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Adding browser to RemoveEvents", sPluginIdentifier, __FUNCTION__);
452        AddEventDictionary(eventDictionary, plugin->_onRemoveEvents, browser);
453    }
454
455    // Add Token Mapping
456    CFDictionarySetValue(plugin->_tokenToBrowserMap, launchdToken, browser);
457
458    // Release Memory
459    CFRelease(eventDictionary);
460}
461
462/*****************************************************************************
463* RemoveEventFromPlugin
464* -
465* This method is invoked when launchd wishes the plugin to setup a launch
466* event matching the parameters in the dictionary.
467*****************************************************************************/
468void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken)
469{
470    NetBrowserInfo* browser = (NetBrowserInfo*)CFDictionaryGetValue(plugin->_tokenToBrowserMap, launchdToken);
471    Boolean othersUsingBrowser = false;
472
473    if (!browser)
474    {
475        long long value = 0;
476        CFNumberGetValue(launchdToken, kCFNumberLongLongType, &value);
477        fprintf(stderr, "%s:%s Launchd asked us to remove a token we did not register! ==Token:%lld== \n", sPluginIdentifier, __FUNCTION__, value);
478        return;
479    }
480
481    CFMutableArrayRef onAddEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onAddEvents, browser);
482    CFMutableArrayRef onRemoveEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onRemoveEvents, browser);
483
484    if (onAddEvents)
485    {
486        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Calling RemoveEventFromArray for OnAddEvents", sPluginIdentifier, __FUNCTION__);
487        RemoveEventFromArray(onAddEvents, launchdToken);
488
489        // Is the array now empty, clean up
490        if (CFArrayGetCount(onAddEvents) == 0)
491        {
492            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing the browser from AddEvents", sPluginIdentifier, __FUNCTION__);
493            CFDictionaryRemoveValue(plugin->_onAddEvents, browser);
494        }
495    }
496
497    if (onRemoveEvents)
498    {
499        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Calling RemoveEventFromArray for OnRemoveEvents", sPluginIdentifier, __FUNCTION__);
500        RemoveEventFromArray(onRemoveEvents, launchdToken);
501
502        // Is the array now empty, clean up
503        if (CFArrayGetCount(onRemoveEvents) == 0)
504        {
505            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing the browser from RemoveEvents", sPluginIdentifier, __FUNCTION__);
506            CFDictionaryRemoveValue(plugin->_onRemoveEvents, browser);
507        }
508    }
509
510    // Remove ourselves from the token dictionary.
511    CFDictionaryRemoveValue(plugin->_tokenToBrowserMap, launchdToken);
512
513    // Check to see if anyone else is using this browser.
514    CFIndex i;
515    CFIndex count = CFDictionaryGetCount(plugin->_tokenToBrowserMap);
516    NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));
517
518    // Fetch the values of the token dictionary
519    CFDictionaryGetKeysAndValues(plugin->_tokenToBrowserMap, NULL, (const void**)browsers);
520
521    for (i = 0; i < count; ++i)
522    {
523        if (NetBrowserInfoEqual(browsers[i], browser))
524        {
525            othersUsingBrowser = true;
526            break;
527        }
528    }
529
530    // If no one else is useing our browser, clean up!
531    if (!othersUsingBrowser)
532    {
533        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Removing browser %p from _browsers", sPluginIdentifier, __FUNCTION__, browser);
534        CFDictionaryRemoveValue(plugin->_browsers, browser); // This triggers release and dealloc of the browser
535    }
536    else
537    {
538        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Decrementing browsers %p count", sPluginIdentifier, __FUNCTION__, browser);
539        // Decrement my reference count (it was incremented when it was added to _browsers in CreateBrowser)
540        NetBrowserInfoRelease(NULL, browser);
541    }
542
543    free(browsers);
544}
545
546
547/*****************************************************************************
548* CreateBrowser
549* -
550* This method returns a NetBrowserInfo that is looking for a type of
551* service in a domain. If no browser exists, it will create one and return it.
552*****************************************************************************/
553NetBrowserInfo* CreateBrowser(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain)
554{
555    CFIndex i;
556    CFIndex count = CFDictionaryGetCount(plugin->_browsers);
557    NetBrowserInfo* browser = NULL;
558    CFDictionaryRef* dicts = malloc(count * sizeof(CFDictionaryRef));
559    NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));
560
561    // Fetch the values of the browser dictionary
562    CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, (const void**)dicts);
563
564
565    // Loop thru the browsers list and see if we can find a matching one.
566    for (i = 0; i < count; ++i)
567    {
568        CFDictionaryRef browserDict = dicts[i];
569
570        CFStringRef browserType = CFDictionaryGetValue(browserDict, sServiceTypeKey);
571        CFStringRef browserDomain = CFDictionaryGetValue(browserDict, sServiceDomainKey);
572
573        // If we have a matching browser, break
574        if ((CFStringCompare(browserType, type, kCFCompareCaseInsensitive) == kCFCompareEqualTo) &&
575            (CFStringCompare(browserDomain, domain, kCFCompareCaseInsensitive) == kCFCompareEqualTo))
576        {
577            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: found a duplicate browser\n", sPluginIdentifier, __FUNCTION__);
578            browser = browsers[i];
579            NetBrowserInfoRetain(NULL, browser);
580            break;
581        }
582    }
583
584    // No match found, lets create one!
585    if (!browser)
586    {
587
588        browser = NetBrowserInfoCreate(type, domain, plugin);
589
590        if (!browser)
591        {
592            fprintf(stderr, "%s:%s failed to search for %s.%s", sPluginIdentifier, __FUNCTION__, CStringFromCFString(type), CStringFromCFString(domain));
593            free(dicts);
594            free(browsers);
595            return NULL;
596        }
597
598        // Service browser created, lets add this to ourselves to the dictionary.
599        CFMutableDictionaryRef browserDict = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
600
601        CFDictionarySetValue(browserDict, sServiceTypeKey, type);
602        CFDictionarySetValue(browserDict, sServiceDomainKey, domain);
603
604        // Add the dictionary to the browsers dictionary.
605        CFDictionarySetValue(plugin->_browsers, browser, browserDict);
606
607        NetBrowserInfoRelease(NULL, browser);
608
609        // Release Memory
610        CFRelease(browserDict);
611    }
612
613    free(dicts);
614    free(browsers);
615
616    return browser;
617}
618
619/*****************************************************************************
620* BrowserForSDRef
621* -
622* This method returns a NetBrowserInfo that matches the calling SDRef passed
623* in via the callback.
624*****************************************************************************/
625NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef)
626{
627    CFIndex i;
628    CFIndex count = CFDictionaryGetCount(plugin->_browsers);
629    NetBrowserInfo* browser = NULL;
630    NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));
631
632    // Fetch the values of the browser dictionary
633    CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, NULL);
634
635    // Loop thru the browsers list and see if we can find a matching one.
636    for (i = 0; i < count; ++i)
637    {
638        NetBrowserInfo* currentBrowser = browsers[i];
639
640        if (currentBrowser->browserRef == sdRef)
641        {
642            browser = currentBrowser;
643            break;
644        }
645    }
646
647
648    free(browsers);
649
650    return browser;
651}
652
653/*****************************************************************************
654* AddEventDictionary
655* -
656* Adds a event to a browser's event dictionary
657*****************************************************************************/
658
659void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key)
660{
661    CFMutableArrayRef eventsForBrowser = (CFMutableArrayRef)CFDictionaryGetValue(allEventsDictionary, key);
662
663    if (!eventsForBrowser) // We have no events for this browser yet, lets add him.
664    {
665        eventsForBrowser = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
666        CFDictionarySetValue(allEventsDictionary, key, eventsForBrowser);
667        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s creating a new array", sPluginIdentifier, __FUNCTION__);
668    }
669    else
670    {
671        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s Incrementing refcount", sPluginIdentifier, __FUNCTION__);
672        CFRetain(eventsForBrowser);
673    }
674
675    CFArrayAppendValue(eventsForBrowser, eventDict);
676    CFRelease(eventsForBrowser);
677}
678
679/*****************************************************************************
680* RemoveEventFromArray
681* -
682* Searches a Array of Event Dictionaries to find one with a matching launchd
683* token and remove it.
684*****************************************************************************/
685
686void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken)
687{
688    CFIndex i;
689    CFIndex count = CFArrayGetCount(array);
690
691    // Loop thru looking for us.
692    for (i = 0; i < count; )
693    {
694        CFDictionaryRef eventDict = CFArrayGetValueAtIndex(array, i);
695        CFNumberRef token = CFDictionaryGetValue(eventDict, sLaunchdTokenKey);
696
697        if (CFEqual(token, launchdToken)) // This is the same event?
698        {
699            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s found token", sPluginIdentifier, __FUNCTION__);
700            CFArrayRemoveValueAtIndex(array, i);    // Remove the event,
701            break; // The token should only exist once, so it makes no sense to continue.
702        }
703        else
704        {
705            ++i; // If it's not us, advance.
706        }
707    }
708    if (i == count) asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s did not find token", sPluginIdentifier, __FUNCTION__);
709}
710
711#pragma mark -
712#pragma mark Net Service Browser Stuff
713#pragma mark -
714
715/*****************************************************************************
716* ServiceBrowserCallback
717* -
718* This method is the heart of the plugin. It's the runloop callback annoucing
719* the appearence and disappearance of network services.
720*****************************************************************************/
721
722void ServiceBrowserCallback (DNSServiceRef sdRef,
723                             DNSServiceFlags flags,
724                             uint32_t interfaceIndex,
725                             DNSServiceErrorType errorCode,
726                             const char*                serviceName,
727                             const char*                regtype,
728                             const char*                replyDomain,
729                             void*                      context )
730{
731    (void)interfaceIndex;
732    (void)regtype;
733    (void)replyDomain;
734    BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)context;
735    NetBrowserInfo* browser = BrowserForSDRef(plugin, sdRef);
736
737    if (!browser) // Missing browser?
738    {
739        fprintf(stderr, "%s:%s ServiceBrowserCallback: missing browser\n", sPluginIdentifier, __FUNCTION__);
740        return;
741    }
742
743    if (errorCode != kDNSServiceErr_NoError)
744    {
745        fprintf(stderr, "%s:%s ServiceBrowserCallback: errcode set %d\n", sPluginIdentifier, __FUNCTION__, errorCode);
746        return;
747    }
748
749    CFStringRef cfServiceName = CFStringCreateWithCString(NULL, serviceName, kCFStringEncodingUTF8);
750    if (cfServiceName == NULL)
751    {
752        static int msgCount = 0;
753        if (msgCount < 1000)
754        {
755            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s Can not create CFString for serviceName %s", sPluginIdentifier, __FUNCTION__, serviceName);
756            msgCount++;
757        }
758        return;
759    }
760
761    if (flags & kDNSServiceFlagsAdd)
762    {
763        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling HandleTemporaryEventsForService Add\n", sPluginIdentifier, __FUNCTION__);
764        HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onAddEvents);
765    }
766    else
767    {
768        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s calling HandleTemporaryEventsForService Remove\n", sPluginIdentifier, __FUNCTION__);
769        HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onRemoveEvents);
770    }
771
772    CFRelease(cfServiceName);
773}
774
775/*****************************************************************************
776* HandleTemporaryEventsForService
777* -
778* This method handles the firing of one shot events. Aka. Events that are
779* signaled when a service appears / disappears. They have a temporarly
780* signaled state.
781*****************************************************************************/
782void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary)
783{
784    CFArrayRef events = (CFArrayRef)CFDictionaryGetValue(eventsDictionary, browser); // Get events for the browser we passed in.
785    CFIndex i;
786    CFIndex count;
787
788    if (!events)  // Somehow we have a orphan browser...
789        return;
790
791    count = CFArrayGetCount(events);
792
793    // Go thru the events and run filters, notifity if they pass.
794    for (i = 0; i < count; ++i)
795    {
796        CFDictionaryRef eventDict = (CFDictionaryRef)CFArrayGetValueAtIndex(events, i);
797        CFStringRef eventServiceName = (CFStringRef)CFDictionaryGetValue(eventDict, sServiceNameKey);
798        CFNumberRef token = (CFNumberRef) CFDictionaryGetValue(eventDict, sLaunchdTokenKey);
799        CFDictionaryRef dict = (CFDictionaryRef) CFDictionaryGetValue(eventDict, sLaunchdDictKey);
800
801        // Currently we only filter on service name, that makes this as simple as...
802        if (!eventServiceName || CFEqual(serviceName, eventServiceName))
803        {
804            uint64_t tokenUint64;
805            // Signal Event: This is edge trigger. When the action has been taken, it will not
806            // be remembered anymore.
807
808            asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s HandleTemporaryEventsForService signal\n", sPluginIdentifier, __FUNCTION__);
809            CFNumberGetValue(token, kCFNumberLongLongType, &tokenUint64);
810
811            xpc_object_t jobRequest = _CFXPCCreateXPCObjectFromCFObject(dict);
812
813            UserEventAgentFireEvent(plugin->_pluginContext, tokenUint64, jobRequest);
814            xpc_release(jobRequest);
815        }
816    }
817}
818
819#pragma mark -
820#pragma mark Convenience
821#pragma mark -
822
823/*****************************************************************************
824* CStringFromCFString
825* -
826* Silly convenence function for dealing with non-critical CFSTR -> cStr
827* conversions.
828*****************************************************************************/
829
830const char* CStringFromCFString(CFStringRef string)
831{
832    const char* defaultString = "??????";
833    const char* cstring;
834
835    if (!string)
836        return defaultString;
837
838    cstring = CFStringGetCStringPtr(string, kCFStringEncodingUTF8);
839
840    return (cstring) ? cstring : defaultString;
841
842}
843
844#pragma mark -
845#pragma mark NetBrowserInfo "Object"
846#pragma mark -
847/*****************************************************************************
848* NetBrowserInfoCreate
849* -
850* The method creates a NetBrowserInfo Object and initalizes it.
851*****************************************************************************/
852NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context)
853{
854    NetBrowserInfo* outObj = NULL;
855    DNSServiceRef browserRef = NULL;
856    char* cServiceType = NULL;
857    char* cDomain = NULL;
858    Boolean success = true;
859
860    CFIndex serviceSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(serviceType), kCFStringEncodingUTF8);
861    cServiceType = calloc(serviceSize, 1);
862    success = CFStringGetCString(serviceType, cServiceType, serviceSize, kCFStringEncodingUTF8);
863
864
865    if (domain)
866    {
867        CFIndex domainSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(domain), kCFStringEncodingUTF8);
868        if (domainSize)
869        {
870            cDomain = calloc(domainSize, 1);
871            success = success && CFStringGetCString(domain, cDomain, domainSize, kCFStringEncodingUTF8);
872        }
873    }
874
875    if (!success)
876    {
877        fprintf(stderr, "%s:%s LaunchEvent has badly encoded service type or domain.\n", sPluginIdentifier, __FUNCTION__);
878        free(cServiceType);
879
880        if (cDomain)
881            free(cDomain);
882
883        return NULL;
884    }
885
886    DNSServiceErrorType err = DNSServiceBrowse(&browserRef, 0, 0, cServiceType, cDomain, ServiceBrowserCallback, context);
887
888    if (err != kDNSServiceErr_NoError)
889    {
890        fprintf(stderr, "%s:%s Failed to create browser for %s, %s\n", sPluginIdentifier, __FUNCTION__, cServiceType, cDomain);
891        free(cServiceType);
892
893        if (cDomain)
894            free(cDomain);
895
896        return NULL;
897    }
898
899    DNSServiceSetDispatchQueue(browserRef, dispatch_get_main_queue());
900
901
902    outObj = malloc(sizeof(NetBrowserInfo));
903
904    outObj->refCount = 1;
905    outObj->browserRef = browserRef;
906
907    asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: created new object %p", sPluginIdentifier, __FUNCTION__, outObj);
908
909    free(cServiceType);
910
911    if (cDomain)
912        free(cDomain);
913
914    return outObj;
915}
916
917/*****************************************************************************
918* NetBrowserInfoRetain
919* -
920* The method retains a NetBrowserInfo object.
921*****************************************************************************/
922const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info)
923{
924    (void)allocator;
925    NetBrowserInfo* obj = (NetBrowserInfo*)info;
926
927    if (!obj)
928        return NULL;
929
930    ++obj->refCount;
931    asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Incremented ref count on %p, count %d", sPluginIdentifier, __FUNCTION__, obj->browserRef, (int)obj->refCount);
932
933    return obj;
934}
935
936/*****************************************************************************
937* NetBrowserInfoRelease
938* -
939* The method releases a NetBrowserInfo object.
940*****************************************************************************/
941void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info)
942{
943    (void)allocator;
944    NetBrowserInfo* obj = (NetBrowserInfo*)info;
945
946    if (!obj)
947        return;
948
949    if (obj->refCount == 1)
950    {
951        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: DNSServiceRefDeallocate %p", sPluginIdentifier, __FUNCTION__, obj->browserRef);
952        DNSServiceRefDeallocate(obj->browserRef);
953        free(obj);
954    }
955    else
956    {
957        --obj->refCount;
958        asl_log(NULL, NULL, ASL_LEVEL_INFO, "%s:%s: Decremented ref count on %p, count %d", sPluginIdentifier, __FUNCTION__, obj->browserRef, (int)obj->refCount);
959    }
960
961}
962
963/*****************************************************************************
964* NetBrowserInfoEqual
965* -
966* The method is used to compare two NetBrowserInfo objects for equality.
967*****************************************************************************/
968Boolean NetBrowserInfoEqual(const void *value1, const void *value2)
969{
970    NetBrowserInfo* obj1 = (NetBrowserInfo*)value1;
971    NetBrowserInfo* obj2 = (NetBrowserInfo*)value2;
972
973    if (obj1->browserRef == obj2->browserRef)
974        return true;
975
976    return false;
977}
978
979/*****************************************************************************
980* NetBrowserInfoHash
981* -
982* The method is used to make a hash for the object. We can cheat and use the
983* browser pointer.
984*****************************************************************************/
985CFHashCode  NetBrowserInfoHash(const void *value)
986{
987    return (CFHashCode)((NetBrowserInfo*)value)->browserRef;
988}
989
990
991/*****************************************************************************
992* NetBrowserInfoCopyDescription
993* -
994* Make CF happy.
995*****************************************************************************/
996CFStringRef NetBrowserInfoCopyDescription(const void *value)
997{
998    (void)value;
999    return CFStringCreateWithCString(NULL, "NetBrowserInfo: No useful description", kCFStringEncodingUTF8);
1000}
1001
1002