ba5f64bfd638e0a13aa4920be46592df612cdc7a
[platform/upstream/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 Inc. All rights reserved.
7
8     Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 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 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 NSInteger
58 MyArrayCompareFunction(id val1, id val2, void *context)
59 {
60         (void)context; // Unused
61     return CFStringCompare((CFStringRef)val1, (CFStringRef)val2, kCFCompareCaseInsensitive);
62 }
63
64 static NSInteger
65 MyDomainArrayCompareFunction(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
74 static 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
85 static 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     char decodedDomainString[kDNSServiceMaxDomainName] = "\0";
100     char nextLabel[256] = "\0";
101     char * buffer = (char *)replyDomain;
102     
103         while (*buffer) {
104         buffer = (char *)GetNextLabel(buffer, nextLabel);
105         strcat(decodedDomainString, nextLabel);
106         strcat(decodedDomainString, ".");
107     }
108     
109     // Remove trailing dot from domain name.
110     decodedDomainString[strlen(decodedDomainString)-1] = '\0';
111     
112     domainString = [[[NSString alloc] initWithUTF8String:(const char *)decodedDomainString] autorelease];
113
114     if (enumType & kDNSServiceFlagsRegistrationDomains) {
115         domainArray    = [me registrationDataSource];
116         domainComboBox = [me regDomainsComboBox];
117     } else { 
118         domainArray    = [me browseDataSource];
119         domainComboBox = [me browseDomainsComboBox];
120         defaultBrowseDomainsArray = [me defaultBrowseDomainsArray];
121     }
122     
123         if (flags & kDNSServiceFlagsAdd) {
124                 [domainArray removeObject:domainString];  // How can I check if an object is in the array?
125                 [domainArray addObject:domainString];
126         if ((flags & kDNSServiceFlagsDefault) && (enumType & kDNSServiceFlagsRegistrationDomains)) {
127                         [me setDefaultRegDomain:domainString];
128                         if ([[domainComboBox stringValue] length] == 0) [domainComboBox setStringValue:domainString];
129                 } else if ((flags & kDNSServiceFlagsDefault) && !(enumType & kDNSServiceFlagsRegistrationDomains)) {
130                         [defaultBrowseDomainsArray removeObject:domainString];
131                         [defaultBrowseDomainsArray addObject:domainString];
132                 }
133         }
134     
135     if (moreComing == NO) {
136         [domainArray sortUsingFunction:MyArrayCompareFunction context:nil];
137         [domainComboBox reloadData];
138     }    
139 }
140
141
142 static void
143 browseDomainReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
144     DNSServiceErrorType errorCode, const char *replyDomain, void *context)
145 {
146     ServiceDomainEnumReply(sdRef, flags, interfaceIndex, errorCode, replyDomain, context, kDNSServiceFlagsBrowseDomains);
147 }
148
149
150 static void
151 registrationDomainReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
152     DNSServiceErrorType errorCode, const char *replyDomain, void *context)
153 {
154     ServiceDomainEnumReply(sdRef, flags, interfaceIndex, errorCode, replyDomain, context, kDNSServiceFlagsRegistrationDomains);
155 }
156
157
158
159 static void
160 MyDNSServiceCleanUp(MyDNSServiceState * query)
161 {
162     /* Remove the CFRunLoopSource from the current run loop. */
163     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes);
164     CFRelease(query->source);
165     
166     /* Invalidate the CFSocket. */
167     CFSocketInvalidate(query->socket);
168     CFRelease(query->socket);
169     
170     /* Workaround that gives time to CFSocket's select thread so it can remove the socket from its FD set
171     before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273> */
172     usleep(1000);
173     
174     /* Terminate the connection with the mDNSResponder daemon, which cancels the query. */
175     DNSServiceRefDeallocate(query->service);
176 }
177
178
179
180 static void
181 MySocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void * data, void * info)
182 {
183     #pragma unused(s)
184     #pragma unused(type)
185     #pragma unused(address)
186     #pragma unused(data)
187     
188     DNSServiceErrorType err;
189  
190     MyDNSServiceState * query = (MyDNSServiceState *)info;  // context passed in to CFSocketCreateWithNative().
191     assert(query != NULL);
192     
193     /* Read a reply from the mDNSResponder.  */
194     err= DNSServiceProcessResult(query->service);
195     if (err != kDNSServiceErr_NoError) {
196         fprintf(stderr, "DNSServiceProcessResult returned %d\n", err);
197         
198         /* Terminate the query operation and release the CFRunLoopSource and CFSocket. */
199         MyDNSServiceCleanUp(query);
200     }
201 }
202
203
204
205 static void
206 MyDNSServiceAddServiceToRunLoop(MyDNSServiceState * query)
207 {
208     CFSocketNativeHandle sock;
209     CFOptionFlags        sockFlags;
210     CFSocketContext      context = { 0, query, NULL, NULL, NULL };  // Use MyDNSServiceState as context data.
211     
212     /* Access the underlying Unix domain socket to communicate with the mDNSResponder daemon. */
213     sock = DNSServiceRefSockFD(query->service);
214     assert(sock != -1);
215     
216     /* Create a CFSocket using the Unix domain socket. */
217     query->socket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, MySocketReadCallback, &context);
218     assert(query->socket != NULL);
219     
220     /* Prevent CFSocketInvalidate from closing DNSServiceRef's socket. */
221     sockFlags = CFSocketGetSocketFlags(query->socket);
222     CFSocketSetSocketFlags(query->socket, sockFlags & (~kCFSocketCloseOnInvalidate));
223     
224     /* Create a CFRunLoopSource from the CFSocket. */
225     query->source = CFSocketCreateRunLoopSource(NULL, query->socket, 0);
226     assert(query->source != NULL);
227
228     /* Add the CFRunLoopSource to the current run loop. */
229     CFRunLoopAddSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes);
230 }
231
232
233
234 -(void)updateStatusImageView
235 {
236     int value = [self statusForHostName:currentHostName];
237     if      (value == 0) [statusImageView setImage:successImage];
238     else if (value >  0) [statusImageView setImage:inprogressImage];
239     else                 [statusImageView setImage:failureImage];
240 }
241
242
243 - (void)watchForPreferenceChanges
244 {
245         SCDynamicStoreContext context = { 0, self, NULL, NULL, NULL };
246         SCDynamicStoreRef     store   = SCDynamicStoreCreate(NULL, CFSTR("watchForPreferenceChanges"), NetworkChanged, &context);
247         CFMutableArrayRef     keys    = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
248     CFRunLoopSourceRef    rls;
249         
250         assert(store != NULL);
251         assert(keys != NULL);
252     
253         CFArrayAppendValue(keys, SC_DYNDNS_STATE_KEY);
254         CFArrayAppendValue(keys, SC_DYNDNS_SETUP_KEY);
255
256         (void)SCDynamicStoreSetNotificationKeys(store, keys, NULL);
257
258         rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
259     assert(rls != NULL);
260     
261         CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes);
262         CFRelease(rls);
263
264     CFRelease(keys);
265         CFRelease(store);
266 }
267
268
269 -(int)statusForHostName:(NSString * )domain
270 {
271         SCDynamicStoreRef store       = SCDynamicStoreCreate(NULL, CFSTR("statusForHostName"), NULL, NULL);
272     NSString     *lowercaseDomain = [domain lowercaseString];
273     int status = 1;
274     
275     assert(store != NULL);
276         
277     NSDictionary *dynamicDNS = (NSDictionary *)SCDynamicStoreCopyValue(store, SC_DYNDNS_STATE_KEY);
278     if (dynamicDNS) {
279         NSDictionary *hostNames = [dynamicDNS objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY];
280         NSDictionary *infoDict  = [hostNames objectForKey:lowercaseDomain];
281         if (infoDict) status = [[infoDict objectForKey:(NSString*)SC_DYNDNS_STATUS_KEY] intValue];
282         CFRelease(dynamicDNS);
283         }
284     CFRelease(store);
285
286     return status;
287 }
288
289
290 - (void)startDomainBrowsing
291 {
292     DNSServiceFlags flags;
293     OSStatus err = noErr;
294     
295     flags = kDNSServiceFlagsRegistrationDomains;
296     err = DNSServiceEnumerateDomains(&regQuery.service, flags, 0, registrationDomainReply, (void *)self);
297     if (err == kDNSServiceErr_NoError) MyDNSServiceAddServiceToRunLoop(&regQuery);
298
299     flags = kDNSServiceFlagsBrowseDomains;
300     err = DNSServiceEnumerateDomains(&browseQuery.service, flags, 0, browseDomainReply, (void *)self);
301     if (err == kDNSServiceErr_NoError) MyDNSServiceAddServiceToRunLoop(&browseQuery);
302 }
303
304
305 -(void)readPreferences
306 {
307         NSDictionary *origDict;
308     NSArray      *regDomainArray;
309     NSArray      *hostArray;
310
311     if (currentRegDomain)          [currentRegDomain release];
312     if (currentBrowseDomainsArray) [currentBrowseDomainsArray release];
313     if (currentHostName)           [currentHostName release];
314
315         SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("com.apple.preference.bonjour"), NULL, NULL);
316         origDict = (NSDictionary *)SCDynamicStoreCopyValue(store, SC_DYNDNS_SETUP_KEY);
317
318         regDomainArray = [origDict objectForKey:(NSString *)SC_DYNDNS_REGDOMAINS_KEY];
319         if (regDomainArray && [regDomainArray count] > 0) {
320                 currentRegDomain = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy];
321                 currentWideAreaState = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue];
322     } else {
323                 currentRegDomain = [[NSString alloc] initWithString:@""];
324                 currentWideAreaState = NO;
325         }
326
327         currentBrowseDomainsArray = [[origDict objectForKey:(NSString *)SC_DYNDNS_BROWSEDOMAINS_KEY] retain];
328
329     hostArray = [origDict objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY];
330         if (hostArray && [hostArray count] > 0) {
331                 currentHostName = [[[hostArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy];
332         } else {
333                 currentHostName = [[NSString alloc] initWithString:@""];
334     }
335
336     if (origDict) CFRelease((CFDictionaryRef)origDict);
337     if (store) CFRelease(store);
338 }
339
340
341 - (void)tableViewSelectionDidChange:(NSNotification *)notification
342 {
343         [removeBrowseDomainButton setEnabled:[[notification object] numberOfSelectedRows]];
344 }
345
346
347 - (void)setBrowseDomainsComboBox
348 {
349         NSString * domain = nil;
350         
351         if ([defaultBrowseDomainsArray count] > 0) {
352                 NSEnumerator * arrayEnumerator = [defaultBrowseDomainsArray objectEnumerator];
353                 while ((domain = [arrayEnumerator nextObject]) != NULL) {
354                         if ([self domainAlreadyInList:domain] == NO) break;
355                 }
356         }
357         if (domain) [browseDomainsComboBox setStringValue:domain];
358         else        [browseDomainsComboBox setStringValue:@""];
359 }
360
361
362 - (IBAction)addBrowseDomainClicked:(id)sender
363 {
364         [self setBrowseDomainsComboBox];
365
366         [NSApp beginSheet:addBrowseDomainWindow modalForWindow:mainWindow modalDelegate:self
367                 didEndSelector:@selector(addBrowseDomainSheetDidEnd:returnCode:contextInfo:) contextInfo:sender];
368
369         [browseDomainList deselectAll:sender];
370         [self updateApplyButtonState];
371 }
372
373
374 - (IBAction)removeBrowseDomainClicked:(id)sender
375 {
376         (void)sender; // Unused
377         int selectedBrowseDomain = [browseDomainList selectedRow];
378         [browseDomainsArray removeObjectAtIndex:selectedBrowseDomain];
379         [browseDomainList reloadData];
380         [self updateApplyButtonState];
381 }
382
383
384 - (IBAction)enableBrowseDomainClicked:(id)sender
385 {
386         NSTableView *tableView = sender;
387     NSMutableDictionary *browseDomainDict;
388         int value;
389         
390         browseDomainDict = [[browseDomainsArray objectAtIndex:[tableView clickedRow]] mutableCopy];
391         value = [[browseDomainDict objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue];
392         [browseDomainDict setObject:[[[NSNumber alloc] initWithInt:(!value)] autorelease] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
393         [browseDomainsArray replaceObjectAtIndex:[tableView clickedRow] withObject:browseDomainDict];
394         [tableView reloadData];
395         [self updateApplyButtonState];
396         [browseDomainDict release];
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     if ((currentHostName && ([hostNameString compare:currentHostName] != NSOrderedSame)) ||
685         (currentRegDomain && ([regDomainString compare:currentRegDomain] != NSOrderedSame) && ([wideAreaCheckBox state])) ||
686         (currentHostName == nil && ([hostNameString length]) > 0) ||
687         (currentRegDomain == nil && ([regDomainString length]) > 0) ||
688         (currentWideAreaState  != [wideAreaCheckBox state]) ||
689         (hostNameSharedSecretValue != nil) ||
690         (regSharedSecretValue != nil) ||
691                 (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO))
692     {
693         [self enableApplyButton];
694     } else {
695         [self disableApplyButton];
696     }
697 }
698
699
700
701 - (void)controlTextDidChange:(NSNotification *)notification
702 {
703         (void)notification; // Unused
704     [self updateApplyButtonState];
705 }
706
707
708
709 - (IBAction)comboAction:(id)sender
710 {
711         (void)sender; // Unused
712     [self updateApplyButtonState];
713 }
714
715
716 - (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(int)ind
717 {
718     NSString *domain = nil;
719     if      ([aComboBox isEqualTo:browseDomainsComboBox]) domain = [browseDataSource objectAtIndex:ind];
720     else if ([aComboBox isEqualTo:regDomainsComboBox])    domain = [registrationDataSource objectAtIndex:ind];
721     return domain;
722 }
723
724
725
726 - (int)numberOfItemsInComboBox:(NSComboBox *)aComboBox
727 {
728     int count = 0;
729     if      ([aComboBox isEqualTo:browseDomainsComboBox]) count = [browseDataSource count];
730     else if ([aComboBox isEqualTo:regDomainsComboBox])    count = [registrationDataSource count];
731     return count;
732 }
733
734
735 - (NSMutableArray *)browseDataSource
736 {
737     return browseDataSource;
738 }
739
740
741 - (NSMutableArray *)registrationDataSource
742 {
743     return registrationDataSource;
744 }
745
746
747 - (NSComboBox *)browseDomainsComboBox
748 {
749     return browseDomainsComboBox;
750 }
751
752
753 - (NSComboBox *)regDomainsComboBox
754 {
755     return regDomainsComboBox;
756 }
757
758
759 - (NSString *)currentRegDomain
760 {
761     return currentRegDomain;
762 }
763
764
765 - (NSMutableArray *)defaultBrowseDomainsArray
766 {
767     return defaultBrowseDomainsArray;
768 }
769
770
771 - (NSArray *)currentBrowseDomainsArray
772 {
773     return currentBrowseDomainsArray;
774 }
775
776
777 - (NSString *)currentHostName
778 {
779     return currentHostName;
780 }
781
782
783 - (NSString *)defaultRegDomain
784 {
785         return defaultRegDomain;
786 }
787
788
789 - (void)setDefaultRegDomain:(NSString *)domain
790 {
791         [defaultRegDomain release];
792         defaultRegDomain = domain;
793         [defaultRegDomain retain];
794 }
795
796
797 - (void)didSelect
798 {
799     [super didSelect];
800     mainWindow = [[self mainView] window];
801 }
802
803
804 - (void)mainViewDidLoad
805 {       
806     [comboAuthButton setString:"system.preferences"];
807     [comboAuthButton setDelegate:self];
808     [comboAuthButton updateStatus:nil];
809     [comboAuthButton setAutoupdate:YES];
810 }
811
812
813
814 - (IBAction)applyClicked:(id)sender
815 {
816         (void)sender; // Unused
817     [self applyCurrentState];
818 }
819
820
821 - (void)applyCurrentState
822 {
823     [self validateTextFields];
824
825     if (toolInstalled == YES) {
826         [self savePreferences];
827         [self disableApplyButton];
828         [mainWindow makeFirstResponder:nil];
829     }
830 }
831
832
833 - (void)enableApplyButton
834 {
835     [applyButton setEnabled:YES];
836     [revertButton setEnabled:YES];
837     prefsNeedUpdating = YES;
838 }
839
840
841 - (void)disableApplyButton
842 {
843     [applyButton setEnabled:NO];
844     [revertButton setEnabled:NO];
845     prefsNeedUpdating = NO;
846 }
847
848
849 - (void)toggleWideAreaBonjour:(BOOL)state
850 {
851         [wideAreaCheckBox setState:state];
852         [regDomainsComboBox setEnabled:state];
853         [registrationSharedSecretButton setEnabled:state];
854 }
855
856
857 - (IBAction)revertClicked:(id)sender
858 {
859     [self restorePreferences];
860         [browseDomainList deselectAll:sender];
861     [mainWindow makeFirstResponder:nil];
862 }
863
864
865 - (void)restorePreferences
866 {
867     [self setupInitialValues];
868 }
869
870
871 - (void)savePanelWillClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
872 {
873         (void)sheet; // Unused
874     DNSServiceDiscoveryPref * me = (DNSServiceDiscoveryPref *)contextInfo;
875     
876     if (returnCode == NSAlertDefaultReturn) {
877         [me applyCurrentState];
878     } else if (returnCode == NSAlertAlternateReturn ) {
879         [me restorePreferences];
880     }
881     
882     [me enableControls];
883     [me replyToShouldUnselect:(returnCode != NSAlertOtherReturn)];
884 }
885
886
887 -(SecKeychainItemRef)copyKeychainItemforDomain:(NSString *)domain
888 {
889     const char * serviceName = [domain UTF8String];
890     UInt32 type              = 'ddns';
891         UInt32 typeLength        = sizeof(type);
892
893         SecKeychainAttribute attrs[] = { { kSecServiceItemAttr, strlen(serviceName),   (char *)serviceName },
894                                      { kSecTypeItemAttr,             typeLength, (UInt32 *)&type       } };
895     
896         SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };
897     SecKeychainSearchRef searchRef;
898     SecKeychainItemRef itemRef = NULL;
899     OSStatus err;
900     
901     err = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef);
902         if (err == noErr) {
903                 err = SecKeychainSearchCopyNext(searchRef, &itemRef);
904                 if (err != noErr) itemRef = NULL;
905         }
906         return itemRef;
907 }
908
909
910 -(NSString *)sharedSecretKeyName:(NSString * )domain
911 {
912         SecKeychainItemRef itemRef = NULL;
913         NSString *keyName = nil;
914         OSStatus err;
915             
916         err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
917         assert(err == noErr);
918
919         itemRef = [self copyKeychainItemforDomain:[domain lowercaseString]];
920     if (itemRef) {
921         UInt32 tags[1];
922         SecKeychainAttributeInfo attrInfo;
923         SecKeychainAttributeList *attrList = NULL;
924         SecKeychainAttribute attribute;
925                 unsigned int i;
926                 
927         tags[0] = kSecAccountItemAttr;
928         attrInfo.count = 1;
929         attrInfo.tag = tags;
930         attrInfo.format = NULL;
931                                         
932         err = SecKeychainItemCopyAttributesAndData(itemRef,  &attrInfo, NULL, &attrList, NULL, NULL);
933         if (err == noErr) {
934             for (i = 0; i < attrList->count; i++) {
935                 attribute = attrList->attr[i];
936                 if (attribute.tag == kSecAccountItemAttr) {
937                     keyName = [[NSString alloc] initWithBytes:attribute.data length:attribute.length encoding:NSUTF8StringEncoding];
938                     break;
939                 }
940             }
941             if (attrList) (void)SecKeychainItemFreeAttributesAndData(attrList, NULL);
942         }
943                 CFRelease(itemRef);
944         }
945     return [keyName autorelease];
946 }
947
948
949 -(NSString *)domainForHostName:(NSString *)hostNameString
950 {
951     NSString * domainName = nil;
952     char text[64];
953     char * ptr = NULL;
954     
955     ptr = (char *)[hostNameString UTF8String];
956     if (ptr) {
957         ptr = (char *)GetNextLabel(ptr, text);
958         domainName = [[NSString alloc] initWithUTF8String:(const char *)ptr];             
959     }
960     return ([domainName autorelease]);
961 }
962
963
964 - (NSData *)dataForDomain:(NSString *)domainName isEnabled:(BOOL)enabled
965 {
966         NSMutableArray      *domainsArray; 
967         NSMutableDictionary *domainDict = nil;
968         
969         if (domainName && [domainName length] > 0) {
970                 domainDict= [[[NSMutableDictionary alloc] initWithCapacity:2] autorelease];
971                 [domainDict setObject:domainName forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
972                 [domainDict setObject:[[[NSNumber alloc] initWithBool:enabled] autorelease] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
973         }
974         domainsArray = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
975         if (domainDict) [domainsArray addObject:domainDict];
976         return [NSArchiver archivedDataWithRootObject:domainsArray];
977 }
978
979
980 - (NSData *)dataForDomainArray:(NSArray *)domainArray
981 {
982         return [NSArchiver archivedDataWithRootObject:domainArray];
983 }
984
985
986 - (NSData *)dataForSharedSecret:(NSString *)secret domain:(NSString *)domainName key:(NSString *)keyName
987 {
988         NSMutableDictionary *sharedSecretDict = [[[NSMutableDictionary alloc] initWithCapacity:3] autorelease];
989         [sharedSecretDict setObject:secret forKey:(NSString *)SC_DYNDNS_SECRET_KEY];
990         [sharedSecretDict setObject:[domainName lowercaseString] forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
991         [sharedSecretDict setObject:keyName forKey:(NSString *)SC_DYNDNS_KEYNAME_KEY];
992         return [NSArchiver archivedDataWithRootObject:sharedSecretDict];
993 }
994
995
996 -(void)savePreferences
997 {
998     NSString      *hostNameString               = [hostName stringValue];
999     NSString      *regDomainString              = [regDomainsComboBox stringValue];
1000     NSString      *tempHostNameSharedSecretName = hostNameSharedSecretName;
1001     NSString      *tempRegSharedSecretName      = regSharedSecretName;
1002         NSData        *browseDomainData             = nil;
1003     BOOL          regSecretWasSet               = NO;
1004     BOOL          hostSecretWasSet              = NO;
1005     OSStatus      err                           = noErr;
1006
1007         hostNameString                = [self trimCharactersFromDomain:hostNameString];
1008         regDomainString               = [self trimCharactersFromDomain:regDomainString];
1009         tempHostNameSharedSecretName  = [self trimCharactersFromDomain:tempHostNameSharedSecretName];
1010         tempRegSharedSecretName       = [self trimCharactersFromDomain:tempRegSharedSecretName];
1011         
1012         [hostName setStringValue:hostNameString];
1013         [regDomainsComboBox setStringValue:regDomainString];
1014     
1015     // Convert Shared Secret account names to lowercase.
1016     tempHostNameSharedSecretName = [tempHostNameSharedSecretName lowercaseString];
1017     tempRegSharedSecretName      = [tempRegSharedSecretName lowercaseString];
1018     
1019     // Save hostname shared secret.
1020     if ([hostNameSharedSecretName length] > 0 && ([hostNameSharedSecretValue length] > 0)) {
1021                 SetKeyForDomain((CFDataRef)[self dataForSharedSecret:hostNameSharedSecretValue domain:hostNameString key:tempHostNameSharedSecretName]);
1022         [hostNameSharedSecretValue release];
1023         hostNameSharedSecretValue = nil;
1024         hostSecretWasSet = YES;
1025     }
1026     
1027     // Save registration domain shared secret.
1028     if (([regSharedSecretName length] > 0) && ([regSharedSecretValue length] > 0)) {
1029                 SetKeyForDomain((CFDataRef)[self dataForSharedSecret:regSharedSecretValue domain:regDomainString key:tempRegSharedSecretName]);
1030         [regSharedSecretValue release];
1031         regSharedSecretValue = nil;
1032         regSecretWasSet = YES;
1033     }
1034
1035     // Save hostname.
1036     if ((currentHostName == NULL) || [currentHostName compare:hostNameString] != NSOrderedSame) {
1037                 err = WriteHostname((CFDataRef)[self dataForDomain:hostNameString isEnabled:YES]);
1038                 if (err != noErr) NSLog(@"WriteHostname returned %d\n", (int32_t)err);
1039         currentHostName = [hostNameString copy];
1040     } else if (hostSecretWasSet) {
1041                 WriteHostname((CFDataRef)[self dataForDomain:@"" isEnabled:NO]);
1042                 usleep(200000);  // Temporary hack
1043         if ([currentHostName length] > 0) WriteHostname((CFDataRef)[self dataForDomain:(NSString *)currentHostName isEnabled:YES]);
1044     }
1045     
1046     // Save browse domain.
1047         if (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO) {
1048                 browseDomainData = [self dataForDomainArray:browseDomainsArray];
1049                 err = WriteBrowseDomain((CFDataRef)browseDomainData);
1050                 if (err != noErr) NSLog(@"WriteBrowseDomain returned %d\n", (int32_t)err);
1051                 currentBrowseDomainsArray = [browseDomainsArray copy];
1052     }
1053         
1054     // Save registration domain.
1055     if ((currentRegDomain == NULL) || ([currentRegDomain compare:regDomainString] != NSOrderedSame) || (currentWideAreaState != [wideAreaCheckBox state])) {
1056
1057                 err = WriteRegistrationDomain((CFDataRef)[self dataForDomain:regDomainString isEnabled:[wideAreaCheckBox state]]);
1058                 if (err != noErr) NSLog(@"WriteRegistrationDomain returned %d\n", (int32_t)err);
1059         
1060                 if (currentRegDomain) CFRelease(currentRegDomain);
1061         currentRegDomain = [regDomainString copy];
1062
1063         if ([currentRegDomain length] > 0) {
1064                         currentWideAreaState = [wideAreaCheckBox state];
1065             [registrationDataSource removeObject:regDomainString];
1066             [registrationDataSource addObject:currentRegDomain];
1067             [registrationDataSource sortUsingFunction:MyArrayCompareFunction context:nil];
1068             [regDomainsComboBox reloadData];
1069         } else {
1070                         currentWideAreaState = NO;
1071                         [self toggleWideAreaBonjour:NO];
1072             if (defaultRegDomain != nil) [regDomainsComboBox setStringValue:defaultRegDomain];
1073                 }
1074     } else if (regSecretWasSet) {
1075         WriteRegistrationDomain((CFDataRef)[self dataForDomain:@"" isEnabled:NO]);
1076                 usleep(200000);  // Temporary hack
1077         if ([currentRegDomain length] > 0) WriteRegistrationDomain((CFDataRef)[self dataForDomain:currentRegDomain isEnabled:currentWideAreaState]);
1078     }
1079 }   
1080
1081
1082 - (NSPreferencePaneUnselectReply)shouldUnselect
1083 {
1084 #if 1
1085     if (prefsNeedUpdating == YES) {
1086     
1087         [self disableControls];
1088         
1089         NSBeginAlertSheet(
1090                     @"Apply Configuration Changes?",
1091                     @"Apply",
1092                     @"Don't Apply",
1093                     @"Cancel",
1094                     mainWindow,
1095                     self,
1096                     @selector( savePanelWillClose:returnCode:contextInfo: ),
1097                     NULL,
1098                     (void *) self, // sender,
1099                     @"" );
1100         return NSUnselectLater;
1101     }
1102 #endif
1103     
1104     return NSUnselectNow;
1105 }
1106
1107
1108 -(void)disableControls
1109 {
1110     [hostName setEnabled:NO];
1111     [hostNameSharedSecretButton setEnabled:NO];
1112     [browseDomainsComboBox setEnabled:NO];
1113     [applyButton setEnabled:NO];
1114     [revertButton setEnabled:NO];
1115     [wideAreaCheckBox setEnabled:NO];
1116     [regDomainsComboBox setEnabled:NO];
1117     [registrationSharedSecretButton setEnabled:NO];
1118     [statusImageView setEnabled:NO];
1119         
1120         browseDomainListEnabled = NO;
1121         [browseDomainList deselectAll:self];
1122         [browseDomainList setEnabled:NO];
1123         
1124         [addBrowseDomainButton setEnabled:NO];
1125         [removeBrowseDomainButton setEnabled:NO];
1126 }
1127
1128
1129 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
1130 {
1131         (void)row; // Unused
1132         (void)tableView; // Unused
1133         return browseDomainListEnabled;
1134 }
1135
1136
1137 -(void)enableControls
1138 {
1139     [hostName setEnabled:YES];
1140     [hostNameSharedSecretButton setEnabled:YES];
1141     [browseDomainsComboBox setEnabled:YES];
1142     [wideAreaCheckBox setEnabled:YES];
1143     [registrationSharedSecretButton setEnabled:YES];
1144     [self toggleWideAreaBonjour:[wideAreaCheckBox state]];
1145     [statusImageView setEnabled:YES];
1146         [addBrowseDomainButton setEnabled:YES];
1147
1148         [browseDomainList setEnabled:YES];
1149         [browseDomainList deselectAll:self];
1150         browseDomainListEnabled = YES;
1151
1152         [removeBrowseDomainButton setEnabled:[browseDomainList numberOfSelectedRows]];
1153         [applyButton setEnabled:prefsNeedUpdating];
1154         [revertButton setEnabled:prefsNeedUpdating];
1155 }
1156
1157
1158 - (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view
1159 {
1160         (void)view; // Unused
1161     [self enableControls];
1162 }
1163
1164
1165 - (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view
1166 {    
1167         (void)view; // Unused
1168     [self disableControls];
1169 }
1170
1171 @end
1172
1173
1174 // Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion
1175 // e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4"
1176 // To expand "version" to its value before making the string, use STRINGIFY(version) instead
1177 #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) #s
1178 #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)
1179
1180 // NOT static -- otherwise the compiler may optimize it out
1181 // The "@(#) " pattern is a special prefix the "what" command looks for
1182 const char VersionString_SCCS[] = "@(#) Bonjour Preference Pane " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
1183
1184 #if _BUILDING_XCODE_PROJECT_
1185 // If the process crashes, then this string will be magically included in the automatically-generated crash log
1186 const char *__crashreporter_info__ = VersionString_SCCS + 5;
1187 asm(".desc ___crashreporter_info__, 0x10");
1188 #endif