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