1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 #import <Cocoa/Cocoa.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
22 #include <arpa/inet.h>
24 #include <sys/select.h>
25 #include <netinet/in.h>
29 @class ServiceController; // holds state corresponding to outstanding DNSServiceRef
31 @interface BrowserController : NSObject
33 IBOutlet id nameField;
34 IBOutlet id typeField;
36 IBOutlet id serviceDisplayTable;
37 IBOutlet id typeColumn;
38 IBOutlet id nameColumn;
39 IBOutlet id serviceTypeField;
40 IBOutlet id serviceNameField;
42 IBOutlet id hostField;
43 IBOutlet id ipAddressField;
44 IBOutlet id ip6AddressField;
45 IBOutlet id portField;
46 IBOutlet id interfaceField;
47 IBOutlet id textField;
49 NSMutableArray *_srvtypeKeys;
50 NSMutableArray *_srvnameKeys;
51 NSMutableArray *_sortedServices;
52 NSMutableDictionary *_servicesDict;
54 ServiceController *_serviceBrowser;
55 ServiceController *_serviceResolver;
56 ServiceController *_ipv4AddressResolver;
57 ServiceController *_ipv6AddressResolver;
60 - (void)notifyTypeSelectionChange:(NSNotification*)note;
61 - (void)notifyNameSelectionChange:(NSNotification*)note;
63 - (IBAction)connect:(id)sender;
65 - (IBAction)handleTableClick:(id)sender;
66 - (IBAction)removeSelected:(id)sender;
67 - (IBAction)addNewService:(id)sender;
69 - (IBAction)update:(NSString *)Type;
71 - (void)updateBrowseWithName:(const char *)name type:(const char *)resulttype domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags;
72 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen;
73 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen host:(const char*)host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome;
75 - (void)_cancelPendingResolve;
76 - (void)_clearResolvedInfo;
80 // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
81 @interface ServiceController : NSObject
83 DNSServiceRef fServiceRef;
84 CFSocketRef fSocketRef;
85 CFRunLoopSourceRef fRunloopSrc;
88 - (id)initWithServiceRef:(DNSServiceRef)ref;
89 - (void)addToCurrentRunLoop;
90 - (DNSServiceRef)serviceRef;
93 @end // interface ServiceController
97 ProcessSockData(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
99 DNSServiceRef serviceRef = (DNSServiceRef)info;
100 DNSServiceErrorType err = DNSServiceProcessResult(serviceRef);
101 if (err != kDNSServiceErr_NoError) {
102 printf("DNSServiceProcessResult() returned an error! %d\n", err);
108 ServiceBrowseReply(DNSServiceRef sdRef, DNSServiceFlags servFlags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
109 const char *serviceName, const char *regtype, const char *replyDomain, void *context)
111 if (errorCode == kDNSServiceErr_NoError) {
112 [(BrowserController*)context updateBrowseWithName:serviceName type:regtype domain:replyDomain interface:interfaceIndex flags:servFlags];
114 printf("ServiceBrowseReply got an error! %d\n", errorCode);
120 ServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
121 const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
123 if (errorCode == kDNSServiceErr_NoError) {
124 [(BrowserController*)context resolveClientWitHost:[NSString stringWithUTF8String:hosttarget] port:port interfaceIndex:interfaceIndex txtRecord:txtRecord txtLen:txtLen];
126 printf("ServiceResolveReply got an error! %d\n", errorCode);
132 QueryRecordReply(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
133 const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
135 if (errorCode == kDNSServiceErr_NoError) {
136 [(BrowserController*)context updateAddress:rrtype addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex more:(flags & kDNSServiceFlagsMoreComing)];
138 printf("QueryRecordReply got an error! %d\n", errorCode);
144 InterfaceIndexToName(uint32_t interface, char *interfaceName)
146 assert(interfaceName);
148 if (interface == kDNSServiceInterfaceIndexAny) {
149 // All active network interfaces.
150 strlcpy(interfaceName, "all", IF_NAMESIZE);
151 } else if (interface == kDNSServiceInterfaceIndexLocalOnly) {
152 // Only available locally on this machine.
153 strlcpy(interfaceName, "local", IF_NAMESIZE);
154 } else if (interface == kDNSServiceInterfaceIndexP2P) {
156 strlcpy(interfaceName, "p2p", IF_NAMESIZE);
158 // Converts interface index to interface name.
159 if_indextoname(interface, interfaceName);
164 @implementation BrowserController //Begin implementation of BrowserController methods
166 - (void)registerDefaults
168 NSMutableDictionary *regDict = [NSMutableDictionary dictionary];
170 NSArray *typeArray = [NSArray arrayWithObjects:@"_afpovertcp._tcp",
184 NSArray *nameArray = [NSArray arrayWithObjects:@"AppleShare Servers",
192 @"AirPort Base Stations",
198 [regDict setObject:typeArray forKey:@"SrvTypeKeys"];
199 [regDict setObject:nameArray forKey:@"SrvNameKeys"];
201 [[NSUserDefaults standardUserDefaults] registerDefaults:regDict];
211 _serviceBrowser = nil;
212 _serviceResolver = nil;
213 _ipv4AddressResolver = nil;
214 _ipv6AddressResolver = nil;
215 _sortedServices = [[NSMutableArray alloc] init];
216 _servicesDict = [[NSMutableDictionary alloc] init];
224 [typeField sizeLastColumnToFit];
225 [nameField sizeLastColumnToFit];
226 [nameField setDoubleAction:@selector(connect:)];
228 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyTypeSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:typeField];
229 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyNameSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:nameField];
231 _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
232 _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
234 if (!_srvtypeKeys || !_srvnameKeys) {
235 [_srvtypeKeys release];
236 [_srvnameKeys release];
237 [self registerDefaults];
238 _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
239 _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
242 [typeField reloadData];
248 [_srvtypeKeys release];
249 [_srvnameKeys release];
250 [_servicesDict release];
251 [_sortedServices release];
256 -(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
262 - (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods
264 if (theTableView == typeField) {
265 return [_srvnameKeys count];
267 if (theTableView == nameField) {
268 return [_servicesDict count];
270 if (theTableView == serviceDisplayTable) {
271 return [_srvnameKeys count];
277 - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex
279 if (theTableView == typeField) {
280 return [_srvnameKeys objectAtIndex:rowIndex];
282 if (theTableView == nameField) {
283 return [[_servicesDict objectForKey:[_sortedServices objectAtIndex:rowIndex]] name];
285 if (theTableView == serviceDisplayTable) {
286 if (theColumn == typeColumn) {
287 return [_srvtypeKeys objectAtIndex:rowIndex];
289 if (theColumn == nameColumn) {
290 return [_srvnameKeys objectAtIndex:rowIndex];
299 - (void)notifyTypeSelectionChange:(NSNotification*)note
301 [self _cancelPendingResolve];
303 int index = [[note object] selectedRow];
305 [self update:[_srvtypeKeys objectAtIndex:index]];
312 - (void)notifyNameSelectionChange:(NSNotification*)note
314 [self _cancelPendingResolve];
316 int index = [[note object] selectedRow];
321 // Get the currently selected service
322 NSNetService *service = [_servicesDict objectForKey:[_sortedServices objectAtIndex:index]];
324 DNSServiceRef serviceRef;
325 DNSServiceErrorType err = DNSServiceResolve(&serviceRef,
327 kDNSServiceInterfaceIndexAny,
328 (const char *)[[service name] UTF8String],
329 (const char *)[[service type] UTF8String],
330 (const char *)[[service domain] UTF8String],
331 (DNSServiceResolveReply)ServiceResolveReply,
334 if (kDNSServiceErr_NoError == err) {
335 _serviceResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
336 [_serviceResolver addToCurrentRunLoop];
341 - (IBAction)update:(NSString *)theType
343 [_servicesDict removeAllObjects];
344 [_sortedServices removeAllObjects];
345 [nameField reloadData];
347 // get rid of the previous browser if one exists
348 if (_serviceBrowser != nil) {
349 [_serviceBrowser release];
350 _serviceBrowser = nil;
354 DNSServiceRef serviceRef;
355 DNSServiceErrorType err = DNSServiceBrowse(&serviceRef, (DNSServiceFlags)0, 0, [theType UTF8String], NULL, ServiceBrowseReply, self);
356 if (kDNSServiceErr_NoError == err) {
357 _serviceBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef];
358 [_serviceBrowser addToCurrentRunLoop];
364 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
370 - (void)updateBrowseWithName:(const char *)name type:(const char *)type domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags
372 NSString *key = [NSString stringWithFormat:@"%s.%s%s%d", name, type, domain, interface];
373 NSNetService *service = [[NSNetService alloc] initWithDomain:[NSString stringWithUTF8String:domain] type:[NSString stringWithUTF8String:type] name:[NSString stringWithUTF8String:name]];
375 if (flags & kDNSServiceFlagsAdd) {
376 [_servicesDict setObject:service forKey:key];
378 [_servicesDict removeObjectForKey:key];
381 // If not expecting any more data, then reload (redraw) TableView with newly found data
382 if (!(flags & kDNSServiceFlagsMoreComing)) {
384 // Save the current TableView selection
385 int index = [nameField selectedRow];
386 NSString *selected = (index != -1) ? [[_sortedServices objectAtIndex:index] copy] : nil;
388 [_sortedServices release];
389 _sortedServices = [[_servicesDict allKeys] mutableCopy];
390 [_sortedServices sortUsingSelector:@selector(caseInsensitiveCompare:)];
391 [nameField reloadData];
393 // Restore the previous TableView selection
394 index = selected ? [_sortedServices indexOfObject:selected] : NSNotFound;
395 if (index != NSNotFound) {
396 [nameField selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
397 [nameField scrollRowToVisible:index];
409 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen
411 DNSServiceRef serviceRef;
413 if (_ipv4AddressResolver) {
414 [_ipv4AddressResolver release];
415 _ipv4AddressResolver = nil;
418 if (_ipv6AddressResolver) {
419 [_ipv6AddressResolver release];
420 _ipv6AddressResolver = nil;
423 // Start an async lookup for IPv4 addresses
424 DNSServiceErrorType err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_A, kDNSServiceClass_IN, QueryRecordReply, self);
425 if (err == kDNSServiceErr_NoError) {
426 _ipv4AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
427 [_ipv4AddressResolver addToCurrentRunLoop];
430 // Start an async lookup for IPv6 addresses
431 err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_AAAA, kDNSServiceClass_IN, QueryRecordReply, self);
432 if (err == kDNSServiceErr_NoError) {
433 _ipv6AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
434 [_ipv6AddressResolver addToCurrentRunLoop];
437 char interfaceName[IF_NAMESIZE];
438 InterfaceIndexToName(interface, interfaceName);
440 [hostField setStringValue:host];
441 [interfaceField setStringValue:[NSString stringWithUTF8String:interfaceName]];
442 [portField setIntValue:ntohs(port)];
444 // kind of a hack: munge txtRecord so it's human-readable
446 char *readableText = (char*) malloc(txtLen);
447 if (readableText != nil) {
448 ByteCount index, subStrLen;
449 memcpy(readableText, txtRecord, txtLen);
450 for (index=0; index < txtLen - 1; index += subStrLen + 1) {
451 subStrLen = readableText[index];
452 readableText[index] = ' ';
454 [textField setStringValue:[NSString stringWithCString:&readableText[1] length:txtLen - 1]];
461 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen host:(const char*) host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome
465 if (rrtype == kDNSServiceType_A) {
466 inet_ntop(AF_INET, buff, addrBuff, sizeof(addrBuff));
467 if ([[ipAddressField stringValue] length] > 0) {
468 [ipAddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ipAddressField stringValue]]];
470 [ipAddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ipAddressField stringValue], addrBuff]];
473 [_ipv4AddressResolver release];
474 _ipv4AddressResolver = nil;
476 } else if (rrtype == kDNSServiceType_AAAA) {
477 inet_ntop(AF_INET6, buff, addrBuff, sizeof(addrBuff));
478 if ([[ip6AddressField stringValue] length] > 0) {
479 [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ip6AddressField stringValue]]];
481 [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ip6AddressField stringValue], addrBuff]];
484 [_ipv6AddressResolver release];
485 _ipv6AddressResolver = nil;
491 - (void)connect:(id)sender
493 NSString *host = [hostField stringValue];
494 NSString *txtRecord = [textField stringValue];
495 int port = [portField intValue];
497 int index = [nameField selectedRow];
498 NSString *selected = (index >= 0) ? [_sortedServices objectAtIndex:index] : nil;
499 NSString *type = [[_servicesDict objectForKey:selected] type];
501 if ([type isEqual:@"_http._tcp."]) {
502 NSString *pathDelim = @"path=";
505 // If the TXT record specifies a path, extract it.
506 where = [txtRecord rangeOfString:pathDelim options:NSCaseInsensitiveSearch];
508 NSRange targetRange = { where.location + where.length, [txtRecord length] - where.location - where.length };
509 NSRange endDelim = [txtRecord rangeOfString:@"\n" options:kNilOptions range:targetRange];
511 if (endDelim.length) // if a delimiter was found, truncate the target range
512 targetRange.length = endDelim.location - targetRange.location;
514 NSString *path = [txtRecord substringWithRange:targetRange];
515 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d%@", host, port, path]]];
517 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", host, port]]];
520 else if ([type isEqual:@"_ftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", host, port]]];
521 else if ([type isEqual:@"_ssh._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", host, port]]];
522 else if ([type isEqual:@"_afpovertcp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", host, port]]];
523 else if ([type isEqual:@"_smb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", host, port]]];
524 else if ([type isEqual:@"_rfb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"vnc://%@:%d/", host, port]]];
530 - (IBAction)handleTableClick:(id)sender
532 //populate the text fields
536 - (IBAction)removeSelected:(id)sender
538 // remove the selected row and force a refresh
540 int selectedRow = [serviceDisplayTable selectedRow];
544 [_srvtypeKeys removeObjectAtIndex:selectedRow];
545 [_srvnameKeys removeObjectAtIndex:selectedRow];
547 [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
548 [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
550 [typeField reloadData];
551 [serviceDisplayTable reloadData];
556 - (IBAction)addNewService:(id)sender
558 // add new entries from the edit fields to the arrays for the defaults
559 NSString *newType = [serviceTypeField stringValue];
560 NSString *newName = [serviceNameField stringValue];
562 // 3282283: trim trailing '.' from service type field
563 if ([newType length] && [newType hasSuffix:@"."])
564 newType = [newType substringToIndex:[newType length] - 1];
566 if ([newType length] && [newName length]) {
567 [_srvtypeKeys addObject:newType];
568 [_srvnameKeys addObject:newName];
570 [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
571 [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
573 [typeField reloadData];
574 [serviceDisplayTable reloadData];
579 - (void)_cancelPendingResolve
581 [_ipv4AddressResolver release];
582 _ipv4AddressResolver = nil;
584 [_ipv6AddressResolver release];
585 _ipv6AddressResolver = nil;
587 [_serviceResolver release];
588 _serviceResolver = nil;
590 [self _clearResolvedInfo];
594 - (void)_clearResolvedInfo
596 [hostField setStringValue:@""];
597 [ipAddressField setStringValue:@""];
598 [ip6AddressField setStringValue:@""];
599 [portField setStringValue:@""];
600 [interfaceField setStringValue:@""];
601 [textField setStringValue:@""];
604 @end // implementation BrowserController
607 @implementation ServiceController : NSObject
609 DNSServiceRef fServiceRef;
610 CFSocketRef fSocketRef;
611 CFRunLoopSourceRef fRunloopSrc;
615 - (id)initWithServiceRef:(DNSServiceRef)ref
627 - (void)addToCurrentRunLoop
629 CFSocketContext context = { 0, (void*)fServiceRef, NULL, NULL, NULL };
631 fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef), kCFSocketReadCallBack, ProcessSockData, &context);
633 // Prevent CFSocketInvalidate from closing DNSServiceRef's socket.
634 CFOptionFlags sockFlags = CFSocketGetSocketFlags(fSocketRef);
635 CFSocketSetSocketFlags(fSocketRef, sockFlags & (~kCFSocketCloseOnInvalidate));
636 fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 0);
639 CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
641 printf("Could not listen to runloop socket\n");
646 - (DNSServiceRef)serviceRef
655 CFSocketInvalidate(fSocketRef); // Note: Also closes the underlying socket
656 CFRelease(fSocketRef);
658 // Workaround that gives time to CFSocket's select thread so it can remove the socket from its
659 // FD set before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273>
664 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
665 CFRelease(fRunloopSrc);
668 DNSServiceRefDeallocate(fServiceRef);
674 @end // implementation ServiceController
676 int main(int argc, const char *argv[])
678 return NSApplicationMain(argc, argv);