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