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