1/* 2 File: DNSServiceDiscoveryPref.m 3 4 Abstract: System Preference Pane for Dynamic DNS and Wide-Area DNS Service Discovery 5 6 Copyright: (c) Copyright 2005-2011 Apple Computer, Inc. All rights reserved. 7 8 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. 9 ("Apple") in consideration of your agreement to the following terms, and your 10 use, installation, modification or redistribution of this Apple software 11 constitutes acceptance of these terms. If you do not agree with these terms, 12 please do not use, install, modify or redistribute this Apple software. 13 14 In consideration of your agreement to abide by the following terms, and subject 15 to these terms, Apple grants you a personal, non-exclusive license, under Apple's 16 copyrights in this original Apple software (the "Apple Software"), to use, 17 reproduce, modify and redistribute the Apple Software, with or without 18 modifications, in source and/or binary forms; provided that if you redistribute 19 the Apple Software in its entirety and without modifications, you must retain 20 this notice and the following text and disclaimers in all such redistributions of 21 the Apple Software. Neither the name, trademarks, service marks or logos of 22 Apple Computer, Inc. may be used to endorse or promote products derived from the 23 Apple Software without specific prior written permission from Apple. Except as 24 expressly stated in this notice, no other rights or licenses, express or implied, 25 are granted by Apple herein, including but not limited to any patent rights that 26 may be infringed by your derivative works or by other works in which the Apple 27 Software may be incorporated. 28 29 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 30 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 31 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 33 COMBINATION WITH YOUR PRODUCTS. 34 35 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 36 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 37 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 38 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION 39 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT 40 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN 41 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44#import "DNSServiceDiscoveryPref.h" 45#import "ConfigurationAuthority.h" 46#import "PrivilegedOperations.h" 47#import <unistd.h> 48 49#include "../../Clients/ClientCommon.h" 50 51#ifndef NSINTEGER_DEFINED 52#define NSInteger int 53#endif 54 55@implementation DNSServiceDiscoveryPref 56 57static NSInteger 58MyArrayCompareFunction(id val1, id val2, void *context) 59{ 60 (void)context; // Unused 61 return CFStringCompare((CFStringRef)val1, (CFStringRef)val2, kCFCompareCaseInsensitive); 62} 63 64static NSInteger 65MyDomainArrayCompareFunction(id val1, id val2, void *context) 66{ 67 (void)context; // Unused 68 NSString *domain1 = [val1 objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 69 NSString *domain2 = [val2 objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 70 return CFStringCompare((CFStringRef)domain1, (CFStringRef)domain2, kCFCompareCaseInsensitive); 71} 72 73 74static void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context) 75{ 76 (void)store; // Unused 77 (void)changedKeys; // Unused 78 DNSServiceDiscoveryPref * me = (DNSServiceDiscoveryPref *)context; 79 assert(me != NULL); 80 81 [me setupInitialValues]; 82} 83 84 85static void ServiceDomainEnumReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, 86 DNSServiceErrorType errorCode, const char *replyDomain, void *context, DNSServiceFlags enumType) 87{ 88 (void)sdRef; // Unused 89 (void)interfaceIndex; // Unused 90 (void)errorCode; // Unused 91 if (strcmp(replyDomain, "local.") == 0) return; // local domain is not interesting 92 93 DNSServiceDiscoveryPref * me = (DNSServiceDiscoveryPref *)context; 94 BOOL moreComing = (BOOL)(flags & kDNSServiceFlagsMoreComing); 95 NSMutableArray * domainArray; 96 NSMutableArray * defaultBrowseDomainsArray = nil; 97 NSComboBox * domainComboBox; 98 NSString * domainString; 99 NSString * currentDomain = nil; 100 char decodedDomainString[kDNSServiceMaxDomainName] = "\0"; 101 char nextLabel[256] = "\0"; 102 char * buffer = (char *)replyDomain; 103 104 while (*buffer) { 105 buffer = (char *)GetNextLabel(buffer, nextLabel); 106 strcat(decodedDomainString, nextLabel); 107 strcat(decodedDomainString, "."); 108 } 109 110 // Remove trailing dot from domain name. 111 decodedDomainString[strlen(decodedDomainString)-1] = '\0'; 112 113 domainString = [[[NSString alloc] initWithUTF8String:(const char *)decodedDomainString] autorelease]; 114 115 if (enumType & kDNSServiceFlagsRegistrationDomains) { 116 domainArray = [me registrationDataSource]; 117 domainComboBox = [me regDomainsComboBox]; 118 currentDomain = [me currentRegDomain]; 119 } else { 120 domainArray = [me browseDataSource]; 121 domainComboBox = [me browseDomainsComboBox]; 122 defaultBrowseDomainsArray = [me defaultBrowseDomainsArray]; 123 } 124 125 if (flags & kDNSServiceFlagsAdd) { 126 [domainArray removeObject:domainString]; // How can I check if an object is in the array? 127 [domainArray addObject:domainString]; 128 if ((flags & kDNSServiceFlagsDefault) && (enumType & kDNSServiceFlagsRegistrationDomains)) { 129 [me setDefaultRegDomain:domainString]; 130 if ([[domainComboBox stringValue] length] == 0) [domainComboBox setStringValue:domainString]; 131 } else if ((flags & kDNSServiceFlagsDefault) && !(enumType & kDNSServiceFlagsRegistrationDomains)) { 132 [defaultBrowseDomainsArray removeObject:domainString]; 133 [defaultBrowseDomainsArray addObject:domainString]; 134 } 135 } 136 137 if (moreComing == NO) { 138 [domainArray sortUsingFunction:MyArrayCompareFunction context:nil]; 139 [domainComboBox reloadData]; 140 } 141} 142 143 144static void 145browseDomainReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, 146 DNSServiceErrorType errorCode, const char *replyDomain, void *context) 147{ 148 ServiceDomainEnumReply(sdRef, flags, interfaceIndex, errorCode, replyDomain, context, kDNSServiceFlagsBrowseDomains); 149} 150 151 152static void 153registrationDomainReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, 154 DNSServiceErrorType errorCode, const char *replyDomain, void *context) 155{ 156 ServiceDomainEnumReply(sdRef, flags, interfaceIndex, errorCode, replyDomain, context, kDNSServiceFlagsRegistrationDomains); 157} 158 159 160 161static void 162MyDNSServiceCleanUp(MyDNSServiceState * query) 163{ 164 /* Remove the CFRunLoopSource from the current run loop. */ 165 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes); 166 CFRelease(query->source); 167 168 /* Invalidate the CFSocket. */ 169 CFSocketInvalidate(query->socket); 170 CFRelease(query->socket); 171 172 /* Workaround that gives time to CFSocket's select thread so it can remove the socket from its FD set 173 before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273> */ 174 usleep(1000); 175 176 /* Terminate the connection with the mDNSResponder daemon, which cancels the query. */ 177 DNSServiceRefDeallocate(query->service); 178} 179 180 181 182static void 183MySocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void * data, void * info) 184{ 185 #pragma unused(s) 186 #pragma unused(type) 187 #pragma unused(address) 188 #pragma unused(data) 189 190 DNSServiceErrorType err; 191 192 MyDNSServiceState * query = (MyDNSServiceState *)info; // context passed in to CFSocketCreateWithNative(). 193 assert(query != NULL); 194 195 /* Read a reply from the mDNSResponder. */ 196 err= DNSServiceProcessResult(query->service); 197 if (err != kDNSServiceErr_NoError) { 198 fprintf(stderr, "DNSServiceProcessResult returned %d\n", err); 199 200 /* Terminate the query operation and release the CFRunLoopSource and CFSocket. */ 201 MyDNSServiceCleanUp(query); 202 } 203} 204 205 206 207static void 208MyDNSServiceAddServiceToRunLoop(MyDNSServiceState * query) 209{ 210 CFSocketNativeHandle sock; 211 CFOptionFlags sockFlags; 212 CFSocketContext context = { 0, query, NULL, NULL, NULL }; // Use MyDNSServiceState as context data. 213 214 /* Access the underlying Unix domain socket to communicate with the mDNSResponder daemon. */ 215 sock = DNSServiceRefSockFD(query->service); 216 assert(sock != -1); 217 218 /* Create a CFSocket using the Unix domain socket. */ 219 query->socket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, MySocketReadCallback, &context); 220 assert(query->socket != NULL); 221 222 /* Prevent CFSocketInvalidate from closing DNSServiceRef's socket. */ 223 sockFlags = CFSocketGetSocketFlags(query->socket); 224 CFSocketSetSocketFlags(query->socket, sockFlags & (~kCFSocketCloseOnInvalidate)); 225 226 /* Create a CFRunLoopSource from the CFSocket. */ 227 query->source = CFSocketCreateRunLoopSource(NULL, query->socket, 0); 228 assert(query->source != NULL); 229 230 /* Add the CFRunLoopSource to the current run loop. */ 231 CFRunLoopAddSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes); 232} 233 234 235 236-(void)updateStatusImageView 237{ 238 int value = [self statusForHostName:currentHostName]; 239 if (value == 0) [statusImageView setImage:successImage]; 240 else if (value > 0) [statusImageView setImage:inprogressImage]; 241 else [statusImageView setImage:failureImage]; 242} 243 244 245- (void)watchForPreferenceChanges 246{ 247 SCDynamicStoreContext context = { 0, self, NULL, NULL, NULL }; 248 SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("watchForPreferenceChanges"), NetworkChanged, &context); 249 CFMutableArrayRef keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 250 CFRunLoopSourceRef rls; 251 252 assert(store != NULL); 253 assert(keys != NULL); 254 255 CFArrayAppendValue(keys, SC_DYNDNS_STATE_KEY); 256 CFArrayAppendValue(keys, SC_DYNDNS_SETUP_KEY); 257 258 (void)SCDynamicStoreSetNotificationKeys(store, keys, NULL); 259 260 rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0); 261 assert(rls != NULL); 262 263 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes); 264 265 CFRelease(keys); 266 CFRelease(store); 267} 268 269 270-(int)statusForHostName:(NSString * )domain 271{ 272 SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("statusForHostName"), NULL, NULL); 273 NSString *lowercaseDomain = [domain lowercaseString]; 274 int status = 1; 275 276 assert(store != NULL); 277 278 NSDictionary *dynamicDNS = (NSDictionary *)SCDynamicStoreCopyValue(store, SC_DYNDNS_STATE_KEY); 279 if (dynamicDNS) { 280 NSDictionary *hostNames = [dynamicDNS objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY]; 281 NSDictionary *infoDict = [hostNames objectForKey:lowercaseDomain]; 282 if (infoDict) status = [[infoDict objectForKey:(NSString*)SC_DYNDNS_STATUS_KEY] intValue]; 283 CFRelease(dynamicDNS); 284 } 285 CFRelease(store); 286 287 return status; 288} 289 290 291- (void)startDomainBrowsing 292{ 293 DNSServiceFlags flags; 294 OSStatus err = noErr; 295 296 flags = kDNSServiceFlagsRegistrationDomains; 297 err = DNSServiceEnumerateDomains(®Query.service, flags, 0, registrationDomainReply, (void *)self); 298 if (err == kDNSServiceErr_NoError) MyDNSServiceAddServiceToRunLoop(®Query); 299 300 flags = kDNSServiceFlagsBrowseDomains; 301 err = DNSServiceEnumerateDomains(&browseQuery.service, flags, 0, browseDomainReply, (void *)self); 302 if (err == kDNSServiceErr_NoError) MyDNSServiceAddServiceToRunLoop(&browseQuery); 303} 304 305 306-(void)readPreferences 307{ 308 NSDictionary *origDict; 309 NSArray *regDomainArray; 310 NSArray *hostArray; 311 312 if (currentRegDomain) [currentRegDomain release]; 313 if (currentBrowseDomainsArray) [currentBrowseDomainsArray release]; 314 if (currentHostName) [currentHostName release]; 315 316 SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("com.apple.preference.bonjour"), NULL, NULL); 317 origDict = (NSDictionary *)SCDynamicStoreCopyValue(store, SC_DYNDNS_SETUP_KEY); 318 319 regDomainArray = [origDict objectForKey:(NSString *)SC_DYNDNS_REGDOMAINS_KEY]; 320 if (regDomainArray && [regDomainArray count] > 0) { 321 currentRegDomain = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy]; 322 currentWideAreaState = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue]; 323 } else { 324 currentRegDomain = [[NSString alloc] initWithString:@""]; 325 currentWideAreaState = NO; 326 } 327 328 currentBrowseDomainsArray = [[origDict objectForKey:(NSString *)SC_DYNDNS_BROWSEDOMAINS_KEY] retain]; 329 330 hostArray = [origDict objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY]; 331 if (hostArray && [hostArray count] > 0) { 332 currentHostName = [[[hostArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy]; 333 } else { 334 currentHostName = [[NSString alloc] initWithString:@""]; 335 } 336 337 [origDict release]; 338 CFRelease(store); 339} 340 341 342- (void)tableViewSelectionDidChange:(NSNotification *)notification 343{ 344 [removeBrowseDomainButton setEnabled:[[notification object] numberOfSelectedRows]]; 345} 346 347 348- (void)setBrowseDomainsComboBox 349{ 350 NSString * domain = nil; 351 352 if ([defaultBrowseDomainsArray count] > 0) { 353 NSEnumerator * arrayEnumerator = [defaultBrowseDomainsArray objectEnumerator]; 354 while ((domain = [arrayEnumerator nextObject]) != NULL) { 355 if ([self domainAlreadyInList:domain] == NO) break; 356 } 357 } 358 if (domain) [browseDomainsComboBox setStringValue:domain]; 359 else [browseDomainsComboBox setStringValue:@""]; 360} 361 362 363- (IBAction)addBrowseDomainClicked:(id)sender 364{ 365 [self setBrowseDomainsComboBox]; 366 367 [NSApp beginSheet:addBrowseDomainWindow modalForWindow:mainWindow modalDelegate:self 368 didEndSelector:@selector(addBrowseDomainSheetDidEnd:returnCode:contextInfo:) contextInfo:sender]; 369 370 [browseDomainList deselectAll:sender]; 371 [self updateApplyButtonState]; 372} 373 374 375- (IBAction)removeBrowseDomainClicked:(id)sender 376{ 377 (void)sender; // Unused 378 int selectedBrowseDomain = [browseDomainList selectedRow]; 379 [browseDomainsArray removeObjectAtIndex:selectedBrowseDomain]; 380 [browseDomainList reloadData]; 381 [self updateApplyButtonState]; 382} 383 384 385- (IBAction)enableBrowseDomainClicked:(id)sender 386{ 387 NSTableView *tableView = sender; 388 NSMutableDictionary *browseDomainDict; 389 int value; 390 391 browseDomainDict = [[browseDomainsArray objectAtIndex:[tableView clickedRow]] mutableCopy]; 392 value = [[browseDomainDict objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue]; 393 [browseDomainDict setObject:[[[NSNumber alloc] initWithInt:(!value)] autorelease] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY]; 394 [browseDomainsArray replaceObjectAtIndex:[tableView clickedRow] withObject:browseDomainDict]; 395 [tableView reloadData]; 396 [self updateApplyButtonState]; 397} 398 399 400 401- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView 402{ 403 (void)tableView; // Unused 404 int numberOfRows = 0; 405 406 if (browseDomainsArray) { 407 numberOfRows = [browseDomainsArray count]; 408 } 409 return numberOfRows; 410} 411 412 413- (void)tabView:(NSTabView *)xtabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem 414{ 415 (void)xtabView; // Unused 416 (void)tabViewItem; // Unused 417 [browseDomainList deselectAll:self]; 418 [mainWindow makeFirstResponder:nil]; 419} 420 421 422- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row 423{ 424 (void)tableView; // Unused 425 NSDictionary *browseDomainDict; 426 id value = nil; 427 428 if (browseDomainsArray) { 429 browseDomainDict = [browseDomainsArray objectAtIndex:row]; 430 if (browseDomainDict) { 431 if ([[tableColumn identifier] isEqualTo:(NSString *)SC_DYNDNS_ENABLED_KEY]) { 432 value = [browseDomainDict objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY]; 433 } else if ([[tableColumn identifier] isEqualTo:(NSString *)SC_DYNDNS_DOMAIN_KEY]) { 434 value = [browseDomainDict objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 435 } 436 } 437 } 438 return value; 439} 440 441 442- (void)setupInitialValues 443{ 444 [self readPreferences]; 445 446 if (currentHostName) { 447 [hostName setStringValue:currentHostName]; 448 [self updateStatusImageView]; 449 } 450 451 if (browseDomainsArray) { 452 [browseDomainsArray release]; 453 browseDomainsArray = nil; 454 } 455 456 if (currentBrowseDomainsArray) { 457 browseDomainsArray = [currentBrowseDomainsArray mutableCopy]; 458 if (browseDomainsArray) { 459 [browseDomainsArray sortUsingFunction:MyDomainArrayCompareFunction context:nil]; 460 if ([browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO) { 461 OSStatus err = WriteBrowseDomain((CFDataRef)[self dataForDomainArray:browseDomainsArray]); 462 if (err != noErr) NSLog(@"WriteBrowseDomain returned %d\n", (int32_t)err); 463 [currentBrowseDomainsArray release]; 464 currentBrowseDomainsArray = [browseDomainsArray copy]; 465 } 466 } 467 } else { 468 browseDomainsArray = nil; 469 } 470 [browseDomainList reloadData]; 471 472 if (currentRegDomain && ([currentRegDomain length] > 0)) { 473 [regDomainsComboBox setStringValue:currentRegDomain]; 474 [registrationDataSource removeObject:currentRegDomain]; 475 [registrationDataSource addObject:currentRegDomain]; 476 [registrationDataSource sortUsingFunction:MyArrayCompareFunction context:nil]; 477 [regDomainsComboBox reloadData]; 478 } 479 480 if (currentWideAreaState) { 481 [self toggleWideAreaBonjour:YES]; 482 } else { 483 [self toggleWideAreaBonjour:NO]; 484 } 485 486 if (hostNameSharedSecretValue) { 487 [hostNameSharedSecretValue release]; 488 hostNameSharedSecretValue = nil; 489 } 490 491 if (regSharedSecretValue) { 492 [regSharedSecretValue release]; 493 regSharedSecretValue = nil; 494 } 495 496 [self updateApplyButtonState]; 497 [mainWindow makeFirstResponder:nil]; 498 [browseDomainList deselectAll:self]; 499 [removeBrowseDomainButton setEnabled:NO]; 500} 501 502 503 504- (void)awakeFromNib 505{ 506 OSStatus err; 507 508 prefsNeedUpdating = NO; 509 toolInstalled = NO; 510 browseDomainListEnabled = NO; 511 defaultRegDomain = nil; 512 currentRegDomain = nil; 513 currentBrowseDomainsArray = nil; 514 currentHostName = nil; 515 hostNameSharedSecretValue = nil; 516 regSharedSecretValue = nil; 517 browseDomainsArray = nil; 518 justStartedEditing = YES; 519 currentWideAreaState = NO; 520 NSString *successPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"success" ofType:@"tiff"]; 521 NSString *inprogressPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"inprogress" ofType:@"tiff"]; 522 NSString *failurePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"failure" ofType:@"tiff"]; 523 524 registrationDataSource = [[NSMutableArray alloc] init]; 525 browseDataSource = [[NSMutableArray alloc] init]; 526 defaultBrowseDomainsArray = [[NSMutableArray alloc] init]; 527 successImage = [[NSImage alloc] initWithContentsOfFile:successPath]; 528 inprogressImage = [[NSImage alloc] initWithContentsOfFile:inprogressPath]; 529 failureImage = [[NSImage alloc] initWithContentsOfFile:failurePath]; 530 531 [tabView selectFirstTabViewItem:self]; 532 [self setupInitialValues]; 533 [self startDomainBrowsing]; 534 [self watchForPreferenceChanges]; 535 536 InitConfigAuthority(); 537 err = EnsureToolInstalled(); 538 if (err == noErr) toolInstalled = YES; 539 else { long int tmp = err; fprintf(stderr, "EnsureToolInstalled returned %ld\n", tmp); } 540 541} 542 543 544- (IBAction)closeMyCustomSheet:(id)sender 545{ 546 BOOL result = [sender isEqualTo:browseOKButton] || [sender isEqualTo:secretOKButton]; 547 548 if (result) [NSApp endSheet:[sender window] returnCode:NSOKButton]; 549 else [NSApp endSheet:[sender window] returnCode:NSCancelButton]; 550} 551 552 553- (void)sharedSecretSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 554{ 555 NSButton * button = (NSButton *)contextInfo; 556 [sheet orderOut:self]; 557 [self enableControls]; 558 559 if (returnCode == NSOKButton) { 560 if ([button isEqualTo:hostNameSharedSecretButton]) { 561 hostNameSharedSecretName = [[NSString alloc] initWithString:[sharedSecretName stringValue]]; 562 hostNameSharedSecretValue = [[NSString alloc] initWithString:[sharedSecretValue stringValue]]; 563 } else { 564 regSharedSecretName = [[NSString alloc] initWithString:[sharedSecretName stringValue]]; 565 regSharedSecretValue = [[NSString alloc] initWithString:[sharedSecretValue stringValue]]; 566 } 567 [self updateApplyButtonState]; 568 } 569 [sharedSecretValue setStringValue:@""]; 570} 571 572 573- (BOOL)domainAlreadyInList:(NSString *)domainString 574{ 575 if (browseDomainsArray) { 576 NSDictionary *domainDict; 577 NSString *domainName; 578 NSEnumerator *arrayEnumerator = [browseDomainsArray objectEnumerator]; 579 while ((domainDict = [arrayEnumerator nextObject]) != NULL) { 580 domainName = [domainDict objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 581 if ([domainString caseInsensitiveCompare:domainName] == NSOrderedSame) return YES; 582 } 583 } 584 return NO; 585} 586 587 588- (NSString *)trimCharactersFromDomain:(NSString *)domain 589{ 590 NSMutableCharacterSet * trimSet = [[[NSCharacterSet whitespaceCharacterSet] mutableCopy] autorelease]; 591 [trimSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; 592 return [domain stringByTrimmingCharactersInSet:trimSet]; 593} 594 595 596- (void)addBrowseDomainSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 597{ 598 (void)contextInfo; // Unused 599 [sheet orderOut:self]; 600 [self enableControls]; 601 602 if (returnCode == NSOKButton) { 603 NSString * newBrowseDomainString = [self trimCharactersFromDomain:[browseDomainsComboBox stringValue]]; 604 NSMutableDictionary *newBrowseDomainDict; 605 606 if (browseDomainsArray == nil) browseDomainsArray = [[NSMutableArray alloc] initWithCapacity:0]; 607 if ([self domainAlreadyInList:newBrowseDomainString] == NO) { 608 newBrowseDomainDict = [[[NSMutableDictionary alloc] initWithCapacity:2] autorelease]; 609 610 [newBrowseDomainDict setObject:newBrowseDomainString forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 611 [newBrowseDomainDict setObject:[[[NSNumber alloc] initWithBool:YES] autorelease] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY]; 612 613 [browseDomainsArray addObject:newBrowseDomainDict]; 614 [browseDomainsArray sortUsingFunction:MyDomainArrayCompareFunction context:nil]; 615 [browseDomainList reloadData]; 616 [self updateApplyButtonState]; 617 } 618 } 619} 620 621 622-(void)validateTextFields 623{ 624 [hostName validateEditing]; 625 [browseDomainsComboBox validateEditing]; 626 [regDomainsComboBox validateEditing]; 627} 628 629 630- (IBAction)changeButtonPressed:(id)sender 631{ 632 NSString * keyName; 633 634 [self disableControls]; 635 [self validateTextFields]; 636 [mainWindow makeFirstResponder:nil]; 637 [browseDomainList deselectAll:sender]; 638 639 if ([sender isEqualTo:hostNameSharedSecretButton]) { 640 if (hostNameSharedSecretValue) { 641 [sharedSecretValue setStringValue:hostNameSharedSecretValue]; 642 } else if ((keyName = [self sharedSecretKeyName:[hostName stringValue]]) != NULL) { 643 [sharedSecretName setStringValue:keyName]; 644 [sharedSecretValue setStringValue:@"****************"]; 645 } else { 646 [sharedSecretName setStringValue:[hostName stringValue]]; 647 [sharedSecretValue setStringValue:@""]; 648 } 649 650 } else { 651 if (regSharedSecretValue) { 652 [sharedSecretValue setStringValue:regSharedSecretValue]; 653 } else if ((keyName = [self sharedSecretKeyName:[regDomainsComboBox stringValue]]) != NULL) { 654 [sharedSecretName setStringValue:keyName]; 655 [sharedSecretValue setStringValue:@"****************"]; 656 } else { 657 [sharedSecretName setStringValue:[regDomainsComboBox stringValue]]; 658 [sharedSecretValue setStringValue:@""]; 659 } 660 } 661 662 [sharedSecretWindow resignFirstResponder]; 663 664 if ([[sharedSecretName stringValue] length] > 0) [sharedSecretWindow makeFirstResponder:sharedSecretValue]; 665 else [sharedSecretWindow makeFirstResponder:sharedSecretName]; 666 667 [NSApp beginSheet:sharedSecretWindow modalForWindow:mainWindow modalDelegate:self 668 didEndSelector:@selector(sharedSecretSheetDidEnd:returnCode:contextInfo:) contextInfo:sender]; 669} 670 671 672- (IBAction)wideAreaCheckBoxChanged:(id)sender 673{ 674 [self toggleWideAreaBonjour:[sender state]]; 675 [self updateApplyButtonState]; 676 [mainWindow makeFirstResponder:nil]; 677} 678 679 680- (void)updateApplyButtonState 681{ 682 NSString *hostNameString = [hostName stringValue]; 683 NSString *regDomainString = [regDomainsComboBox stringValue]; 684 685 NSComparisonResult hostNameResult = [hostNameString compare:currentHostName]; 686 NSComparisonResult regDomainResult = [regDomainString compare:currentRegDomain]; 687 688 if ((currentHostName && (hostNameResult != NSOrderedSame)) || 689 (currentRegDomain && (regDomainResult != NSOrderedSame) && ([wideAreaCheckBox state])) || 690 (currentHostName == nil && ([hostNameString length]) > 0) || 691 (currentRegDomain == nil && ([regDomainString length]) > 0) || 692 (currentWideAreaState != [wideAreaCheckBox state]) || 693 (hostNameSharedSecretValue != nil) || 694 (regSharedSecretValue != nil) || 695 (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO)) 696 { 697 [self enableApplyButton]; 698 } else { 699 [self disableApplyButton]; 700 } 701} 702 703 704 705- (void)controlTextDidChange:(NSNotification *)notification 706{ 707 (void)notification; // Unused 708 [self updateApplyButtonState]; 709} 710 711 712 713- (IBAction)comboAction:(id)sender 714{ 715 (void)sender; // Unused 716 [self updateApplyButtonState]; 717} 718 719 720- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(int)ind 721{ 722 NSString *domain = nil; 723 if ([aComboBox isEqualTo:browseDomainsComboBox]) domain = [browseDataSource objectAtIndex:ind]; 724 else if ([aComboBox isEqualTo:regDomainsComboBox]) domain = [registrationDataSource objectAtIndex:ind]; 725 return domain; 726} 727 728 729 730- (int)numberOfItemsInComboBox:(NSComboBox *)aComboBox 731{ 732 int count = 0; 733 if ([aComboBox isEqualTo:browseDomainsComboBox]) count = [browseDataSource count]; 734 else if ([aComboBox isEqualTo:regDomainsComboBox]) count = [registrationDataSource count]; 735 return count; 736} 737 738 739- (NSMutableArray *)browseDataSource 740{ 741 return browseDataSource; 742} 743 744 745- (NSMutableArray *)registrationDataSource 746{ 747 return registrationDataSource; 748} 749 750 751- (NSComboBox *)browseDomainsComboBox 752{ 753 return browseDomainsComboBox; 754} 755 756 757- (NSComboBox *)regDomainsComboBox 758{ 759 return regDomainsComboBox; 760} 761 762 763- (NSString *)currentRegDomain 764{ 765 return currentRegDomain; 766} 767 768 769- (NSMutableArray *)defaultBrowseDomainsArray 770{ 771 return defaultBrowseDomainsArray; 772} 773 774 775- (NSArray *)currentBrowseDomainsArray 776{ 777 return currentBrowseDomainsArray; 778} 779 780 781- (NSString *)currentHostName 782{ 783 return currentHostName; 784} 785 786 787- (NSString *)defaultRegDomain 788{ 789 return defaultRegDomain; 790} 791 792 793- (void)setDefaultRegDomain:(NSString *)domain 794{ 795 [defaultRegDomain release]; 796 defaultRegDomain = domain; 797 [defaultRegDomain retain]; 798} 799 800 801- (void)didSelect 802{ 803 [super didSelect]; 804 mainWindow = [[self mainView] window]; 805} 806 807 808- (void)mainViewDidLoad 809{ 810 [comboAuthButton setString:"system.preferences"]; 811 [comboAuthButton setDelegate:self]; 812 [comboAuthButton updateStatus:nil]; 813 [comboAuthButton setAutoupdate:YES]; 814} 815 816 817 818- (IBAction)applyClicked:(id)sender 819{ 820 (void)sender; // Unused 821 [self applyCurrentState]; 822} 823 824 825- (void)applyCurrentState 826{ 827 [self validateTextFields]; 828 829 if (toolInstalled == YES) { 830 [self savePreferences]; 831 [self disableApplyButton]; 832 [mainWindow makeFirstResponder:nil]; 833 } 834} 835 836 837- (void)enableApplyButton 838{ 839 [applyButton setEnabled:YES]; 840 [revertButton setEnabled:YES]; 841 prefsNeedUpdating = YES; 842} 843 844 845- (void)disableApplyButton 846{ 847 [applyButton setEnabled:NO]; 848 [revertButton setEnabled:NO]; 849 prefsNeedUpdating = NO; 850} 851 852 853- (void)toggleWideAreaBonjour:(BOOL)state 854{ 855 [wideAreaCheckBox setState:state]; 856 [regDomainsComboBox setEnabled:state]; 857 [registrationSharedSecretButton setEnabled:state]; 858} 859 860 861- (IBAction)revertClicked:(id)sender 862{ 863 [self restorePreferences]; 864 [browseDomainList deselectAll:sender]; 865 [mainWindow makeFirstResponder:nil]; 866} 867 868 869- (void)restorePreferences 870{ 871 [self setupInitialValues]; 872} 873 874 875- (void)savePanelWillClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 876{ 877 (void)sheet; // Unused 878 DNSServiceDiscoveryPref * me = (DNSServiceDiscoveryPref *)contextInfo; 879 880 if (returnCode == NSAlertDefaultReturn) { 881 [me applyCurrentState]; 882 } else if (returnCode == NSAlertAlternateReturn ) { 883 [me restorePreferences]; 884 } 885 886 [me enableControls]; 887 [me replyToShouldUnselect:(returnCode != NSAlertOtherReturn)]; 888} 889 890 891-(SecKeychainItemRef)copyKeychainItemforDomain:(NSString *)domain 892{ 893 const char * serviceName = [domain UTF8String]; 894 UInt32 type = 'ddns'; 895 UInt32 typeLength = sizeof(type); 896 897 SecKeychainAttribute attrs[] = { { kSecServiceItemAttr, strlen(serviceName), (char *)serviceName }, 898 { kSecTypeItemAttr, typeLength, (UInt32 *)&type } }; 899 900 SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs }; 901 SecKeychainSearchRef searchRef; 902 SecKeychainItemRef itemRef = NULL; 903 OSStatus err; 904 905 err = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef); 906 if (err == noErr) { 907 err = SecKeychainSearchCopyNext(searchRef, &itemRef); 908 if (err != noErr) itemRef = NULL; 909 } 910 return itemRef; 911} 912 913 914-(NSString *)sharedSecretKeyName:(NSString * )domain 915{ 916 SecKeychainItemRef itemRef = NULL; 917 NSString *keyName = nil; 918 OSStatus err; 919 920 err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); 921 assert(err == noErr); 922 923 itemRef = [self copyKeychainItemforDomain:[domain lowercaseString]]; 924 if (itemRef) { 925 UInt32 tags[1]; 926 SecKeychainAttributeInfo attrInfo; 927 SecKeychainAttributeList *attrList = NULL; 928 SecKeychainAttribute attribute; 929 unsigned int i; 930 931 tags[0] = kSecAccountItemAttr; 932 attrInfo.count = 1; 933 attrInfo.tag = tags; 934 attrInfo.format = NULL; 935 936 err = SecKeychainItemCopyAttributesAndData(itemRef, &attrInfo, NULL, &attrList, NULL, NULL); 937 if (err == noErr) { 938 for (i = 0; i < attrList->count; i++) { 939 attribute = attrList->attr[i]; 940 if (attribute.tag == kSecAccountItemAttr) { 941 keyName = [[NSString alloc] initWithBytes:attribute.data length:attribute.length encoding:NSUTF8StringEncoding]; 942 break; 943 } 944 } 945 if (attrList) (void)SecKeychainItemFreeAttributesAndData(attrList, NULL); 946 } 947 CFRelease(itemRef); 948 } 949 return keyName; 950} 951 952 953-(NSString *)domainForHostName:(NSString *)hostNameString 954{ 955 NSString * domainName = nil; 956 char text[64]; 957 char * ptr = NULL; 958 959 ptr = (char *)[hostNameString UTF8String]; 960 if (ptr) { 961 ptr = (char *)GetNextLabel(ptr, text); 962 domainName = [[NSString alloc] initWithUTF8String:(const char *)ptr]; 963 } 964 return ([domainName autorelease]); 965} 966 967 968- (NSData *)dataForDomain:(NSString *)domainName isEnabled:(BOOL)enabled 969{ 970 NSMutableArray *domainsArray; 971 NSMutableDictionary *domainDict = nil; 972 973 if (domainName && [domainName length] > 0) { 974 domainDict= [[[NSMutableDictionary alloc] initWithCapacity:2] autorelease]; 975 [domainDict setObject:domainName forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 976 [domainDict setObject:[[[NSNumber alloc] initWithBool:enabled] autorelease] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY]; 977 } 978 domainsArray = [[[NSMutableArray alloc] initWithCapacity:1] autorelease]; 979 if (domainDict) [domainsArray addObject:domainDict]; 980 return [NSArchiver archivedDataWithRootObject:domainsArray]; 981} 982 983 984- (NSData *)dataForDomainArray:(NSArray *)domainArray 985{ 986 return [NSArchiver archivedDataWithRootObject:domainArray]; 987} 988 989 990- (NSData *)dataForSharedSecret:(NSString *)secret domain:(NSString *)domainName key:(NSString *)keyName 991{ 992 NSMutableDictionary *sharedSecretDict = [[[NSMutableDictionary alloc] initWithCapacity:3] autorelease]; 993 [sharedSecretDict setObject:secret forKey:(NSString *)SC_DYNDNS_SECRET_KEY]; 994 [sharedSecretDict setObject:[domainName lowercaseString] forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY]; 995 [sharedSecretDict setObject:keyName forKey:(NSString *)SC_DYNDNS_KEYNAME_KEY]; 996 return [NSArchiver archivedDataWithRootObject:sharedSecretDict]; 997} 998 999 1000-(void)savePreferences 1001{ 1002 NSString *hostNameString = [hostName stringValue]; 1003 NSString *browseDomainString = [browseDomainsComboBox stringValue]; 1004 NSString *regDomainString = [regDomainsComboBox stringValue]; 1005 NSString *tempHostNameSharedSecretName = hostNameSharedSecretName; 1006 NSString *tempRegSharedSecretName = regSharedSecretName; 1007 NSData *browseDomainData = nil; 1008 BOOL regSecretWasSet = NO; 1009 BOOL hostSecretWasSet = NO; 1010 OSStatus err = noErr; 1011 1012 hostNameString = [self trimCharactersFromDomain:hostNameString]; 1013 browseDomainString = [self trimCharactersFromDomain:browseDomainString]; 1014 regDomainString = [self trimCharactersFromDomain:regDomainString]; 1015 tempHostNameSharedSecretName = [self trimCharactersFromDomain:tempHostNameSharedSecretName]; 1016 tempRegSharedSecretName = [self trimCharactersFromDomain:tempRegSharedSecretName]; 1017 1018 [hostName setStringValue:hostNameString]; 1019 [regDomainsComboBox setStringValue:regDomainString]; 1020 1021 // Convert Shared Secret account names to lowercase. 1022 tempHostNameSharedSecretName = [tempHostNameSharedSecretName lowercaseString]; 1023 tempRegSharedSecretName = [tempRegSharedSecretName lowercaseString]; 1024 1025 // Save hostname shared secret. 1026 if ([hostNameSharedSecretName length] > 0 && ([hostNameSharedSecretValue length] > 0)) { 1027 SetKeyForDomain((CFDataRef)[self dataForSharedSecret:hostNameSharedSecretValue domain:hostNameString key:tempHostNameSharedSecretName]); 1028 [hostNameSharedSecretValue release]; 1029 hostNameSharedSecretValue = nil; 1030 hostSecretWasSet = YES; 1031 } 1032 1033 // Save registration domain shared secret. 1034 if (([regSharedSecretName length] > 0) && ([regSharedSecretValue length] > 0)) { 1035 SetKeyForDomain((CFDataRef)[self dataForSharedSecret:regSharedSecretValue domain:regDomainString key:tempRegSharedSecretName]); 1036 [regSharedSecretValue release]; 1037 regSharedSecretValue = nil; 1038 regSecretWasSet = YES; 1039 } 1040 1041 // Save hostname. 1042 if ((currentHostName == NULL) || [currentHostName compare:hostNameString] != NSOrderedSame) { 1043 err = WriteHostname((CFDataRef)[self dataForDomain:hostNameString isEnabled:YES]); 1044 if (err != noErr) NSLog(@"WriteHostname returned %d\n", (int32_t)err); 1045 currentHostName = [hostNameString copy]; 1046 } else if (hostSecretWasSet) { 1047 WriteHostname((CFDataRef)[self dataForDomain:@"" isEnabled:NO]); 1048 usleep(200000); // Temporary hack 1049 if ([currentHostName length] > 0) WriteHostname((CFDataRef)[self dataForDomain:(NSString *)currentHostName isEnabled:YES]); 1050 } 1051 1052 // Save browse domain. 1053 if (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO) { 1054 browseDomainData = [self dataForDomainArray:browseDomainsArray]; 1055 err = WriteBrowseDomain((CFDataRef)browseDomainData); 1056 if (err != noErr) NSLog(@"WriteBrowseDomain returned %d\n", (int32_t)err); 1057 currentBrowseDomainsArray = [browseDomainsArray copy]; 1058 } 1059 1060 // Save registration domain. 1061 if ((currentRegDomain == NULL) || ([currentRegDomain compare:regDomainString] != NSOrderedSame) || (currentWideAreaState != [wideAreaCheckBox state])) { 1062 1063 err = WriteRegistrationDomain((CFDataRef)[self dataForDomain:regDomainString isEnabled:[wideAreaCheckBox state]]); 1064 if (err != noErr) NSLog(@"WriteRegistrationDomain returned %d\n", (int32_t)err); 1065 1066 if (currentRegDomain) CFRelease(currentRegDomain); 1067 currentRegDomain = [regDomainString copy]; 1068 1069 if ([currentRegDomain length] > 0) { 1070 currentWideAreaState = [wideAreaCheckBox state]; 1071 [registrationDataSource removeObject:regDomainString]; 1072 [registrationDataSource addObject:currentRegDomain]; 1073 [registrationDataSource sortUsingFunction:MyArrayCompareFunction context:nil]; 1074 [regDomainsComboBox reloadData]; 1075 } else { 1076 currentWideAreaState = NO; 1077 [self toggleWideAreaBonjour:NO]; 1078 if (defaultRegDomain != nil) [regDomainsComboBox setStringValue:defaultRegDomain]; 1079 } 1080 } else if (regSecretWasSet) { 1081 WriteRegistrationDomain((CFDataRef)[self dataForDomain:@"" isEnabled:NO]); 1082 usleep(200000); // Temporary hack 1083 if ([currentRegDomain length] > 0) WriteRegistrationDomain((CFDataRef)[self dataForDomain:currentRegDomain isEnabled:currentWideAreaState]); 1084 } 1085} 1086 1087 1088- (NSPreferencePaneUnselectReply)shouldUnselect 1089{ 1090#if 1 1091 if (prefsNeedUpdating == YES) { 1092 1093 [self disableControls]; 1094 1095 NSBeginAlertSheet( 1096 @"Apply Configuration Changes?", 1097 @"Apply", 1098 @"Don't Apply", 1099 @"Cancel", 1100 mainWindow, 1101 self, 1102 @selector( savePanelWillClose:returnCode:contextInfo: ), 1103 NULL, 1104 (void *) self, // sender, 1105 @"" ); 1106 return NSUnselectLater; 1107 } 1108#endif 1109 1110 return NSUnselectNow; 1111} 1112 1113 1114-(void)disableControls 1115{ 1116 [hostName setEnabled:NO]; 1117 [hostNameSharedSecretButton setEnabled:NO]; 1118 [browseDomainsComboBox setEnabled:NO]; 1119 [applyButton setEnabled:NO]; 1120 [revertButton setEnabled:NO]; 1121 [wideAreaCheckBox setEnabled:NO]; 1122 [regDomainsComboBox setEnabled:NO]; 1123 [registrationSharedSecretButton setEnabled:NO]; 1124 [statusImageView setEnabled:NO]; 1125 1126 browseDomainListEnabled = NO; 1127 [browseDomainList deselectAll:self]; 1128 [browseDomainList setEnabled:NO]; 1129 1130 [addBrowseDomainButton setEnabled:NO]; 1131 [removeBrowseDomainButton setEnabled:NO]; 1132} 1133 1134 1135- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row 1136{ 1137 (void)row; // Unused 1138 (void)tableView; // Unused 1139 return browseDomainListEnabled; 1140} 1141 1142 1143-(void)enableControls 1144{ 1145 [hostName setEnabled:YES]; 1146 [hostNameSharedSecretButton setEnabled:YES]; 1147 [browseDomainsComboBox setEnabled:YES]; 1148 [wideAreaCheckBox setEnabled:YES]; 1149 [registrationSharedSecretButton setEnabled:YES]; 1150 [self toggleWideAreaBonjour:[wideAreaCheckBox state]]; 1151 [statusImageView setEnabled:YES]; 1152 [addBrowseDomainButton setEnabled:YES]; 1153 1154 [browseDomainList setEnabled:YES]; 1155 [browseDomainList deselectAll:self]; 1156 browseDomainListEnabled = YES; 1157 1158 [removeBrowseDomainButton setEnabled:[browseDomainList numberOfSelectedRows]]; 1159 [applyButton setEnabled:prefsNeedUpdating]; 1160 [revertButton setEnabled:prefsNeedUpdating]; 1161} 1162 1163 1164- (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view 1165{ 1166 (void)view; // Unused 1167 [self enableControls]; 1168} 1169 1170 1171- (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view 1172{ 1173 (void)view; // Unused 1174 [self disableControls]; 1175} 1176 1177@end 1178 1179 1180// Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion 1181// e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4" 1182// To expand "version" to its value before making the string, use STRINGIFY(version) instead 1183#define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) #s 1184#define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) 1185 1186// NOT static -- otherwise the compiler may optimize it out 1187// The "@(#) " pattern is a special prefix the "what" command looks for 1188const char VersionString_SCCS[] = "@(#) Bonjour Preference Pane " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; 1189 1190#if _BUILDING_XCODE_PROJECT_ 1191// If the process crashes, then this string will be magically included in the automatically-generated crash log 1192const char *__crashreporter_info__ = VersionString_SCCS + 5; 1193asm(".desc ___crashreporter_info__, 0x10"); 1194#endif 1195