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