Imported Upstream version 878.70.2
[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 "CNDomainBrowserView.h"
46 #import "BonjourSCStore.h"
47 #import "BonjourPrefTool.h"
48 #import <Foundation/NSXPCConnection_Private.h>
49
50 #include "../../Clients/ClientCommon.h"
51
52 #pragma mark - BonjourPrefTool
53
54 static OSStatus
55 DNSPrefTool_SetKeychainEntry(NSDictionary * secretDictionary)
56 {
57     __block OSStatus result;
58     BonjourPrefTool * prefTool;
59     
60     NSXPCConnection * _connectionToTool = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.preference.bonjour.tool"];
61     _connectionToTool.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BonjourPrefToolProtocol)];
62     [_connectionToTool resume];
63     
64 #if 0
65     prefTool = [_connectionToTool remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
66         NSLog( @"Cannot connect to BonjourPrefTool: %@.", error);
67         result = error.code;
68     }];
69 #else
70     prefTool = [_connectionToTool synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
71         NSLog( @"Cannot connect to BonjourPrefTool: %@.", error);
72         result = error.code;
73     }];
74 #endif
75     [prefTool setKeychainEntry: secretDictionary withStatus: ^(OSStatus status){
76         result = status;
77     }];
78
79     [_connectionToTool invalidate];
80
81     return (result);
82 }
83
84 #pragma mark -
85
86 @implementation DNSServiceDiscoveryPref
87
88 static NSInteger
89 MyArrayCompareFunction(id val1, id val2, void *context)
90 {
91         (void)context; // Unused
92     return CFStringCompare((CFStringRef)val1, (CFStringRef)val2, kCFCompareCaseInsensitive);
93 }
94
95 static NSInteger
96 MyDomainArrayCompareFunction(id val1, id val2, void *context)
97 {
98         (void)context; // Unused
99         NSString *domain1 = [val1 objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
100         NSString *domain2 = [val2 objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
101     return CFStringCompare((CFStringRef)domain1, (CFStringRef)domain2, kCFCompareCaseInsensitive);
102 }
103
104
105 static void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context)
106 {
107         (void)store; // Unused
108         (void)changedKeys; // Unused
109     DNSServiceDiscoveryPref * me = (__bridge DNSServiceDiscoveryPref *)context;
110     assert(me != NULL);
111     
112     [me setupInitialValues];
113 }
114
115
116 -(void)updateStatusImageView
117 {
118     int value = [self statusForHostName:currentHostName];
119     if      (value == 0) [statusImageView setImage:successImage];
120     else if (value >  0) [statusImageView setImage:inprogressImage];
121     else                 [statusImageView setImage:failureImage];
122 }
123
124
125 - (void)watchForPreferenceChanges
126 {
127         SCDynamicStoreContext context = { 0, (__bridge void * _Nullable)(self), NULL, NULL, NULL };
128         SCDynamicStoreRef     store   = SCDynamicStoreCreate(NULL, CFSTR("watchForPreferenceChanges"), NetworkChanged, &context);
129         CFMutableArrayRef     keys    = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
130     CFRunLoopSourceRef    rls;
131         
132         assert(store != NULL);
133         assert(keys != NULL);
134     
135         CFArrayAppendValue(keys, SC_DYNDNS_STATE_KEY);
136         CFArrayAppendValue(keys, SC_DYNDNS_SETUP_KEY);
137
138         (void)SCDynamicStoreSetNotificationKeys(store, keys, NULL);
139
140         rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
141     assert(rls != NULL);
142     
143         CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes);
144         CFRelease(rls);
145
146     CFRelease(keys);
147         CFRelease(store);
148 }
149
150
151 -(int)statusForHostName:(NSString * )domain
152 {
153         SCDynamicStoreRef store       = SCDynamicStoreCreate(NULL, CFSTR("statusForHostName"), NULL, NULL);
154     NSString     *lowercaseDomain = [domain lowercaseString];
155     int status = 1;
156     
157     assert(store != NULL);
158         
159     NSDictionary *dynamicDNS = (NSDictionary *)CFBridgingRelease(SCDynamicStoreCopyValue(store, SC_DYNDNS_STATE_KEY));
160     if (dynamicDNS) {
161         NSDictionary *hostNames = [dynamicDNS objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY];
162         NSDictionary *infoDict  = [hostNames objectForKey:lowercaseDomain];
163         if (infoDict) status = [[infoDict objectForKey:(NSString*)SC_DYNDNS_STATUS_KEY] intValue];
164         }
165     CFRelease(store);
166
167     return status;
168 }
169
170
171 -(void)readPreferences
172 {
173         NSDictionary *origDict;
174     NSArray      *regDomainArray;
175     NSArray      *hostArray;
176
177     if (currentRegDomain)          currentRegDomain = nil;
178     if (currentBrowseDomainsArray) currentBrowseDomainsArray = nil;
179     if (currentHostName)           currentHostName = nil;
180
181         SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("com.apple.preference.bonjour"), NULL, NULL);
182         origDict = (NSDictionary *)CFBridgingRelease(SCDynamicStoreCopyValue(store, SC_DYNDNS_SETUP_KEY));
183
184         regDomainArray = [origDict objectForKey:(NSString *)SC_DYNDNS_REGDOMAINS_KEY];
185         if (regDomainArray && [regDomainArray count] > 0) {
186                 currentRegDomain = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy];
187                 currentWideAreaState = [[[regDomainArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue];
188     } else {
189                 currentRegDomain = @"";
190                 currentWideAreaState = NO;
191         }
192
193         currentBrowseDomainsArray = [origDict objectForKey:(NSString *)SC_DYNDNS_BROWSEDOMAINS_KEY];
194
195     hostArray = [origDict objectForKey:(NSString *)SC_DYNDNS_HOSTNAMES_KEY];
196         if (hostArray && [hostArray count] > 0) {
197                 currentHostName = [[[hostArray objectAtIndex:0] objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY] copy];
198         } else {
199                 currentHostName = @"";
200     }
201
202     if (store) CFRelease(store);
203 }
204
205
206 - (void)tableViewSelectionDidChange:(NSNotification *)notification
207 {
208         [removeBrowseDomainButton setEnabled:[[notification object] numberOfSelectedRows]];
209 }
210
211
212 - (IBAction)addBrowseDomainClicked:(id)sender
213 {
214     NSWindow *  window = (([NSEvent modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask) ? addBrowseDomainManualWindow : addBrowseDomainWindow;
215     [browseDomainTextField setStringValue: [NSString string]];
216
217     [self disableControls];
218         [NSApp beginSheet:window modalForWindow:mainWindow modalDelegate:self
219                 didEndSelector:@selector(addBrowseDomainSheetDidEnd:returnCode:contextInfo:) contextInfo:(__bridge void * _Null_unspecified)(sender)];
220
221         [browseDomainList deselectAll:sender];
222         [self updateApplyButtonState];
223 }
224
225
226 - (IBAction)removeBrowseDomainClicked:(id)sender
227 {
228         (void)sender; // Unused
229         int selectedBrowseDomain = [browseDomainList selectedRow];
230         [browseDomainsArray removeObjectAtIndex:selectedBrowseDomain];
231         [browseDomainList reloadData];
232         [self updateApplyButtonState];
233 }
234
235
236 - (IBAction)enableBrowseDomainClicked:(id)sender
237 {
238         NSTableView *tableView = sender;
239     NSMutableDictionary *browseDomainDict;
240         NSInteger value;
241         
242         browseDomainDict = [[browseDomainsArray objectAtIndex:[tableView clickedRow]] mutableCopy];
243         value = [[browseDomainDict objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY] intValue];
244         [browseDomainDict setObject:[NSNumber numberWithInt:(!value)] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
245         [browseDomainsArray replaceObjectAtIndex:[tableView clickedRow] withObject:browseDomainDict];
246         [tableView reloadData];
247         [self updateApplyButtonState];
248 }
249
250
251
252 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
253 {
254         (void)tableView; // Unused
255         int numberOfRows = 0;
256                 
257         if (browseDomainsArray) {
258                 numberOfRows = [browseDomainsArray count];
259         }
260         return numberOfRows;
261 }
262
263
264 - (void)tabView:(NSTabView *)xtabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
265 {
266         (void)xtabView; // Unused
267         (void)tabViewItem; // Unused
268         [browseDomainList deselectAll:self];
269         [mainWindow makeFirstResponder:nil];
270 }
271  
272
273 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
274 {
275         (void)tableView; // Unused
276         NSDictionary *browseDomainDict;
277         id           value = nil;
278                 
279         if (browseDomainsArray) {
280                 browseDomainDict = [browseDomainsArray objectAtIndex:row];
281                 if (browseDomainDict) {
282                         if ([[tableColumn identifier] isEqualTo:(NSString *)SC_DYNDNS_ENABLED_KEY]) {
283                                 value = [browseDomainDict objectForKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
284                         } else if ([[tableColumn identifier] isEqualTo:(NSString *)SC_DYNDNS_DOMAIN_KEY]) {
285                 value = [browseDomainDict objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
286                         }
287                 }
288         }
289         return value;
290 }
291
292
293 - (void)setupInitialValues
294 {    
295     [self readPreferences];
296     
297     if (currentHostName) {
298                 [hostName setStringValue:currentHostName];
299                 [self updateStatusImageView];
300         }
301         
302         if (browseDomainsArray) {
303                 browseDomainsArray = nil;
304         }
305         
306         if (currentBrowseDomainsArray) {
307                 browseDomainsArray = [currentBrowseDomainsArray mutableCopy];
308                 if (browseDomainsArray) {
309                         [browseDomainsArray sortUsingFunction:MyDomainArrayCompareFunction context:nil];
310                         if ([browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO) {
311                 [BonjourSCStore setObject: browseDomainsArray forKey: (NSString *)SC_DYNDNS_BROWSEDOMAINS_KEY];
312                                 currentBrowseDomainsArray = [browseDomainsArray copy];
313                         }
314                 }
315         } else {
316                 browseDomainsArray = nil;
317         }
318         [browseDomainList reloadData];
319         
320     if (currentRegDomain && ([currentRegDomain length] > 0)) {
321         regDomainView.domain = currentRegDomain;
322     }
323     
324     if (currentWideAreaState) {
325         [self toggleWideAreaBonjour:YES];
326     } else {
327         [self toggleWideAreaBonjour:NO];
328     }
329
330     if (hostNameSharedSecretValue) {
331         hostNameSharedSecretValue = nil;
332     }
333     
334     if (regSharedSecretValue) {
335         regSharedSecretValue = nil;
336     }
337     
338     [self updateApplyButtonState];
339     [mainWindow makeFirstResponder:nil];
340         [browseDomainList deselectAll:self];
341         [removeBrowseDomainButton setEnabled:NO];
342 }
343
344
345
346 - (void)awakeFromNib
347 {
348     prefsNeedUpdating         = NO;
349         browseDomainListEnabled   = NO;
350         defaultRegDomain          = nil;
351     currentRegDomain          = nil;
352         currentBrowseDomainsArray = nil;
353     currentHostName           = nil;
354     hostNameSharedSecretValue = nil;
355     regSharedSecretValue      = nil;
356         browseDomainsArray        = nil;
357     currentWideAreaState      = NO;
358         NSString *successPath     = [[NSBundle bundleForClass:[self class]] pathForResource:@"success"    ofType:@"tiff"];
359         NSString *inprogressPath  = [[NSBundle bundleForClass:[self class]] pathForResource:@"inprogress" ofType:@"tiff"];
360         NSString *failurePath     = [[NSBundle bundleForClass:[self class]] pathForResource:@"failure"    ofType:@"tiff"];
361
362     registrationDataSource    = [[NSMutableArray alloc] init];
363         successImage              = [[NSImage alloc] initWithContentsOfFile:successPath];
364         inprogressImage           = [[NSImage alloc] initWithContentsOfFile:inprogressPath];
365         failureImage              = [[NSImage alloc] initWithContentsOfFile:failurePath];
366
367     [tabView selectFirstTabViewItem:self];
368     [self setupInitialValues];
369     [self watchForPreferenceChanges];
370     
371 }
372
373 - (void)willSelect
374 {
375     [super willSelect];
376     [bonjourBrowserView startBrowse];
377     [registrationBrowserView startBrowse];
378     
379 }
380
381 - (void)willUnselect
382 {
383     [super willUnselect];
384     [bonjourBrowserView stopBrowse];
385     [registrationBrowserView stopBrowse];
386 }
387
388 - (IBAction)closeMyCustomSheet:(id)sender
389 {
390     BOOL result = [sender tag];
391
392     if (result) [NSApp endSheet:[sender window] returnCode:NSOKButton];
393     else        [NSApp endSheet:[sender window] returnCode:NSCancelButton];
394 }
395
396
397 - (void)sharedSecretSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
398 {
399     NSButton * button = (__bridge NSButton *)contextInfo;
400     [sheet orderOut:self];
401     [self enableControls];
402     
403     if (returnCode == NSOKButton) {
404         if ([button isEqualTo:hostNameSharedSecretButton]) {
405             hostNameSharedSecretName = [[NSString alloc] initWithString:[sharedSecretName stringValue]];
406             hostNameSharedSecretValue = [[NSString alloc] initWithString:[sharedSecretValue stringValue]];
407         } else {
408             regSharedSecretName = [[NSString alloc] initWithString:[sharedSecretName stringValue]];
409             regSharedSecretValue = [[NSString alloc] initWithString:[sharedSecretValue stringValue]];
410         }
411         [self updateApplyButtonState];
412     }
413     [sharedSecretValue setStringValue:@""];
414 }
415
416
417 - (BOOL)domainAlreadyInList:(NSString *)domainString
418 {
419         if (browseDomainsArray) {
420                 NSDictionary *domainDict;
421                 NSString     *domainName;
422                 NSEnumerator *arrayEnumerator = [browseDomainsArray objectEnumerator];
423                 while ((domainDict = [arrayEnumerator nextObject]) != NULL) {
424                         domainName = [domainDict objectForKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
425                         if ([domainString caseInsensitiveCompare:domainName] == NSOrderedSame) return YES;
426                 }
427         }
428         return NO;
429 }
430
431
432 - (NSString *)trimCharactersFromDomain:(NSString *)domain
433 {
434         NSMutableCharacterSet * trimSet = [NSMutableCharacterSet whitespaceCharacterSet];
435         [trimSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
436         return [domain stringByTrimmingCharactersInSet:trimSet];        
437 }
438
439
440 - (void)addBrowseDomainSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
441 {
442         (void)contextInfo; // Unused
443     [sheet orderOut:self];
444     [self enableControls];
445     
446     if (returnCode == NSOKButton) {
447         NSString * newBrowseDomainString;
448         if(sheet == addBrowseDomainManualWindow)    newBrowseDomainString = [self trimCharactersFromDomain:[browseDomainTextField stringValue]];
449         else                                        newBrowseDomainString = [self trimCharactersFromDomain:bonjourBrowserView.selectedDNSDomain];
450                 NSMutableDictionary *newBrowseDomainDict;
451                 if (browseDomainsArray == nil) browseDomainsArray = [[NSMutableArray alloc] initWithCapacity:0];
452                 if ([self domainAlreadyInList:newBrowseDomainString] == NO) {
453                         newBrowseDomainDict = [[NSMutableDictionary alloc] initWithCapacity:2];
454
455                         [newBrowseDomainDict setObject:newBrowseDomainString forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
456                         [newBrowseDomainDict setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
457                         
458                         [browseDomainsArray addObject:newBrowseDomainDict];
459                         [browseDomainsArray sortUsingFunction:MyDomainArrayCompareFunction context:nil];
460                         [browseDomainList reloadData];
461                         [self updateApplyButtonState];
462                 }
463     }
464 }
465
466 -(void)validateTextFields
467 {
468     [hostName validateEditing];
469     [browseDomainTextField validateEditing];
470 }
471
472
473 - (IBAction)changeButtonPressed:(id)sender
474 {
475     NSString * keyName;
476     
477     [self disableControls];
478     [self validateTextFields];
479     [mainWindow makeFirstResponder:nil];
480         [browseDomainList deselectAll:sender];
481
482     if ([sender isEqualTo:hostNameSharedSecretButton]) {                
483         if (hostNameSharedSecretValue) {
484                         [sharedSecretValue setStringValue:hostNameSharedSecretValue];
485         } else if ((keyName = [self sharedSecretKeyName:[hostName stringValue]]) != NULL) {
486                         [sharedSecretName setStringValue:keyName];
487             [sharedSecretValue setStringValue:@"****************"];
488                 } else {
489                         [sharedSecretName setStringValue:[hostName stringValue]];
490             [sharedSecretValue setStringValue:@""];
491         }
492
493     } else {        
494         if (regSharedSecretValue) {
495                         [sharedSecretValue setStringValue:regSharedSecretValue];
496         } else if ((keyName = [self sharedSecretKeyName:regDomainView.domain]) != NULL) {
497                         [sharedSecretName setStringValue:keyName];
498             [sharedSecretValue setStringValue:@"****************"];
499                 } else {
500                         [sharedSecretName setStringValue:regDomainView.domain];
501             [sharedSecretValue setStringValue:@""];
502         }
503     }
504     
505     [sharedSecretWindow resignFirstResponder];
506
507     if ([[sharedSecretName stringValue] length] > 0) [sharedSecretWindow makeFirstResponder:sharedSecretValue];
508     else                                             [sharedSecretWindow makeFirstResponder:sharedSecretName];
509     
510     [NSApp beginSheet:sharedSecretWindow modalForWindow:mainWindow modalDelegate:self
511             didEndSelector:@selector(sharedSecretSheetDidEnd:returnCode:contextInfo:) contextInfo:(__bridge void * _Null_unspecified)(sender)];
512 }
513
514
515 - (IBAction)selectWideAreaDomainButtonPressed:(id)sender
516 {
517         NSWindow *  window = (([NSEvent modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask) ? selectRegistrationDomainManualWindow : selectRegistrationDomainWindow;
518         regDomainTextField.stringValue = regDomainView.domain;
519         
520     [self disableControls];
521         [NSApp beginSheet:window modalForWindow:mainWindow modalDelegate:self
522            didEndSelector:@selector(selectWideAreaDomainSheetDidEnd:returnCode:contextInfo:) contextInfo:(__bridge void * _Null_unspecified)(sender)];
523         
524         [self updateApplyButtonState];
525 }
526
527 - (void)selectWideAreaDomainSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
528 {
529         (void)contextInfo; // Unused
530         [sheet orderOut:self];
531         [self enableControls];
532         
533         if (returnCode == NSOKButton) {
534                 NSString * newRegDomainString;
535                 if(sheet == selectRegistrationDomainManualWindow) newRegDomainString = [self trimCharactersFromDomain:[regDomainTextField stringValue]];
536                 else                                              newRegDomainString = [self trimCharactersFromDomain:registrationBrowserView.selectedDNSDomain];
537         regDomainView.domain = newRegDomainString;
538         [self updateApplyButtonState];
539         }
540 }
541
542 - (IBAction)wideAreaCheckBoxChanged:(id)sender
543 {    
544     [self toggleWideAreaBonjour:[sender state]];
545     [self updateApplyButtonState];
546     [mainWindow makeFirstResponder:nil];
547 }
548
549
550
551 - (void)updateApplyButtonState
552 {
553     NSString *hostNameString  = [hostName stringValue];
554     NSString *regDomainString = regDomainView.domain;
555     if ((currentHostName && ([hostNameString compare:currentHostName] != NSOrderedSame)) ||
556         (currentRegDomain && ([regDomainString compare:currentRegDomain] != NSOrderedSame) && ([wideAreaCheckBox state])) ||
557         (currentHostName == nil && ([hostNameString length]) > 0) ||
558         (currentRegDomain == nil && ([regDomainString length]) > 0) ||
559         (currentWideAreaState  != [wideAreaCheckBox state]) ||
560         (hostNameSharedSecretValue != nil) ||
561         (regSharedSecretValue != nil) ||
562                 (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO))
563     {
564         [self enableApplyButton];
565     } else {
566         [self disableApplyButton];
567     }
568 }
569
570
571 - (void)controlTextDidChange:(NSNotification *)notification
572 {
573         (void)notification; // Unused
574     [self updateApplyButtonState];
575 }
576
577
578 - (NSMutableArray *)registrationDataSource
579 {
580     return registrationDataSource;
581 }
582
583
584 - (NSString *)currentRegDomain
585 {
586     return currentRegDomain;
587 }
588
589
590 - (NSArray *)currentBrowseDomainsArray
591 {
592     return currentBrowseDomainsArray;
593 }
594
595
596 - (NSString *)currentHostName
597 {
598     return currentHostName;
599 }
600
601
602 - (NSString *)defaultRegDomain
603 {
604         return defaultRegDomain;
605 }
606
607
608 - (void)setDefaultRegDomain:(NSString *)domain
609 {
610         defaultRegDomain = domain;
611 }
612
613
614 - (void)didSelect
615 {
616     [super didSelect];
617     mainWindow = [[self mainView] window];
618 }
619
620 - (void)mainViewDidLoad
621 {
622     [comboAuthButton setString:"system.preferences"];
623     [comboAuthButton setDelegate:self];
624     [comboAuthButton setAutoupdate:YES];
625     [super mainViewDidLoad];
626 }
627
628
629
630 - (IBAction)applyClicked:(id)sender
631 {
632         (void)sender; // Unused
633     [self applyCurrentState];
634 }
635
636
637 - (void)applyCurrentState
638 {
639     [self validateTextFields];
640     [self savePreferences];
641     [self disableApplyButton];
642     [mainWindow makeFirstResponder:nil];
643 }
644
645
646 - (void)enableApplyButton
647 {
648     [applyButton setEnabled:YES];
649     [revertButton setEnabled:YES];
650     prefsNeedUpdating = YES;
651 }
652
653
654 - (void)disableApplyButton
655 {
656     [applyButton setEnabled:NO];
657     [revertButton setEnabled:NO];
658     prefsNeedUpdating = NO;
659 }
660
661
662 - (void)toggleWideAreaBonjour:(BOOL)state
663 {
664         [wideAreaCheckBox setState:state];
665         [registrationSelectButton setEnabled:state];
666         [registrationSharedSecretButton setEnabled:state];
667 }
668
669
670 - (IBAction)revertClicked:(id)sender
671 {
672     [self restorePreferences];
673         [browseDomainList deselectAll:sender];
674     [mainWindow makeFirstResponder:nil];
675 }
676
677
678 - (void)restorePreferences
679 {
680     [self setupInitialValues];
681 }
682
683
684 - (void)savePanelWillClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
685 {
686         (void)sheet; // Unused
687     DNSServiceDiscoveryPref * me = (__bridge DNSServiceDiscoveryPref *)contextInfo;
688     
689     if (returnCode == NSAlertDefaultReturn) {
690         [me applyCurrentState];
691     } else if (returnCode == NSAlertAlternateReturn ) {
692         [me restorePreferences];
693     }
694     
695     [me enableControls];
696     [me replyToShouldUnselect:(returnCode != NSAlertOtherReturn)];
697 }
698
699
700 -(SecKeychainItemRef)copyKeychainItemforDomain:(NSString *)domain
701 {
702     const char * serviceName = [domain UTF8String];
703     UInt32 type              = 'ddns';
704         UInt32 typeLength        = sizeof(type);
705
706         SecKeychainAttribute attrs[] = { { kSecServiceItemAttr, strlen(serviceName),   (char *)serviceName },
707                                      { kSecTypeItemAttr,             typeLength, (UInt32 *)&type       } };
708     
709         SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };
710     SecKeychainSearchRef searchRef;
711     SecKeychainItemRef itemRef = NULL;
712     OSStatus err;
713     
714     err = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef);
715         if (err == noErr) {
716                 err = SecKeychainSearchCopyNext(searchRef, &itemRef);
717                 if (err != noErr) itemRef = NULL;
718         }
719         return itemRef;
720 }
721
722
723 -(NSString *)sharedSecretKeyName:(NSString * )domain
724 {
725         SecKeychainItemRef itemRef = NULL;
726         NSString *keyName = nil;
727         OSStatus err;
728             
729         err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
730         assert(err == noErr);
731
732         itemRef = [self copyKeychainItemforDomain:[domain lowercaseString]];
733     if (itemRef) {
734         UInt32 tags[1];
735         SecKeychainAttributeInfo attrInfo;
736         SecKeychainAttributeList *attrList = NULL;
737         SecKeychainAttribute attribute;
738                 unsigned int i;
739                 
740         tags[0] = kSecAccountItemAttr;
741         attrInfo.count = 1;
742         attrInfo.tag = tags;
743         attrInfo.format = NULL;
744                                         
745         err = SecKeychainItemCopyAttributesAndData(itemRef,  &attrInfo, NULL, &attrList, NULL, NULL);
746         if (err == noErr) {
747             for (i = 0; i < attrList->count; i++) {
748                 attribute = attrList->attr[i];
749                 if (attribute.tag == kSecAccountItemAttr) {
750                     keyName = [[NSString alloc] initWithBytes:attribute.data length:attribute.length encoding:NSUTF8StringEncoding];
751                     break;
752                 }
753             }
754             if (attrList) (void)SecKeychainItemFreeAttributesAndData(attrList, NULL);
755         }
756                 CFRelease(itemRef);
757         }
758     return keyName;
759 }
760
761
762 -(NSString *)domainForHostName:(NSString *)hostNameString
763 {
764     NSString * domainName = nil;
765     char text[64];
766     char * ptr = NULL;
767     
768     ptr = (char *)[hostNameString UTF8String];
769     if (ptr) {
770         ptr = (char *)GetNextLabel(ptr, text);
771         domainName = [[NSString alloc] initWithUTF8String:(const char *)ptr];             
772     }
773     return (domainName);
774 }
775
776
777 - (NSData *)dataForDomain:(NSString *)domainName isEnabled:(BOOL)enabled
778 {
779         NSMutableArray      *domainsArray; 
780         NSMutableDictionary *domainDict = nil;
781         
782         if (domainName && [domainName length] > 0) {
783                 domainDict= [NSMutableDictionary dictionaryWithCapacity:2];
784                 [domainDict setObject:domainName forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
785                 [domainDict setObject:[NSNumber numberWithBool:enabled] forKey:(NSString *)SC_DYNDNS_ENABLED_KEY];
786         }
787         domainsArray = [NSMutableArray arrayWithCapacity:1];
788         if (domainDict) [domainsArray addObject:domainDict];
789         return [NSArchiver archivedDataWithRootObject:domainsArray];
790 }
791
792
793 - (NSData *)dataForDomainArray:(NSArray *)domainArray
794 {
795         return [NSArchiver archivedDataWithRootObject:domainArray];
796 }
797
798
799 - (NSDictionary *)dictionaryForSharedSecret:(NSString *)secret domain:(NSString *)domainName key:(NSString *)keyName
800 {
801         NSMutableDictionary *sharedSecretDict = [NSMutableDictionary dictionaryWithCapacity:3];
802         [sharedSecretDict setObject:secret forKey:(NSString *)SC_DYNDNS_SECRET_KEY];
803         [sharedSecretDict setObject:[domainName lowercaseString] forKey:(NSString *)SC_DYNDNS_DOMAIN_KEY];
804         [sharedSecretDict setObject:keyName forKey:(NSString *)SC_DYNDNS_KEYNAME_KEY];
805         return sharedSecretDict;
806 }
807
808
809 -(void)savePreferences
810 {
811     NSString      *hostNameString               = [hostName stringValue];
812     NSString      *regDomainString              = regDomainView.domain;
813     NSString      *tempHostNameSharedSecretName = hostNameSharedSecretName;
814     NSString      *tempRegSharedSecretName      = regSharedSecretName;
815     BOOL          regSecretWasSet               = NO;
816     BOOL          hostSecretWasSet              = NO;
817     BOOL          updateHostname                = NO;
818
819         hostNameString                = [self trimCharactersFromDomain:hostNameString];
820         regDomainString               = [self trimCharactersFromDomain:regDomainString];
821         tempHostNameSharedSecretName  = [self trimCharactersFromDomain:tempHostNameSharedSecretName];
822         tempRegSharedSecretName       = [self trimCharactersFromDomain:tempRegSharedSecretName];
823         
824         [hostName setStringValue:hostNameString];
825         regDomainView.domain = regDomainString;
826     
827     // Convert Shared Secret account names to lowercase.
828     tempHostNameSharedSecretName = [tempHostNameSharedSecretName lowercaseString];
829     tempRegSharedSecretName      = [tempRegSharedSecretName lowercaseString];
830     
831     // Save hostname shared secret.
832     if ([hostNameSharedSecretName length] > 0 && ([hostNameSharedSecretValue length] > 0)) {
833         DNSPrefTool_SetKeychainEntry([self dictionaryForSharedSecret:hostNameSharedSecretValue domain:hostNameString key:tempHostNameSharedSecretName]);
834         hostNameSharedSecretValue = nil;
835         hostSecretWasSet = YES;
836     }
837     
838     // Save registration domain shared secret.
839     if (([regSharedSecretName length] > 0) && ([regSharedSecretValue length] > 0)) {
840         DNSPrefTool_SetKeychainEntry([self dictionaryForSharedSecret:regSharedSecretValue domain:regDomainString key:tempRegSharedSecretName]);
841         regSharedSecretValue = nil;
842         regSecretWasSet = YES;
843     }
844
845     // Save hostname.
846     if ((currentHostName == NULL) || [currentHostName compare:hostNameString] != NSOrderedSame) {
847         currentHostName = [hostNameString copy];
848         updateHostname = YES;
849     } else if (hostSecretWasSet) {
850         currentHostName = @"";
851         updateHostname = YES;
852     }
853
854     if (updateHostname) {
855         [BonjourSCStore setObject: currentHostName.length ? @[@{
856                                                                    (NSString *)SC_DYNDNS_DOMAIN_KEY  : currentHostName,
857                                                                    (NSString *)SC_DYNDNS_ENABLED_KEY : @YES
858                                                                    }] : nil
859                            forKey: (NSString *)SC_DYNDNS_HOSTNAMES_KEY];
860     }
861     
862     // Save browse domain.
863         if (browseDomainsArray && [browseDomainsArray isEqualToArray:currentBrowseDomainsArray] == NO) {
864         [BonjourSCStore setObject: browseDomainsArray forKey: (NSString *)SC_DYNDNS_BROWSEDOMAINS_KEY];
865                 currentBrowseDomainsArray = [browseDomainsArray copy];
866     }
867         
868     // Save registration domain.
869     if ((currentRegDomain == NULL) || ([currentRegDomain compare:regDomainString] != NSOrderedSame) || (currentWideAreaState != [wideAreaCheckBox state])) {
870         [BonjourSCStore setObject: @[@{
871                                          (NSString *)SC_DYNDNS_DOMAIN_KEY  : regDomainString,
872                                          (NSString *)SC_DYNDNS_ENABLED_KEY : [wideAreaCheckBox state] ? @YES : @NO
873                                          }]
874                            forKey: (NSString *)SC_DYNDNS_REGDOMAINS_KEY];
875         currentRegDomain = [regDomainString copy];
876
877         if ([currentRegDomain length] > 0) {
878                         currentWideAreaState = [wideAreaCheckBox state];
879             [registrationDataSource removeObject:regDomainString];
880             [registrationDataSource addObject:currentRegDomain];
881             [registrationDataSource sortUsingFunction:MyArrayCompareFunction context:nil];
882  //           [regDomainsComboBox reloadData];
883         } else {
884                         currentWideAreaState = NO;
885                         [self toggleWideAreaBonjour:NO];
886             if (defaultRegDomain != nil) regDomainView.domain = defaultRegDomain;
887                 }
888     } else if (regSecretWasSet) {
889         [BonjourSCStore setObject: @[@{
890                                          (NSString *)SC_DYNDNS_DOMAIN_KEY  : @"",
891                                          (NSString *)SC_DYNDNS_ENABLED_KEY : @NO
892                                          }]
893                            forKey: (NSString *)SC_DYNDNS_REGDOMAINS_KEY];
894         if ([currentRegDomain length] > 0) {
895             [BonjourSCStore setObject: @[@{
896                                              (NSString *)SC_DYNDNS_DOMAIN_KEY  : currentRegDomain,
897                                              (NSString *)SC_DYNDNS_ENABLED_KEY : currentWideAreaState ? @YES : @NO
898                                              }]
899                                forKey: (NSString *)SC_DYNDNS_REGDOMAINS_KEY];
900         }
901     }
902 }   
903
904
905 - (NSPreferencePaneUnselectReply)shouldUnselect
906 {
907 #if 1
908     if (prefsNeedUpdating == YES) {
909     
910         [self disableControls];
911         
912         NSBeginAlertSheet(
913                     @"Apply Configuration Changes?",
914                     @"Apply",
915                     @"Don't Apply",
916                     @"Cancel",
917                     mainWindow,
918                     self,
919                     @selector( savePanelWillClose:returnCode:contextInfo: ),
920                     NULL,
921                     (__bridge void *) self, // sender,
922                     @"" );
923         return NSUnselectLater;
924     }
925 #endif
926     
927     return NSUnselectNow;
928 }
929
930
931 -(void)disableControls
932 {
933     [hostName setEnabled:NO];
934     [hostNameSharedSecretButton setEnabled:NO];
935     [applyButton setEnabled:NO];
936     [revertButton setEnabled:NO];
937     [wideAreaCheckBox setEnabled:NO];
938         [registrationSelectButton setEnabled: NO];
939     [registrationSharedSecretButton setEnabled:NO];
940     [statusImageView setEnabled:NO];
941         
942         browseDomainListEnabled = NO;
943         [browseDomainList deselectAll:self];
944         [browseDomainList setEnabled:NO];
945         
946         [addBrowseDomainButton setEnabled:NO];
947         [removeBrowseDomainButton setEnabled:NO];
948 }
949
950
951 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
952 {
953         (void)row; // Unused
954         (void)tableView; // Unused
955         return browseDomainListEnabled;
956 }
957
958
959 -(void)enableControls
960 {
961     [hostName setEnabled:YES];
962     [hostNameSharedSecretButton setEnabled:YES];
963     [wideAreaCheckBox setEnabled:YES];
964         [registrationSelectButton setEnabled: YES];
965     [registrationSharedSecretButton setEnabled:YES];
966     [self toggleWideAreaBonjour:[wideAreaCheckBox state]];
967     [statusImageView setEnabled:YES];
968         [addBrowseDomainButton setEnabled:YES];
969
970         [browseDomainList setEnabled:YES];
971         [browseDomainList deselectAll:self];
972         browseDomainListEnabled = YES;
973
974         [removeBrowseDomainButton setEnabled:[browseDomainList numberOfSelectedRows]];
975         [applyButton setEnabled:prefsNeedUpdating];
976         [revertButton setEnabled:prefsNeedUpdating];
977 }
978
979
980 - (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view
981 {
982     (void)view; //  unused
983     [self enableControls];
984 }
985
986
987 - (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view
988 {    
989     (void)view; //  unused
990     [self disableControls];
991 }
992
993 @end
994
995
996 // Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion
997 // e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4"
998 // To expand "version" to its value before making the string, use STRINGIFY(version) instead
999 #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) #s
1000 #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)
1001
1002 // NOT static -- otherwise the compiler may optimize it out
1003 // The "@(#) " pattern is a special prefix the "what" command looks for
1004 const char VersionString_SCCS[] = "@(#) Bonjour Preference Pane " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
1005
1006 #if _BUILDING_XCODE_PROJECT_
1007 // If the process crashes, then this string will be magically included in the automatically-generated crash log
1008 const char *__crashreporter_info__ = VersionString_SCCS + 5;
1009 asm(".desc ___crashreporter_info__, 0x10");
1010 #endif