/* -*- Mode: C; tab-width: 4 -*- * * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #include #include #include #include #include #include #include #include #include @class ServiceController; // holds state corresponding to outstanding DNSServiceRef @interface BrowserController : NSObject { IBOutlet id nameField; IBOutlet id typeField; IBOutlet id serviceDisplayTable; IBOutlet id typeColumn; IBOutlet id nameColumn; IBOutlet id serviceTypeField; IBOutlet id serviceNameField; IBOutlet id hostField; IBOutlet id ipAddressField; IBOutlet id ip6AddressField; IBOutlet id portField; IBOutlet id interfaceField; IBOutlet id textField; NSMutableArray *_srvtypeKeys; NSMutableArray *_srvnameKeys; NSMutableArray *_sortedServices; NSMutableDictionary *_servicesDict; ServiceController *_serviceBrowser; ServiceController *_serviceResolver; ServiceController *_ipv4AddressResolver; ServiceController *_ipv6AddressResolver; } - (void)notifyTypeSelectionChange:(NSNotification*)note; - (void)notifyNameSelectionChange:(NSNotification*)note; - (IBAction)connect:(id)sender; - (IBAction)handleTableClick:(id)sender; - (IBAction)removeSelected:(id)sender; - (IBAction)addNewService:(id)sender; - (IBAction)update:(NSString *)Type; - (void)updateBrowseWithName:(const char *)name type:(const char *)resulttype domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags; - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen; - (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; - (void)_cancelPendingResolve; - (void)_clearResolvedInfo; @end // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request @interface ServiceController : NSObject { DNSServiceRef fServiceRef; CFSocketRef fSocketRef; CFRunLoopSourceRef fRunloopSrc; } - (id)initWithServiceRef:(DNSServiceRef)ref; - (void)addToCurrentRunLoop; - (DNSServiceRef)serviceRef; - (void)dealloc; @end // interface ServiceController static void ProcessSockData(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { DNSServiceRef serviceRef = (DNSServiceRef)info; DNSServiceErrorType err = DNSServiceProcessResult(serviceRef); if (err != kDNSServiceErr_NoError) { printf("DNSServiceProcessResult() returned an error! %d\n", err); } } static void ServiceBrowseReply(DNSServiceRef sdRef, DNSServiceFlags servFlags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { if (errorCode == kDNSServiceErr_NoError) { [(BrowserController*)context updateBrowseWithName:serviceName type:regtype domain:replyDomain interface:interfaceIndex flags:servFlags]; } else { printf("ServiceBrowseReply got an error! %d\n", errorCode); } } static void ServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context) { if (errorCode == kDNSServiceErr_NoError) { [(BrowserController*)context resolveClientWitHost:[NSString stringWithUTF8String:hosttarget] port:port interfaceIndex:interfaceIndex txtRecord:txtRecord txtLen:txtLen]; } else { printf("ServiceResolveReply got an error! %d\n", errorCode); } } static void QueryRecordReply(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { if (errorCode == kDNSServiceErr_NoError) { [(BrowserController*)context updateAddress:rrtype addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex more:(flags & kDNSServiceFlagsMoreComing)]; } else { printf("QueryRecordReply got an error! %d\n", errorCode); } } static void InterfaceIndexToName(uint32_t interface, char *interfaceName) { assert(interfaceName); if (interface == kDNSServiceInterfaceIndexAny) { // All active network interfaces. strlcpy(interfaceName, "all", IF_NAMESIZE); } else if (interface == kDNSServiceInterfaceIndexLocalOnly) { // Only available locally on this machine. strlcpy(interfaceName, "local", IF_NAMESIZE); } else if (interface == kDNSServiceInterfaceIndexP2P) { // Peer-to-peer. strlcpy(interfaceName, "p2p", IF_NAMESIZE); } else { // Converts interface index to interface name. if_indextoname(interface, interfaceName); } } @implementation BrowserController //Begin implementation of BrowserController methods - (void)registerDefaults { NSMutableDictionary *regDict = [NSMutableDictionary dictionary]; NSArray *typeArray = [NSArray arrayWithObjects:@"_afpovertcp._tcp", @"_smb._tcp", @"_rfb._tcp", @"_ssh._tcp", @"_ftp._tcp", @"_http._tcp", @"_printer._tcp", @"_ipp._tcp", @"_airport._tcp", @"_presence._tcp", @"_daap._tcp", @"_dpap._tcp", nil]; NSArray *nameArray = [NSArray arrayWithObjects:@"AppleShare Servers", @"Windows Sharing", @"Screen Sharing", @"Secure Shell", @"FTP Servers", @"Web Servers", @"LPR Printers", @"IPP Printers", @"AirPort Base Stations", @"iChat Buddies", @"iTunes Libraries", @"iPhoto Libraries", nil]; [regDict setObject:typeArray forKey:@"SrvTypeKeys"]; [regDict setObject:nameArray forKey:@"SrvNameKeys"]; [[NSUserDefaults standardUserDefaults] registerDefaults:regDict]; } - (id)init { self = [super init]; if (self) { _srvtypeKeys = nil; _srvnameKeys = nil; _serviceBrowser = nil; _serviceResolver = nil; _ipv4AddressResolver = nil; _ipv6AddressResolver = nil; _sortedServices = [[NSMutableArray alloc] init]; _servicesDict = [[NSMutableDictionary alloc] init]; } return self; } - (void)awakeFromNib { [typeField sizeLastColumnToFit]; [nameField sizeLastColumnToFit]; [nameField setDoubleAction:@selector(connect:)]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyTypeSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:typeField]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyNameSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:nameField]; _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy]; _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy]; if (!_srvtypeKeys || !_srvnameKeys) { [_srvtypeKeys release]; [_srvnameKeys release]; [self registerDefaults]; _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy]; _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy]; } [typeField reloadData]; } - (void)dealloc { [_srvtypeKeys release]; [_srvnameKeys release]; [_servicesDict release]; [_sortedServices release]; [super dealloc]; } -(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row { if (row < 0) return; } - (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods { if (theTableView == typeField) { return [_srvnameKeys count]; } if (theTableView == nameField) { return [_servicesDict count]; } if (theTableView == serviceDisplayTable) { return [_srvnameKeys count]; } return 0; } - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex { if (theTableView == typeField) { return [_srvnameKeys objectAtIndex:rowIndex]; } if (theTableView == nameField) { return [[_servicesDict objectForKey:[_sortedServices objectAtIndex:rowIndex]] name]; } if (theTableView == serviceDisplayTable) { if (theColumn == typeColumn) { return [_srvtypeKeys objectAtIndex:rowIndex]; } if (theColumn == nameColumn) { return [_srvnameKeys objectAtIndex:rowIndex]; } return nil; } return nil; } - (void)notifyTypeSelectionChange:(NSNotification*)note { [self _cancelPendingResolve]; int index = [[note object] selectedRow]; if (index != -1) { [self update:[_srvtypeKeys objectAtIndex:index]]; } else { [self update:nil]; } } - (void)notifyNameSelectionChange:(NSNotification*)note { [self _cancelPendingResolve]; int index = [[note object] selectedRow]; if (index == -1) { return; } // Get the currently selected service NSNetService *service = [_servicesDict objectForKey:[_sortedServices objectAtIndex:index]]; DNSServiceRef serviceRef; DNSServiceErrorType err = DNSServiceResolve(&serviceRef, (DNSServiceFlags)0, kDNSServiceInterfaceIndexAny, (const char *)[[service name] UTF8String], (const char *)[[service type] UTF8String], (const char *)[[service domain] UTF8String], (DNSServiceResolveReply)ServiceResolveReply, self); if (kDNSServiceErr_NoError == err) { _serviceResolver = [[ServiceController alloc] initWithServiceRef:serviceRef]; [_serviceResolver addToCurrentRunLoop]; } } - (IBAction)update:(NSString *)theType { [_servicesDict removeAllObjects]; [_sortedServices removeAllObjects]; [nameField reloadData]; // get rid of the previous browser if one exists if (_serviceBrowser != nil) { [_serviceBrowser release]; _serviceBrowser = nil; } if (theType) { DNSServiceRef serviceRef; DNSServiceErrorType err = DNSServiceBrowse(&serviceRef, (DNSServiceFlags)0, 0, [theType UTF8String], NULL, ServiceBrowseReply, self); if (kDNSServiceErr_NoError == err) { _serviceBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef]; [_serviceBrowser addToCurrentRunLoop]; } } } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { return YES; } - (void)updateBrowseWithName:(const char *)name type:(const char *)type domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags { NSString *key = [NSString stringWithFormat:@"%s.%s%s%d", name, type, domain, interface]; NSNetService *service = [[NSNetService alloc] initWithDomain:[NSString stringWithUTF8String:domain] type:[NSString stringWithUTF8String:type] name:[NSString stringWithUTF8String:name]]; if (flags & kDNSServiceFlagsAdd) { [_servicesDict setObject:service forKey:key]; } else { [_servicesDict removeObjectForKey:key]; } // If not expecting any more data, then reload (redraw) TableView with newly found data if (!(flags & kDNSServiceFlagsMoreComing)) { // Save the current TableView selection int index = [nameField selectedRow]; NSString *selected = (index != -1) ? [[_sortedServices objectAtIndex:index] copy] : nil; [_sortedServices release]; _sortedServices = [[_servicesDict allKeys] mutableCopy]; [_sortedServices sortUsingSelector:@selector(caseInsensitiveCompare:)]; [nameField reloadData]; // Restore the previous TableView selection index = selected ? [_sortedServices indexOfObject:selected] : NSNotFound; if (index != NSNotFound) { [nameField selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO]; [nameField scrollRowToVisible:index]; } [selected release]; } [service release]; return; } - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen { DNSServiceRef serviceRef; if (_ipv4AddressResolver) { [_ipv4AddressResolver release]; _ipv4AddressResolver = nil; } if (_ipv6AddressResolver) { [_ipv6AddressResolver release]; _ipv6AddressResolver = nil; } // Start an async lookup for IPv4 addresses DNSServiceErrorType err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_A, kDNSServiceClass_IN, QueryRecordReply, self); if (err == kDNSServiceErr_NoError) { _ipv4AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef]; [_ipv4AddressResolver addToCurrentRunLoop]; } // Start an async lookup for IPv6 addresses err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_AAAA, kDNSServiceClass_IN, QueryRecordReply, self); if (err == kDNSServiceErr_NoError) { _ipv6AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef]; [_ipv6AddressResolver addToCurrentRunLoop]; } char interfaceName[IF_NAMESIZE]; InterfaceIndexToName(interface, interfaceName); [hostField setStringValue:host]; [interfaceField setStringValue:[NSString stringWithUTF8String:interfaceName]]; [portField setIntValue:ntohs(port)]; // kind of a hack: munge txtRecord so it's human-readable if (txtLen > 0) { char *readableText = (char*) malloc(txtLen); if (readableText != nil) { ByteCount index, subStrLen; memcpy(readableText, txtRecord, txtLen); for (index=0; index < txtLen - 1; index += subStrLen + 1) { subStrLen = readableText[index]; readableText[index] = ' '; } [textField setStringValue:[NSString stringWithCString:&readableText[1] length:txtLen - 1]]; free(readableText); } } } - (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 { char addrBuff[256]; if (rrtype == kDNSServiceType_A) { inet_ntop(AF_INET, buff, addrBuff, sizeof(addrBuff)); if ([[ipAddressField stringValue] length] > 0) { [ipAddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ipAddressField stringValue]]]; } [ipAddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ipAddressField stringValue], addrBuff]]; if (!moreToCome) { [_ipv4AddressResolver release]; _ipv4AddressResolver = nil; } } else if (rrtype == kDNSServiceType_AAAA) { inet_ntop(AF_INET6, buff, addrBuff, sizeof(addrBuff)); if ([[ip6AddressField stringValue] length] > 0) { [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ip6AddressField stringValue]]]; } [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ip6AddressField stringValue], addrBuff]]; if (!moreToCome) { [_ipv6AddressResolver release]; _ipv6AddressResolver = nil; } } } - (void)connect:(id)sender { NSString *host = [hostField stringValue]; NSString *txtRecord = [textField stringValue]; int port = [portField intValue]; int index = [nameField selectedRow]; NSString *selected = (index >= 0) ? [_sortedServices objectAtIndex:index] : nil; NSString *type = [[_servicesDict objectForKey:selected] type]; if ([type isEqual:@"_http._tcp."]) { NSString *pathDelim = @"path="; NSRange where; // If the TXT record specifies a path, extract it. where = [txtRecord rangeOfString:pathDelim options:NSCaseInsensitiveSearch]; if (where.length) { NSRange targetRange = { where.location + where.length, [txtRecord length] - where.location - where.length }; NSRange endDelim = [txtRecord rangeOfString:@"\n" options:kNilOptions range:targetRange]; if (endDelim.length) // if a delimiter was found, truncate the target range targetRange.length = endDelim.location - targetRange.location; NSString *path = [txtRecord substringWithRange:targetRange]; [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d%@", host, port, path]]]; } else { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", host, port]]]; } } else if ([type isEqual:@"_ftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", host, port]]]; else if ([type isEqual:@"_ssh._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", host, port]]]; else if ([type isEqual:@"_afpovertcp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", host, port]]]; else if ([type isEqual:@"_smb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", host, port]]]; else if ([type isEqual:@"_rfb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"vnc://%@:%d/", host, port]]]; return; } - (IBAction)handleTableClick:(id)sender { //populate the text fields } - (IBAction)removeSelected:(id)sender { // remove the selected row and force a refresh int selectedRow = [serviceDisplayTable selectedRow]; if (selectedRow) { [_srvtypeKeys removeObjectAtIndex:selectedRow]; [_srvnameKeys removeObjectAtIndex:selectedRow]; [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"]; [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"]; [typeField reloadData]; [serviceDisplayTable reloadData]; } } - (IBAction)addNewService:(id)sender { // add new entries from the edit fields to the arrays for the defaults NSString *newType = [serviceTypeField stringValue]; NSString *newName = [serviceNameField stringValue]; // 3282283: trim trailing '.' from service type field if ([newType length] && [newType hasSuffix:@"."]) newType = [newType substringToIndex:[newType length] - 1]; if ([newType length] && [newName length]) { [_srvtypeKeys addObject:newType]; [_srvnameKeys addObject:newName]; [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"]; [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"]; [typeField reloadData]; [serviceDisplayTable reloadData]; } } - (void)_cancelPendingResolve { [_ipv4AddressResolver release]; _ipv4AddressResolver = nil; [_ipv6AddressResolver release]; _ipv6AddressResolver = nil; [_serviceResolver release]; _serviceResolver = nil; [self _clearResolvedInfo]; } - (void)_clearResolvedInfo { [hostField setStringValue:@""]; [ipAddressField setStringValue:@""]; [ip6AddressField setStringValue:@""]; [portField setStringValue:@""]; [interfaceField setStringValue:@""]; [textField setStringValue:@""]; } @end // implementation BrowserController @implementation ServiceController : NSObject { DNSServiceRef fServiceRef; CFSocketRef fSocketRef; CFRunLoopSourceRef fRunloopSrc; } - (id)initWithServiceRef:(DNSServiceRef)ref { self = [super init]; if (self) { fServiceRef = ref; fSocketRef = NULL; fRunloopSrc = NULL; } return self; } - (void)addToCurrentRunLoop { CFSocketContext context = { 0, (void*)fServiceRef, NULL, NULL, NULL }; fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef), kCFSocketReadCallBack, ProcessSockData, &context); if (fSocketRef) { // Prevent CFSocketInvalidate from closing DNSServiceRef's socket. CFOptionFlags sockFlags = CFSocketGetSocketFlags(fSocketRef); CFSocketSetSocketFlags(fSocketRef, sockFlags & (~kCFSocketCloseOnInvalidate)); fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 0); } if (fRunloopSrc) { CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode); } else { printf("Could not listen to runloop socket\n"); } } - (DNSServiceRef)serviceRef { return fServiceRef; } - (void)dealloc { if (fSocketRef) { CFSocketInvalidate(fSocketRef); // Note: Also closes the underlying socket CFRelease(fSocketRef); // Workaround that gives time to CFSocket's select thread so it can remove the socket from its // FD set before we close the socket by calling DNSServiceRefDeallocate. usleep(1000); } if (fRunloopSrc) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode); CFRelease(fRunloopSrc); } DNSServiceRefDeallocate(fServiceRef); [super dealloc]; } @end // implementation ServiceController int main(int argc, const char *argv[]) { return NSApplicationMain(argc, argv); }