Commit | Line | Data |
---|---|---|
b7388343 A |
1 | /* |
2 | * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
8e92c31c A |
6 | * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. |
7 | * | |
b7388343 A |
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 $ | |
8e92c31c A |
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 | ||
b7388343 A |
61 | Revision 1.18 2003/08/12 19:55:07 cheshire |
62 | Update to APSL 2.0 | |
63 | ||
64 | */ | |
65 | ||
8e92c31c A |
66 | #include <arpa/inet.h> |
67 | #include <netdb.h> | |
68 | #include <nameser.h> | |
69 | #include <sys/select.h> | |
b7388343 | 70 | |
8e92c31c A |
71 | #import <Cocoa/Cocoa.h> |
72 | #import <DNSServiceDiscovery/DNSServiceDiscovery.h> | |
b7388343 | 73 | |
8e92c31c A |
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 | |
b7388343 | 80 | { |
8e92c31c A |
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 | ||
b7388343 A |
111 | } |
112 | ||
8e92c31c A |
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 | |
b7388343 | 142 | { |
8e92c31c A |
143 | DNSServiceRef fServiceRef; |
144 | CFSocketRef fSocketRef; | |
145 | CFRunLoopSourceRef fRunloopSrc; | |
b7388343 A |
146 | } |
147 | ||
8e92c31c A |
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. | |
b7388343 | 158 | { |
8e92c31c A |
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 | } | |
b7388343 | 164 | |
8e92c31c A |
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 | } | |
b7388343 A |
175 | } |
176 | ||
8e92c31c A |
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. | |
b7388343 | 180 | { |
8e92c31c A |
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 | } | |
b7388343 | 189 | |
8e92c31c A |
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)]; | |
b7388343 A |
212 | } |
213 | ||
8e92c31c | 214 | |
b7388343 A |
215 | @implementation BrowserController //Begin implementation of BrowserController methods |
216 | ||
217 | - (void)registerDefaults | |
218 | { | |
219 | NSMutableDictionary *regDict = [NSMutableDictionary dictionary]; | |
220 | ||
8e92c31c A |
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]; | |
b7388343 A |
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 | ||
8e92c31c A |
245 | fDomainBrowser = nil; |
246 | fServiceBrowser = nil; | |
247 | fServiceResolver = nil; | |
248 | fAddressResolver = nil; | |
b7388343 A |
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 | |
8e92c31c | 268 | [typeField sizeLastColumnToFit]; //Set column sizes to use their whole table's width. |
b7388343 | 269 | [nameField sizeLastColumnToFit]; |
b7388343 | 270 | [domainField sizeLastColumnToFit]; |
8e92c31c | 271 | // (self is specified as the NSTableViews' data source in the nib) |
b7388343 A |
272 | |
273 | [nameField setDoubleAction:@selector(connect:)]; | |
274 | ||
8e92c31c A |
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 | |
b7388343 | 282 | //[srvnameKeys addObject:@"File Transfer (ftp)"]; |
8e92c31c | 283 | //[srvtypeKeys addObject:@"_printer._tcp"]; //respective arrays |
b7388343 | 284 | //[srvnameKeys addObject:@"Printer (lpr)"]; |
8e92c31c | 285 | //[srvtypeKeys addObject:@"_http._tcp"]; //respective arrays |
b7388343 | 286 | //[srvnameKeys addObject:@"Web Server (http)"]; |
8e92c31c | 287 | //[srvtypeKeys addObject:@"_afp._tcp"]; //respective arrays |
b7388343 A |
288 | //[srvnameKeys addObject:@"AppleShare Server (afp)"]; |
289 | ||
8e92c31c | 290 | [self _clearResolvedInfo]; |
b7388343 A |
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 | { | |
8e92c31c | 349 | return [[nameKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:rowIndex]; |
b7388343 A |
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 | { | |
8e92c31c | 367 | // 3282283: No longer used - update happens in notifyTypeSelectionChange |
b7388343 A |
368 | } |
369 | ||
8e92c31c | 370 | |
b7388343 A |
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 | ||
8e92c31c | 377 | [self _cancelPendingResolve]; |
b7388343 A |
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 | { | |
8e92c31c A |
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 | |
b7388343 | 391 | if (index==-1) return; //Error checking |
8e92c31c A |
392 | SrvType = [srvtypeKeys objectAtIndex:index]; //Save desired Type |
393 | SrvName = [srvnameKeys objectAtIndex:index]; //Save desired Type | |
b7388343 | 394 | |
8e92c31c A |
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 | } | |
b7388343 A |
423 | } |
424 | ||
425 | - (IBAction)loadDomains:(id)sender | |
426 | { | |
8e92c31c A |
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); | |
b7388343 A |
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 | ||
8e92c31c A |
450 | DNSServiceErrorType err = kDNSServiceErr_NoError; |
451 | ||
b7388343 A |
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 | |
8e92c31c A |
462 | if ( fServiceBrowser != nil) { |
463 | [fServiceBrowser release]; | |
464 | fServiceBrowser = nil; | |
b7388343 A |
465 | } |
466 | ||
467 | // now create a browser to return the values for the nameField ... | |
8e92c31c A |
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 | } | |
b7388343 A |
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 | ||
8e92c31c | 488 | - (void)updateEnumWithResult:(DNSServiceFlags)flags domain:(NSString *)domain |
b7388343 | 489 | { |
8e92c31c | 490 | if ( ( flags & kDNSServiceFlagsAdd) != 0) { // new domain received |
b7388343 A |
491 | // add the domain to the list |
492 | [domainKeys addObject:domain]; | |
8e92c31c | 493 | } else if (!(flags & kDNSServiceFlagsAdd)) { |
b7388343 A |
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]; | |
8e92c31c A |
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 | ||
b7388343 A |
518 | return; |
519 | } | |
520 | ||
521 | ||
522 | ||
8e92c31c | 523 | - (void)updateBrowseWithResult:(DNSServiceFlags)flags name:(NSString *)name type:(NSString *)resulttype domain:(NSString *)domain |
b7388343 A |
524 | { |
525 | ||
526 | //NSLog(@"Received result %@ %@ %@ %d", name, resulttype, domain, type); | |
527 | ||
8e92c31c A |
528 | if (!(flags & kDNSServiceFlagsAdd)) { |
529 | if ([nameKeys containsObject:name]) { | |
530 | [nameKeys removeObject:name]; | |
b7388343 | 531 | |
8e92c31c A |
532 | // 3282283: Cancel pending browse if object goes away. |
533 | if ( [name isEqualToString:Name]) | |
534 | [nameField deselectAll:self]; | |
b7388343 | 535 | } |
8e92c31c A |
536 | } |
537 | else if ( ( flags & kDNSServiceFlagsAdd) != 0) { | |
538 | if (![nameKeys containsObject:name]) { | |
539 | [nameKeys addObject:name]; | |
b7388343 | 540 | } |
b7388343 | 541 | } |
8e92c31c A |
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]; | |
b7388343 A |
546 | return; |
547 | } | |
548 | ||
8e92c31c A |
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. */ | |
b7388343 | 552 | { |
8e92c31c A |
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]; | |
b7388343 | 569 | [portField setIntValue:port]; |
b7388343 | 570 | |
8e92c31c A |
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 | } | |
b7388343 A |
624 | } |
625 | ||
8e92c31c | 626 | |
b7388343 A |
627 | - (void)connect:(id)sender |
628 | { | |
8e92c31c | 629 | NSString *host = [hostField stringValue]; |
b7388343 A |
630 | int port = [portField intValue]; |
631 | NSString *txtRecord = [textField stringValue]; | |
632 | ||
633 | if (!txtRecord) txtRecord = @""; | |
634 | ||
8e92c31c | 635 | if (!host || !port) return; |
b7388343 | 636 | |
8e92c31c A |
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]]]; | |
b7388343 A |
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 | |
8e92c31c A |
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]; | |
b7388343 | 703 | |
8e92c31c A |
704 | if ([newType length] && [newName length]) { |
705 | [srvtypeKeys addObject:newType]; | |
706 | [srvnameKeys addObject:newName]; | |
b7388343 A |
707 | |
708 | [[NSUserDefaults standardUserDefaults] setObject:srvtypeKeys forKey:@"SrvTypeKeys"]; | |
709 | [[NSUserDefaults standardUserDefaults] setObject:srvnameKeys forKey:@"SrvNameKeys"]; | |
710 | ||
711 | [typeField reloadData]; | |
712 | [serviceDisplayTable reloadData]; | |
713 | } | |
8e92c31c A |
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 | } | |
b7388343 | 751 | |
8e92c31c A |
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; | |
b7388343 A |
779 | } |
780 | ||
8e92c31c A |
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 | } | |
b7388343 | 793 | |
8e92c31c A |
794 | DNSServiceRefDeallocate( fServiceRef); |
795 | ||
796 | [super dealloc]; | |
797 | } | |
b7388343 | 798 | |
8e92c31c A |
799 | @end // implementation ServiceController |
800 | ||
801 | int main(int argc, const char *argv[]) | |
802 | { | |
803 | return NSApplicationMain(argc, argv); | |
804 | } |