]> git.saurik.com Git - apple/mdnsresponder.git/blob - Clients/DNSServiceBrowser.m
mDNSResponder-87.tar.gz
[apple/mdnsresponder.git] / Clients / DNSServiceBrowser.m
1 /*
2 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22
23 Change History (most recent first):
24
25 $Log: DNSServiceBrowser.m,v $
26 Revision 1.29 2004/06/04 20:58:36 cheshire
27 Move DNSServiceBrowser from mDNSMacOSX directory to Clients directory
28
29 Revision 1.28 2004/05/18 23:51:26 cheshire
30 Tidy up all checkin comments to use consistent "<rdar://problem/xxxxxxx>" format for bug numbers
31
32 Revision 1.27 2003/11/19 18:49:48 rpantos
33 <rdar://problem/3282283>: couple of little tweaks to previous checkin
34
35 Revision 1.26 2003/11/07 19:35:20 rpantos
36 <rdar://problem/3282283>/6: Display multiple IP addresses. Connect using host rather than IP addr.
37
38 Revision 1.25 2003/10/29 05:16:54 rpantos
39 Checkpoint: transition from DNSServiceDiscovery.h to dns_sd.h
40
41 Revision 1.24 2003/10/28 02:25:45 rpantos
42 <rdar://problem/3282283>/9,10: Cancel pending resolve when focus changes or service disappears.
43
44 Revision 1.23 2003/10/28 01:29:15 rpantos
45 <rdar://problem/3282283>/4,5: Restructure a bit to make arrow keys work & views behave better.
46
47 Revision 1.22 2003/10/28 01:23:27 rpantos
48 <rdar://problem/3282283>/11: Bail if mDNS cannot be initialized at startup.
49
50 Revision 1.21 2003/10/28 01:19:45 rpantos
51 <rdar://problem/3282283>/3,11: Do not put a trailing '.' on service names. Handle PATH for HTTP txtRecords.
52
53 Revision 1.20 2003/10/28 01:13:49 rpantos
54 <rdar://problem/3282283>/2: Remove filter when displaying browse results.
55
56 Revision 1.19 2003/10/28 01:10:14 rpantos
57 <rdar://problem/3282283>/1: Change 'compare' to 'caseInsensitiveCompare' to fix sort order.
58
59 Revision 1.18 2003/08/12 19:55:07 cheshire
60 Update to APSL 2.0
61
62 */
63
64 #include <arpa/inet.h>
65 #include <netdb.h>
66 #include <nameser.h>
67 #include <sys/select.h>
68
69 #import <Cocoa/Cocoa.h>
70 #import <DNSServiceDiscovery/DNSServiceDiscovery.h>
71
72 #include <netinet/in.h>
73 #include "dns_sd.h"
74
75 @class ServiceController; // holds state corresponding to outstanding DNSServiceRef
76
77 @interface BrowserController : NSObject
78 {
79 IBOutlet id domainField;
80 IBOutlet id nameField;
81 IBOutlet id typeField;
82
83 IBOutlet id serviceDisplayTable;
84 IBOutlet id typeColumn;
85 IBOutlet id nameColumn;
86 IBOutlet id serviceTypeField;
87 IBOutlet id serviceNameField;
88
89 IBOutlet id hostField;
90 IBOutlet id ipAddressField;
91 IBOutlet id ip6AddressField;
92 IBOutlet id portField;
93 IBOutlet id textField;
94
95 NSMutableArray *srvtypeKeys;
96 NSMutableArray *srvnameKeys;
97 NSMutableArray *domainKeys;
98 NSMutableArray *nameKeys;
99 NSString *Domain;
100 NSString *SrvType;
101 NSString *SrvName;
102 NSString *Name;
103
104 ServiceController *fDomainBrowser;
105 ServiceController *fServiceBrowser;
106 ServiceController *fServiceResolver;
107 ServiceController *fAddressResolver;
108
109 }
110
111 - (IBAction)handleDomainClick:(id)sender;
112 - (IBAction)handleNameClick:(id)sender;
113 - (IBAction)handleTypeClick:(id)sender;
114 - (void)notifyTypeSelectionChange:(NSNotification*)note;
115 - (void)notifyNameSelectionChange:(NSNotification*)note;
116
117 - (IBAction)connect:(id)sender;
118
119 - (IBAction)handleTableClick:(id)sender;
120 - (IBAction)removeSelected:(id)sender;
121 - (IBAction)addNewService:(id)sender;
122
123 - (IBAction)update:(NSString *)Type Domain:(NSString *)Domain;
124 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication;
125 - (IBAction)loadDomains:(id)sender;
126
127 - (void)updateBrowseWithResult:(DNSServiceFlags)flags name:(NSString *)name type:(NSString *)resulttype domain:(NSString *)domain;
128 - (void)updateEnumWithResult:(DNSServiceFlags)flags domain:(NSString *)domain;
129 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen;
130 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen
131 host:(const char*) host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome;
132
133 - (void)_cancelPendingResolve;
134 - (void)_clearResolvedInfo;
135
136 @end
137
138 // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
139 @interface ServiceController : NSObject
140 {
141 DNSServiceRef fServiceRef;
142 CFSocketRef fSocketRef;
143 CFRunLoopSourceRef fRunloopSrc;
144 }
145
146 - (id) initWithServiceRef:(DNSServiceRef) ref;
147 - (boolean_t) addToCurrentRunLoop;
148 - (DNSServiceRef) serviceRef;
149 - (void) dealloc;
150
151 @end // interface ServiceController
152
153
154 static void ProcessSockData( CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
155 // CFRunloop callback that notifies dns_sd when new data appears on a DNSServiceRef's socket.
156 {
157 DNSServiceRef serviceRef = (DNSServiceRef) info;
158 DNSServiceErrorType err = DNSServiceProcessResult( serviceRef);
159 if ( err != kDNSServiceErr_NoError)
160 printf( "DNSServiceProcessResult() returned an error! %d\n", err);
161 }
162
163 static void DomainEnumReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
164 DNSServiceErrorType errorCode, const char *replyDomain, void *context )
165 // Report newly-discovered domains to the BrowserController.
166 {
167 if ( errorCode == kDNSServiceErr_NoError) {
168 BrowserController *pSelf = (BrowserController*) context;
169 [pSelf updateEnumWithResult:flags domain:[NSString stringWithUTF8String:replyDomain]];
170 } else {
171 printf( "DomainEnumReply got an error! %d\n", errorCode);
172 }
173 }
174
175 static void ServiceBrowseReply( DNSServiceRef sdRef, DNSServiceFlags servFlags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
176 const char *serviceName, const char *regtype, const char *replyDomain, void *context )
177 // Report newly-discovered services to the BrowserController.
178 {
179 if ( errorCode == kDNSServiceErr_NoError) {
180 BrowserController *pSelf = (BrowserController*) context;
181 [pSelf updateBrowseWithResult:servFlags name:[NSString stringWithUTF8String:serviceName]
182 type:[NSString stringWithUTF8String:regtype] domain:[NSString stringWithUTF8String:replyDomain]];
183 } else {
184 printf( "ServiceBrowseReply got an error! %d\n", errorCode);
185 }
186 }
187
188 static void ServiceResolveReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
189 DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port,
190 uint16_t txtLen, const char *txtRecord, void *context )
191 // Pass along resolved service info to the BrowserController.
192 {
193 if ( errorCode == kDNSServiceErr_NoError) {
194 BrowserController *pSelf = (BrowserController*) context;
195 [pSelf resolveClientWitHost:[NSString stringWithUTF8String:hosttarget] port:port interfaceIndex:interfaceIndex txtRecord:txtRecord txtLen:txtLen];
196 } else {
197 printf( "ServiceResolveReply got an error! %d\n", errorCode);
198 }
199 }
200
201 static void QueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex,
202 DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
203 uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )
204 // DNSServiceQueryRecord callback used to look up IP addresses.
205 {
206 BrowserController *pBrowser = (BrowserController*) context;
207
208 [pBrowser updateAddress:rrtype addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex
209 more:((flags & kDNSServiceFlagsMoreComing) != 0)];
210 }
211
212
213 @implementation BrowserController //Begin implementation of BrowserController methods
214
215 - (void)registerDefaults
216 {
217 NSMutableDictionary *regDict = [NSMutableDictionary dictionary];
218
219 NSArray *typeArray = [NSArray arrayWithObjects:@"_ftp._tcp", @"_tftp._tcp",
220 @"_ssh._tcp", @"_telnet._tcp",
221 @"_http._tcp",
222 @"_printer._tcp", @"_ipp._tcp",
223 @"_ichat._tcp", @"_eppc._tcp",
224 @"_afpovertcp._tcp", @"_afpovertcp._tcp", @"_MacOSXDupSuppress._tcp", nil];
225 NSArray *nameArray = [NSArray arrayWithObjects:@"File Transfer (ftp)", @"Trivial File Transfer (tftp)",
226 @"Secure Shell (ssh)", @"Telnet",
227 @"Web Server (http)",
228 @"LPR Printer", @"IPP Printer",
229 @"iChat", @"Remote AppleEvents",
230 @"AppleShare Server", @"SMB File Server", @"Mystery Service", nil];
231
232 [regDict setObject:typeArray forKey:@"SrvTypeKeys"];
233 [regDict setObject:nameArray forKey:@"SrvNameKeys"];
234
235 [[NSUserDefaults standardUserDefaults] registerDefaults:regDict];
236 }
237
238
239 - (id)init
240 {
241 [self registerDefaults];
242
243 fDomainBrowser = nil;
244 fServiceBrowser = nil;
245 fServiceResolver = nil;
246 fAddressResolver = nil;
247
248 return [super init];
249 }
250
251 - (void)awakeFromNib //BrowserController startup procedure
252 {
253 SrvType=NULL;
254 Domain=NULL;
255 srvtypeKeys = [NSMutableArray array]; //Define arrays for Type, Domain, and Name
256 srvnameKeys = [NSMutableArray array];
257
258 domainKeys = [NSMutableArray array];
259 [domainKeys retain];
260
261 nameKeys = [NSMutableArray array];
262 [nameKeys retain];
263
264 [srvtypeKeys retain]; //Keep arrays in memory until BrowserController closes
265 [srvnameKeys retain]; //Keep arrays in memory until BrowserController closes
266 [typeField sizeLastColumnToFit]; //Set column sizes to use their whole table's width.
267 [nameField sizeLastColumnToFit];
268 [domainField sizeLastColumnToFit];
269 // (self is specified as the NSTableViews' data source in the nib)
270
271 [nameField setDoubleAction:@selector(connect:)];
272
273 // Listen for table selection changes
274 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyTypeSelectionChange:)
275 name:NSTableViewSelectionDidChangeNotification object:typeField];
276 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyNameSelectionChange:)
277 name:NSTableViewSelectionDidChangeNotification object:nameField];
278
279 //[srvtypeKeys addObject:@"_ftp._tcp"]; //Add supported protocols and domains to their
280 //[srvnameKeys addObject:@"File Transfer (ftp)"];
281 //[srvtypeKeys addObject:@"_printer._tcp"]; //respective arrays
282 //[srvnameKeys addObject:@"Printer (lpr)"];
283 //[srvtypeKeys addObject:@"_http._tcp"]; //respective arrays
284 //[srvnameKeys addObject:@"Web Server (http)"];
285 //[srvtypeKeys addObject:@"_afp._tcp"]; //respective arrays
286 //[srvnameKeys addObject:@"AppleShare Server (afp)"];
287
288 [self _clearResolvedInfo];
289
290 [srvtypeKeys addObjectsFromArray:[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"]];
291 [srvnameKeys addObjectsFromArray:[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"]];
292
293
294 [typeField reloadData]; //Reload (redraw) data in fields
295 [domainField reloadData];
296
297 [self loadDomains:self];
298
299 }
300
301 - (void)dealloc //Deallocation method
302 {
303 [srvtypeKeys release];
304 [srvnameKeys release];
305 [nameKeys release];
306 [domainKeys release];
307 }
308
309 -(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
310 {
311 if (row<0) return;
312 }
313
314 - (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods
315 {
316 if (theTableView == typeField)
317 {
318 return [srvnameKeys count];
319 }
320 if (theTableView == domainField)
321 {
322 return [domainKeys count];
323 }
324 if (theTableView == nameField)
325 {
326 return [nameKeys count];
327 }
328 if (theTableView == serviceDisplayTable)
329 {
330 return [srvnameKeys count];
331 }
332 return 0;
333 }
334
335 - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex
336 {
337 if (theTableView == typeField)
338 {
339 return [srvnameKeys objectAtIndex:rowIndex];
340 }
341 if (theTableView == domainField)
342 {
343 return [domainKeys objectAtIndex:rowIndex];
344 }
345 if (theTableView == nameField)
346 {
347 return [[nameKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:rowIndex];
348 }
349 if (theTableView == serviceDisplayTable)
350 {
351 if (theColumn == typeColumn) {
352 return [srvtypeKeys objectAtIndex:rowIndex];
353 }
354 if (theColumn == nameColumn) {
355 return [srvnameKeys objectAtIndex:rowIndex];
356 }
357 return 0;
358 }
359 else
360 return(0);
361 } //End of mandatory TableView methods
362
363 - (IBAction)handleTypeClick:(id)sender //Handle clicks for Type
364 {
365 // 3282283: No longer used - update happens in notifyTypeSelectionChange
366 }
367
368
369 - (IBAction)handleDomainClick:(id)sender //Handle clicks for Domain
370 {
371 int index=[sender selectedRow]; //Find index of selected row
372 if (index==-1) return; //Error checking
373 Domain = [domainKeys objectAtIndex:index]; //Save desired Domain
374
375 [self _cancelPendingResolve];
376
377 if (SrvType!=NULL) [self update:SrvType Domain:Domain]; //If Type and Domain are set, update records
378 }
379
380 - (IBAction)handleNameClick:(id)sender //Handle clicks for Name
381 {
382 // 3282283: No longer used - update happens in notifyNameSelectionChange
383 }
384
385 - (void)notifyTypeSelectionChange:(NSNotification*)note
386 /* Called when the selection of the Type table changes */
387 {
388 int index=[[note object] selectedRow]; //Find index of selected row
389 if (index==-1) return; //Error checking
390 SrvType = [srvtypeKeys objectAtIndex:index]; //Save desired Type
391 SrvName = [srvnameKeys objectAtIndex:index]; //Save desired Type
392
393 [self _cancelPendingResolve];
394
395 [self update:SrvType Domain:Domain]; //If Type and Domain are set, update records
396 }
397
398 - (void)notifyNameSelectionChange:(NSNotification*)note
399 /* Called when the selection of the Name table changes */
400 {
401 int index=[[note object] selectedRow]; //Find index of selected row
402
403 [self _cancelPendingResolve]; // Cancel any pending Resolve for any table selection change
404
405 if (index==-1) {
406 Name = nil; // Name may no longer point to a list member
407 return;
408 }
409 Name=[[nameKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:index]; //Save desired name
410
411 [self _clearResolvedInfo];
412
413 DNSServiceRef serviceRef;
414 DNSServiceErrorType err;
415 err = DNSServiceResolve ( &serviceRef, (DNSServiceFlags) 0, 0, (char *)[Name UTF8String], (char *)[SrvType UTF8String],
416 (char *)(Domain?[Domain UTF8String]:""), ServiceResolveReply, self);
417 if ( kDNSServiceErr_NoError == err) {
418 fServiceResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
419 [fServiceResolver addToCurrentRunLoop];
420 }
421 }
422
423 - (IBAction)loadDomains:(id)sender
424 {
425 DNSServiceErrorType err;
426 DNSServiceRef serviceRef;
427
428 err = DNSServiceEnumerateDomains( &serviceRef, kDNSServiceFlagsBrowseDomains, 0, DomainEnumReply, self);
429 if ( kDNSServiceErr_NoError == err) {
430 fDomainBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef];
431 [fDomainBrowser addToCurrentRunLoop];
432 }
433 else {
434 NSAlert *alert = [NSAlert alertWithMessageText:@"Could not connect to mDNSResponder!"
435 defaultButton:@"Quit" alternateButton:nil otherButton:nil informativeTextWithFormat:
436 @"Check to see if mDNSResponder is still running."];
437 if ( alert != NULL)
438 [alert runModal];
439 exit( err);
440 }
441 }
442
443 - (IBAction)update:theType Domain:theDomain; //The Big Kahuna: Fetch PTR records and update application
444 {
445 const char * DomainC;
446 const char * TypeC=[theType UTF8String]; //Type in C string format
447
448 DNSServiceErrorType err = kDNSServiceErr_NoError;
449
450 if (theDomain) {
451 DomainC = [theDomain UTF8String]; //Domain in C string format
452 } else {
453 DomainC = "";
454 }
455
456 [nameKeys removeAllObjects]; //Get rid of displayed records if we're going to go get new ones
457 [nameField reloadData]; //Reload (redraw) names to show the old data is gone
458
459 // get rid of the previous browser if one exists
460 if ( fServiceBrowser != nil) {
461 [fServiceBrowser release];
462 fServiceBrowser = nil;
463 }
464
465 // now create a browser to return the values for the nameField ...
466 DNSServiceRef serviceRef;
467 err = DNSServiceBrowse( &serviceRef, (DNSServiceFlags) 0, 0, TypeC, DomainC, ServiceBrowseReply, self);
468 if ( kDNSServiceErr_NoError == err) {
469 fServiceBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef];
470 [fServiceBrowser addToCurrentRunLoop];
471 }
472 }
473
474
475 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication //Quit when main window is closed
476 {
477 return YES;
478 }
479
480 - (BOOL)windowShouldClose:(NSWindow *)sender //Save domains to our domain file when quitting
481 {
482 [domainField reloadData];
483 return YES;
484 }
485
486 - (void)updateEnumWithResult:(DNSServiceFlags)flags domain:(NSString *)domain
487 {
488 if ( ( flags & kDNSServiceFlagsAdd) != 0) { // new domain received
489 // add the domain to the list
490 [domainKeys addObject:domain];
491 } else if (!(flags & kDNSServiceFlagsAdd)) {
492 // remove the domain from the list
493 NSEnumerator *dmnEnum = [domainKeys objectEnumerator];
494 NSString *aDomain = nil;
495
496 while (aDomain = [dmnEnum nextObject]) {
497 if ([aDomain isEqualToString:domain]) {
498 [domainKeys removeObject:domain];
499 break;
500 }
501 }
502 }
503 // update the domain table
504 [domainField reloadData];
505
506 // Terminate the enumeration once the last domain is delivered.
507 if ( ( flags & kDNSServiceFlagsMoreComing) == 0) {
508 [fDomainBrowser release];
509 fDomainBrowser = nil;
510 }
511
512 // At some point, we may want to support a TableView for domain browsing. For now, just pick first domain that comes up.
513 if ( Domain == nil)
514 Domain = [domain retain];
515
516 return;
517 }
518
519
520
521 - (void)updateBrowseWithResult:(DNSServiceFlags)flags name:(NSString *)name type:(NSString *)resulttype domain:(NSString *)domain
522 {
523
524 //NSLog(@"Received result %@ %@ %@ %d", name, resulttype, domain, type);
525
526 if (!(flags & kDNSServiceFlagsAdd)) {
527 if ([nameKeys containsObject:name]) {
528 [nameKeys removeObject:name];
529
530 // 3282283: Cancel pending browse if object goes away.
531 if ( [name isEqualToString:Name])
532 [nameField deselectAll:self];
533 }
534 }
535 else if ( ( flags & kDNSServiceFlagsAdd) != 0) {
536 if (![nameKeys containsObject:name]) {
537 [nameKeys addObject:name];
538 }
539 }
540
541 // If not expecting any more data, then reload (redraw) Name TableView with newly found data
542 if ((flags & kDNSServiceFlagsMoreComing) == 0)
543 [nameField reloadData];
544 return;
545 }
546
547 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface
548 txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen
549 /* Display resolved information about the selected service. */
550 {
551 DNSServiceErrorType err;
552 DNSServiceRef serviceRef;
553
554 // Start an async lookup for IPv4 & IPv6 addresses
555 if ( fAddressResolver != nil) {
556 [fAddressResolver release];
557 fAddressResolver = nil;
558 }
559 err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, [host UTF8String],
560 ns_t_a, ns_c_in, QueryRecordReply, self);
561 if ( err == kDNSServiceErr_NoError) {
562 fAddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
563 [fAddressResolver addToCurrentRunLoop];
564 }
565
566 [hostField setStringValue:host];
567 [portField setIntValue:port];
568
569 // kind of a hack: munge txtRecord so it's human-readable
570 if ( txtLen > 0) {
571 char *readableText = (char*) malloc( txtLen);
572 if ( readableText != nil) {
573 ByteCount index, subStrLen;
574 memcpy( readableText, txtRecord, txtLen);
575 for ( index=0; index < txtLen - 1; index += subStrLen + 1) {
576 subStrLen = readableText[ index];
577 readableText[ index] = '\n';
578 }
579 [textField setStringValue:[NSString stringWithCString:&readableText[1] length:txtLen - 1]];
580 free( readableText);
581 }
582 }
583 }
584
585 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen
586 host:(const char*) host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome
587 /* Update address field(s) with info obtained by fAddressResolver. */
588 {
589 if ( rrtype == ns_t_a) { // IPv4
590 char addrBuff[256];
591 inet_ntop( AF_INET, buff, addrBuff, sizeof addrBuff);
592 strcat( addrBuff, " ");
593 [ipAddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ipAddressField stringValue], addrBuff]];
594
595 if ( !moreToCome) {
596 [fAddressResolver release];
597 fAddressResolver = nil;
598
599 // After we find v4 we look for v6
600 DNSServiceRef serviceRef;
601 DNSServiceErrorType err;
602 err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, host,
603 ns_t_aaaa, ns_c_in, QueryRecordReply, self);
604 if ( err == kDNSServiceErr_NoError) {
605 fAddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
606 [fAddressResolver addToCurrentRunLoop];
607 }
608 }
609 }
610 else if ( rrtype == ns_t_aaaa) // IPv6
611 {
612 char addrBuff[256];
613 inet_ntop( AF_INET6, buff, addrBuff, sizeof addrBuff);
614 strcat( addrBuff, " ");
615 [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ip6AddressField stringValue], addrBuff]];
616
617 if ( !moreToCome) {
618 [fAddressResolver release];
619 fAddressResolver = nil;
620 }
621 }
622 }
623
624
625 - (void)connect:(id)sender
626 {
627 NSString *host = [hostField stringValue];
628 int port = [portField intValue];
629 NSString *txtRecord = [textField stringValue];
630
631 if (!txtRecord) txtRecord = @"";
632
633 if (!host || !port) return;
634
635 if ([SrvType isEqualToString:@"_http._tcp"])
636 {
637 NSString *pathDelim = @"path=";
638 NSRange where;
639
640 // If the TXT record specifies a path, extract it.
641 where = [txtRecord rangeOfString:pathDelim options:NSCaseInsensitiveSearch];
642 if ( where.length)
643 {
644 NSRange targetRange = { where.location + where.length, [txtRecord length] - where.location - where.length };
645 NSRange endDelim = [txtRecord rangeOfString:@"\n" options:kNilOptions range:targetRange];
646
647 if ( endDelim.length) // if a delimiter was found, truncate the target range
648 targetRange.length = endDelim.location - targetRange.location;
649
650 NSString *path = [txtRecord substringWithRange:targetRange];
651 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d%@", host, port, path]]];
652 }
653 else
654 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", host, port]]];
655 }
656 else if ([SrvType isEqualToString:@"_ftp._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", host, port]]];
657 else if ([SrvType isEqualToString:@"_tftp._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tftp://%@:%d/", host, port]]];
658 else if ([SrvType isEqualToString:@"_ssh._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", host, port]]];
659 else if ([SrvType isEqualToString:@"_telnet._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"telnet://%@:%d/", host, port]]];
660 else if ([SrvType isEqualToString:@"_printer._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"lpr://%@:%d/", host, port]]];
661 else if ([SrvType isEqualToString:@"_ipp._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ipp://%@:%d/", host, port]]];
662 else if ([SrvType isEqualToString:@"_afpovertcp._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", host, port]]];
663 else if ([SrvType isEqualToString:@"_smb._tcp"]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", host, port]]];
664
665 return;
666 }
667
668 - (IBAction)handleTableClick:(id)sender
669 {
670 //populate the text fields
671 }
672
673 - (IBAction)removeSelected:(id)sender
674 {
675 // remove the selected row and force a refresh
676
677 int selectedRow = [serviceDisplayTable selectedRow];
678
679 if (selectedRow) {
680
681 [srvtypeKeys removeObjectAtIndex:selectedRow];
682 [srvnameKeys removeObjectAtIndex:selectedRow];
683
684 [[NSUserDefaults standardUserDefaults] setObject:srvtypeKeys forKey:@"SrvTypeKeys"];
685 [[NSUserDefaults standardUserDefaults] setObject:srvnameKeys forKey:@"SrvNameKeys"];
686
687 [typeField reloadData];
688 [serviceDisplayTable reloadData];
689 }
690 }
691
692 - (IBAction)addNewService:(id)sender
693 {
694 // add new entries from the edit fields to the arrays for the defaults
695 NSString *newType = [serviceTypeField stringValue];
696 NSString *newName = [serviceNameField stringValue];
697
698 // 3282283: trim trailing '.' from service type field
699 if ([newType length] && [newType hasSuffix:@"."])
700 newType = [newType substringToIndex:[newType length] - 1];
701
702 if ([newType length] && [newName length]) {
703 [srvtypeKeys addObject:newType];
704 [srvnameKeys addObject:newName];
705
706 [[NSUserDefaults standardUserDefaults] setObject:srvtypeKeys forKey:@"SrvTypeKeys"];
707 [[NSUserDefaults standardUserDefaults] setObject:srvnameKeys forKey:@"SrvNameKeys"];
708
709 [typeField reloadData];
710 [serviceDisplayTable reloadData];
711 }
712 }
713
714 - (void)_cancelPendingResolve
715 // If there a a Resolve outstanding, cancel it.
716 {
717 if ( fAddressResolver != nil) {
718 [fAddressResolver release];
719 fAddressResolver = nil;
720 }
721
722 if ( fServiceResolver != nil) {
723 [fServiceResolver release];
724 fServiceResolver = nil;
725 }
726
727 [self _clearResolvedInfo];
728 }
729
730 - (void)_clearResolvedInfo
731 // Erase the display of resolved info.
732 {
733 [hostField setStringValue:@""];
734 [ipAddressField setStringValue:@""];
735 [ip6AddressField setStringValue:@""];
736 [portField setStringValue:@""];
737 [textField setStringValue:@""];
738 }
739
740 @end // implementation BrowserController
741
742
743 @implementation ServiceController : NSObject
744 {
745 DNSServiceRef fServiceRef;
746 CFSocketRef fSocketRef;
747 CFRunLoopSourceRef fRunloopSrc;
748 }
749
750 - (id) initWithServiceRef:(DNSServiceRef) ref
751 {
752 [super init];
753 fServiceRef = ref;
754 return self;
755 }
756
757 - (boolean_t) addToCurrentRunLoop
758 /* Add the service to the current runloop. Returns non-zero on success. */
759 {
760 CFSocketContext ctx = { 1, (void*) fServiceRef, nil, nil, nil };
761
762 fSocketRef = CFSocketCreateWithNative( kCFAllocatorDefault, DNSServiceRefSockFD( fServiceRef),
763 kCFSocketReadCallBack, ProcessSockData, &ctx);
764 if ( fSocketRef != nil)
765 fRunloopSrc = CFSocketCreateRunLoopSource( kCFAllocatorDefault, fSocketRef, 1);
766 if ( fRunloopSrc != nil)
767 CFRunLoopAddSource( CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
768 else
769 printf("Could not listen to runloop socket\n");
770
771 return fRunloopSrc != nil;
772 }
773
774 - (DNSServiceRef) serviceRef
775 {
776 return fServiceRef;
777 }
778
779 - (void) dealloc
780 /* Remove service from runloop, deallocate service and associated resources */
781 {
782 if ( fSocketRef != nil) {
783 CFSocketInvalidate( fSocketRef);
784 CFRelease( fSocketRef);
785 }
786
787 if ( fRunloopSrc != nil) {
788 CFRunLoopRemoveSource( CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
789 CFRelease( fRunloopSrc);
790 }
791
792 DNSServiceRefDeallocate( fServiceRef);
793
794 [super dealloc];
795 }
796
797 @end // implementation ServiceController
798
799 int main(int argc, const char *argv[])
800 {
801 return NSApplicationMain(argc, argv);
802 }