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