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