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