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