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