root/trunk/TunnelController.m

Revision 99, 15.5 kB (checked in by bart, 1 year ago)

0.8.1

Line 
1 #import "TunnelController.h"
2
3 #import "Controller.h"
4 #import "PreferenceController.h"
5 #import "Libs/SSHTunnel.h"
6 #import "Utilities.h"
7 #import "NSMenu_Additions.h"
8
9 #ifndef NSAppKitVersionNumber10_3
10 #define NSAppKitVersionNumber10_3 743
11 #endif
12
13 #include "SSHKeychain_Prefix.pch"
14
15 /* This function resides in Controller.m. */
16 extern NSString *local(NSString *theString);
17
18 TunnelController *sharedTunnelController;
19
20 @implementation TunnelController
21
22 - (id)init
23 {               
24         if(self = [super init])
25         {
26                 [[NSNotificationCenter defaultCenter] addObserver:self
27                                                 selector:@selector(applicationDidFinishLaunching:)
28                                                 name:@"NSApplicationDidFinishLaunchingNotification" object:NSApp];
29                
30                 [[NSNotificationCenter defaultCenter] addObserver:self
31                                                          selector:@selector(applicationWillTerminate:)
32                                                                  name:@"NSApplicationWillTerminateNotification" object:NSApp];
33                
34                 [[NSNotificationCenter defaultCenter] addObserver:self
35                                                          selector:@selector(agentFilledNotification:) name:@"AgentFilled" object:nil];
36                
37                 [[NSNotificationCenter defaultCenter] addObserver:self
38                                                          selector:@selector(agentEmptiedNotification:) name:@"AgentEmptied" object:nil];
39
40                 sharedTunnelController = self;
41
42                 notificationQueue  = [[NSMutableArray alloc] init];
43                 notificationLock   = [[NSLock alloc] init];
44                 notificationThread = [[NSThread currentThread] retain];
45                 notificationPort   = [[NSMachPort alloc] init];
46
47                 [notificationPort setDelegate:self];
48
49                 [[NSRunLoop currentRunLoop] addPort:notificationPort forMode:(NSString *)kCFRunLoopCommonModes];
50         }
51        
52         return self;
53 }
54
55 + (TunnelController *)sharedController
56 {
57         if(!sharedTunnelController) {
58                 return [[TunnelController alloc] init];
59         }
60
61         return sharedTunnelController;
62 }
63
64 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
65 {
66         tunnels = [[NSMutableArray alloc] init];
67        
68         [self sync];
69
70         [self setToolTipForActiveTunnels];
71 }
72
73 - (void)applicationWillTerminate:(NSNotification *)notification
74 {
75         [self closeAllTunnels];
76 }
77
78 - (void)sync
79 {
80         NSArray *newTunnels;
81         NSMutableDictionary *dict;
82         int i;
83         BOOL match;
84        
85         if(!tunnels) { return; }
86        
87         [[NSUserDefaults standardUserDefaults] synchronize];
88         newTunnels = [[NSUserDefaults standardUserDefaults] arrayForKey:TunnelsString];
89        
90         // Because we're adding UUIDs, we should give them to all tunnels that don't yet have them
91         BOOL setUUID = NO;
92         NSEnumerator *e =  [newTunnels objectEnumerator];
93         NSDictionary *aTunnel;
94         NSMutableArray *modifiedTunnels = [NSMutableArray array];
95         while (aTunnel = [e nextObject]) {
96                 if ([aTunnel objectForKey:@"TunnelUUID"] == nil)
97                 {
98                         NSMutableDictionary *modifiedTunnel = [NSMutableDictionary dictionaryWithDictionary:aTunnel];
99                         [modifiedTunnel setObject:CreateUUID() forKey:@"TunnelUUID"];
100                         [modifiedTunnels addObject:modifiedTunnel];
101                         setUUID = YES;
102                 } else {
103                         [modifiedTunnels addObject:aTunnel];
104                 }
105         }
106         if (setUUID) {
107                 newTunnels = [NSArray arrayWithArray:modifiedTunnels];
108                 [[NSUserDefaults standardUserDefaults] setObject:newTunnels forKey:TunnelsString];
109         }
110        
111         e = [newTunnels objectEnumerator];
112         while (aTunnel = [e nextObject]) {
113                 match = NO;
114                
115                 for(i=0; i < [tunnels count]; i++)
116                 {
117                         NSDictionary *oldTunnel = [tunnels objectAtIndex:i];
118                         if([[aTunnel objectForKey:@"TunnelUUID"] isEqualToString:
119                                 [oldTunnel objectForKey:@"TunnelUUID"]])
120                         {
121                                 dict = [NSMutableDictionary dictionaryWithDictionary:aTunnel];
122
123                                 if([oldTunnel objectForKey:@"TunnelObject"])
124                                 {
125                                         [dict setObject:[oldTunnel objectForKey:@"TunnelObject"] forKey:@"TunnelObject"];
126                                 }
127
128                                 [tunnels replaceObjectAtIndex:i withObject:dict];
129
130                                 match = YES;
131                         }
132                 }
133                
134                 if(!match)
135                 {
136                         dict = [NSMutableDictionary dictionaryWithDictionary:aTunnel];
137                        
138                         id <NSMenuItem> mItem;
139                         mItem = [mainMenuTunnelsItem addItemWithTitle:[dict objectForKey:@"TunnelName"]
140                                                                                                    action:@selector(toggleTunnel:)
141                                                                                         keyEquivalent:@""];
142                         [mItem setTarget:self];
143                         [mItem setRepresentedObject:[dict objectForKey:@"TunnelUUID"]];
144                        
145                         mItem = [dockMenuTunnelsItem addItemWithTitle:[dict objectForKey:@"TunnelName"]
146                                                                                                    action:@selector(toggleTunnel:)
147                                                                                         keyEquivalent:@""];
148                         [mItem setTarget:self];
149                         [mItem setRepresentedObject:[dict objectForKey:@"TunnelUUID"]];
150                        
151                         mItem = [statusbarMenuTunnelsItem addItemWithTitle:[dict objectForKey:@"TunnelName"]
152                                                                                                                 action:@selector(toggleTunnel:) keyEquivalent:@""];
153                         [mItem setTarget:self];
154                         [mItem setRepresentedObject:[dict objectForKey:@"TunnelUUID"]];
155                        
156                         [tunnels addObject:[NSMutableDictionary dictionaryWithDictionary:dict]];
157                 }
158         }
159 }
160
161 - (void)changeTunnel:(NSString *)uuid setName:(NSString *)newName
162 {
163         if(!tunnels) { return; }
164        
165         NSEnumerator *e = [tunnels objectEnumerator];
166         NSMutableDictionary *aTunnel;
167         while (aTunnel = [e nextObject])
168         {
169                 if([[aTunnel objectForKey:@"TunnelUUID"] isEqualToString:uuid])
170                 {
171                         [aTunnel setObject:newName forKey:@"TunnelName"];
172                        
173                         [[mainMenuTunnelsItem itemWithRepresentation:uuid] setTitle:newName];
174                         [[statusbarMenuTunnelsItem itemWithRepresentation:uuid] setTitle:newName];
175                         [[dockMenuTunnelsItem itemWithRepresentation:uuid] setTitle:newName];
176                        
177                         return;
178                 }
179         }
180 }
181
182 - (void)setToolTipForActiveTunnels
183 {
184         Controller *controller = [Controller sharedController];
185         int active = 0;
186        
187         if(!tunnels) {
188                 [controller setToolTip:@""];
189                 return;
190         }
191        
192         NSEnumerator *e = [tunnels objectEnumerator];
193         NSDictionary *aTunnel;
194         while (aTunnel = [e nextObject])
195         {
196                 if([aTunnel objectForKey:@"TunnelObject"])
197                 {
198                         active++;
199                 }
200         }
201        
202         if(!active) {
203                 [controller setToolTip:local(@"NoActiveTunnels")];
204         } else if(active == 1) {
205                 [controller setToolTip:[NSString stringWithFormat:@"1 %@", local(@"ActiveTunnel")]];
206         } else {
207                 [controller setToolTip:[NSString stringWithFormat:@"%d %@", active, local(@"ActiveTunnels")]];
208         }               
209 }
210
211 - (void)removeTunnelWithUUID:(NSString *)uuid
212 {
213         SSHTunnel *tunnel;
214        
215         if(!tunnels) { return; }
216        
217         NSEnumerator *e = [tunnels objectEnumerator];
218         NSDictionary *aTunnel;
219         while (aTunnel = [e nextObject])
220         {
221                 if([[aTunnel objectForKey:@"TunnelUUID"] isEqualToString:uuid])
222                 {
223                         tunnel = [aTunnel objectForKey:@"TunnelObject"];
224                        
225                         if (tunnel)
226                         {
227                                 [tunnel closeTunnel];
228                         }
229                        
230                         [tunnels removeObjectIdenticalTo:aTunnel];
231
232                         [mainMenuTunnelsItem removeItem:[mainMenuTunnelsItem itemWithRepresentation:uuid]];
233                         [dockMenuTunnelsItem removeItem:[dockMenuTunnelsItem itemWithRepresentation:uuid]];
234                         [statusbarMenuTunnelsItem removeItem:[statusbarMenuTunnelsItem itemWithRepresentation:uuid]];
235                 }
236         }
237 }
238
239 - (void)closeAllTunnels
240 {       
241         if(!tunnels) { return; }
242        
243         NSEnumerator *e = [tunnels objectEnumerator];
244         NSMutableDictionary *aTunnel;
245         while (aTunnel = [e nextObject])
246         {
247                 if([aTunnel objectForKey:@"TunnelObject"])
248                 {
249                         SSHTunnel *tunnel = [aTunnel objectForKey:@"TunnelObject"];
250                         NSString *uuid = [aTunnel objectForKey:@"TunnelUUID"];
251                        
252                         [tunnel closeTunnel];
253                         [aTunnel removeObjectForKey:@"TunnelObject"];
254                        
255                         [[mainMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
256                         [[statusbarMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
257                         [[dockMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
258                 }
259         }
260        
261         [self setToolTipForActiveTunnels];
262 }
263
264 - (void)launchAfterSleepTunnels
265 {
266         SSHTunnel *tunnel;
267         NSString *uuid;
268        
269         if(!tunnels) { return; }
270        
271         NSEnumerator *e = [tunnels objectEnumerator];
272         NSMutableDictionary *aTunnel;
273         while (aTunnel = [e nextObject])
274         {
275                 if([aTunnel objectForKey:@"LaunchAfterSleep"])
276                 {
277                         /* First kill the tunnel, if it's still open. */
278                         if([aTunnel objectForKey:@"TunnelObject"])
279                         {
280                                 tunnel = [aTunnel objectForKey:@"TunnelObject"];
281                                 uuid = [aTunnel objectForKey:@"TunnelUUID"];
282                        
283                                 [tunnel closeTunnel];
284                                 [aTunnel removeObjectForKey:@"TunnelObject"];
285                        
286                                 [[mainMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
287                                 [[statusbarMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
288                                 [[dockMenuTunnelsItem itemWithRepresentation:uuid] setState:NO];
289                         }
290
291                         [self openTunnelWithDict:aTunnel];
292                 }
293         }
294        
295         [self setToolTipForActiveTunnels];
296 }
297
298 - (void)toggleTunnel:(id)sender
299 {
300         NSMutableDictionary *dict;
301         SSHTunnel *tunnel;
302        
303         dict = nil;
304        
305         if(!tunnels) { return; }
306        
307         NSEnumerator *e = [tunnels objectEnumerator];
308         NSMutableDictionary *aTunnel;
309         while (aTunnel = [e nextObject])
310         {
311                 if([[aTunnel objectForKey:@"TunnelUUID"] isEqualToString:[sender representedObject]])
312                 {
313                         dict = aTunnel;
314                         break;
315                 }
316         }
317        
318         if(!dict) { return; }
319        
320         if([dict objectForKey:@"TunnelObject"])
321         {
322                
323                 tunnel = [dict objectForKey:@"TunnelObject"];
324
325                 [tunnel closeTunnel];
326                
327                 [dict removeObjectForKey:@"TunnelObject"];
328                
329                 [sender setState:NO];
330         }
331        
332         else
333         {
334                 [self openTunnelWithDict:dict];
335         }
336        
337         [self setToolTipForActiveTunnels];
338 }
339
340 /* Handle closed tunnels. */
341 - (void)handleClosedTunnels:(NSString *)contextInfo
342 {
343         int last_terminated;
344         NSString *output;
345         BOOL fails_exceeded = NO;
346                
347         if(!contextInfo)
348         {
349                 return;
350         }
351
352         [[mainMenuTunnelsItem itemWithRepresentation:contextInfo] setState:NO];
353         [[statusbarMenuTunnelsItem itemWithRepresentation:contextInfo] setState:NO];
354         [[dockMenuTunnelsItem itemWithRepresentation:contextInfo] setState:NO];
355        
356         if(!tunnels)
357         {
358                 return;
359         }
360        
361         NSEnumerator *e = [tunnels objectEnumerator];
362         NSMutableDictionary *aTunnel;
363         while (aTunnel = [e nextObject])
364         {
365                 if([[aTunnel objectForKey:@"TunnelUUID"] isEqualToString:contextInfo])
366                 {
367                         SSHTunnel *tunnel = [aTunnel objectForKey:@"TunnelObject"];
368                        
369                         if(tunnel)
370                         {
371                                 last_terminated = 0;
372                                 fails_exceeded = NO;
373                                
374                                 output = [tunnel getOutput];
375                                
376                                 [aTunnel removeObjectForKey:@"TunnelObject"];
377
378                                 if([aTunnel objectForKey:@"LastTerminated"])
379                                 {
380                                         last_terminated = [[aTunnel objectForKey:@"LastTerminated"] intValue];
381                                 }
382
383                                 if((last_terminated) && ((time(nil) - last_terminated) < 300))
384                                 {
385                                         fails_exceeded = YES;
386                                         [aTunnel removeObjectForKey:@"LastTerminated"];
387                                 }
388                                
389                                 else if((allKeysOnAgent) && ([output length] < 1))
390                                 {
391                                         [aTunnel setObject:[NSNumber numberWithInt:time(nil)] forKey:@"LastTerminated"];
392                                         [self openTunnelWithDict:aTunnel];
393                                 }
394
395                                 if((fails_exceeded) && ([output length] > 0))
396                                 {
397                                         [self warningPanelWithTitle:local(@"TunnelTerminated")
398                                                          andMessage:[NSString stringWithFormat:@"(%@) %@",
399                                                                  [aTunnel objectForKey:@"TunnelName"],
400                                                                  local(@"TunnelTerminatedAndCouldNotBeRestarted")]];
401                                 }
402                                
403                                 else if((!fails_exceeded) && (!allKeysOnAgent) && ([output length] < 1)) {
404                                         [self warningPanelWithTitle:local(@"TunnelTerminated")
405                                                          andMessage:[NSString stringWithFormat:@"(%@) %@",
406                                                                  [aTunnel objectForKey:@"TunnelName"],
407                                                                  local(@"TunnelTerminatedAndCouldNotBeRestarted")]];
408                                 }
409
410                                 else if([output isEqualToString:@"tunnel failed\n"])
411                                 {
412                                         [self warningPanelWithTitle:local(@"TunnelForwardingFailed")
413                                                          andMessage:[NSString stringWithFormat:@"(%@) %@",
414                                                                  [aTunnel objectForKey:@"TunnelName"],
415                                                                  local(@"ForwardingFailedDuringInitialization")]];
416                                 }
417                                
418                                 else if([output length] > 0)
419                                 {
420                                         [self warningPanelWithTitle:local(@"TunnelSetupFailed")
421                                                          andMessage:[NSString stringWithFormat:@"(%@) Error:\n%@",
422                                                                  [aTunnel objectForKey:@"TunnelName"],
423                                                                  output]];
424                                 }
425                                
426                                 else if(fails_exceeded)
427                                 {
428                                         [self warningPanelWithTitle:local(@"TunnelTerminated")
429                                                          andMessage:[NSString stringWithFormat:@"(%@) %@",
430                                                                  [aTunnel objectForKey:@"TunnelName"],
431                                                                  local(@"TunnelUnexpectedlyTerminated")]];
432                                 }
433                         }
434                 }
435         }
436        
437         [self setToolTipForActiveTunnels];
438 }
439
440 /* Handle Apple keychain unlocks. */
441 - (void)agentFilledNotification:(NSNotification *)notification
442 {
443         /* Forward the notification to the correct thread. */
444         if([NSThread currentThread] != notificationThread)
445         {
446                 [notificationLock lock];
447                 [notificationQueue addObject:notification];
448                 [notificationLock unlock];
449
450                 [notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
451
452                 return;
453         }
454                
455         allKeysOnAgent = YES;
456        
457         if(!tunnels) { return; }
458        
459         NSEnumerator *e = [tunnels objectEnumerator];
460         NSMutableDictionary *aTunnel;
461         while (aTunnel = [e nextObject])
462         {
463                 if(![aTunnel objectForKey:@"TunnelObject"] && [aTunnel objectForKey:@"LaunchOnAgentFilled"])
464                 {
465                         [self openTunnelWithDict:aTunnel];
466                 }
467         }
468 }
469
470 /* Handle Apple keychain unlocks. */
471 - (void)agentEmptiedNotification:(NSNotification *)notification
472 {
473         allKeysOnAgent = NO;
474 }
475
476 /* Wrapper to open the tunnel. */
477 - (void)openTunnelWithDict:(NSMutableDictionary *)dict
478 {
479         int i;
480         SSHTunnel *tunnel = [[SSHTunnel alloc] init];
481        
482         [tunnel setTunnelHost:[dict objectForKey:@"TunnelHostname"]
483                         withPort:[[dict objectForKey:@"TunnelPort"] intValue]
484                         andUser:[dict objectForKey:@"TunnelUser"]];
485
486         if([dict objectForKey:@"Compression"])
487         {
488                 [tunnel setCompression:YES];
489         }
490        
491         if([dict objectForKey:@"RemoteAccess"])
492         {
493                 [tunnel setRemoteAccess:YES];
494         }
495
496         if([dict objectForKey:@"LocalPortForwards"])
497         {
498                 for(i=0; i < [[dict objectForKey:@"LocalPortForwards"] count]; i++)
499                 {
500                         [tunnel addLocalPortForwardWithPort:[[[[dict objectForKey:@"LocalPortForwards"] objectAtIndex:i] objectForKey:@"LocalPort"] intValue]
501                                                 remoteHost:[[[dict objectForKey:@"LocalPortForwards"] objectAtIndex:i] objectForKey:@"RemoteHost"]
502                                                 remotePort:[[[[dict objectForKey:@"LocalPortForwards"] objectAtIndex:i] objectForKey:@"RemotePort"] intValue]
503                         ];
504                 }
505         }
506                                                        
507         if([dict objectForKey:@"RemotePortForwards"])
508         {
509                 for(i=0; i < [[dict objectForKey:@"RemotePortForwards"] count]; i++)
510                 {
511                         [tunnel addRemotePortForwardWithPort:[[[[dict objectForKey:@"RemotePortForwards"] objectAtIndex:i] objectForKey:@"RemotePort"] intValue]
512                                                 localHost:[[[dict objectForKey:@"RemotePortForwards"] objectAtIndex:i] objectForKey:@"LocalHost"]
513                                                 localPort:[[[[dict objectForKey:@"RemotePortForwards"] objectAtIndex:i] objectForKey:@"LocalPort"] intValue]
514                         ];
515                 }
516         }
517        
518         if([dict objectForKey:@"DynamicPortForwards"])
519         {
520                 for(i=0; i < [[dict objectForKey:@"DynamicPortForwards"] count]; i++)
521                 {
522                         [tunnel addDynamicPortForwardWithPort:[[[[dict objectForKey:@"DynamicPortForwards"] objectAtIndex:i] objectForKey:@"LocalPort"] intValue]];
523                 }
524         }
525        
526         [tunnel handleClosedWithSelector:@selector(handleClosedTunnels:) toObject:self
527                                 withInfo:[dict objectForKey:@"TunnelUUID"]];
528        
529         if([tunnel openTunnel])
530         {
531                 [dict setObject:tunnel forKey:@"TunnelObject"];
532                 /* Now that the dictionary has it, we can let it go, see below. */
533                
534                 NSString *uuid = [dict objectForKey:@"TunnelUUID"];
535                 [[mainMenuTunnelsItem itemWithRepresentation:uuid] setState:YES];
536                 [[statusbarMenuTunnelsItem itemWithRepresentation:uuid] setState:YES];
537                 [[dockMenuTunnelsItem itemWithRepresentation:uuid] setState:YES];
538                
539                 [self setToolTipForActiveTunnels];
540         }
541         /* Either the dictionary has it, or it didn't work and we don't want it. */
542         [tunnel release];
543
544 }
545
546 /* This method displays a warning. */
547 - (void)warningPanelWithTitle:(NSString *)title andMessage:(NSString *)message
548 {
549         /* Dictionary for the panel. */
550         NSMutableDictionary *dict = [NSMutableDictionary dictionary];
551        
552         [dict setObject:title forKey:(NSString *)kCFUserNotificationAlertHeaderKey];
553         [dict setObject:message forKey:(NSString *)kCFUserNotificationAlertMessageKey];
554        
555         CFUserNotificationCreate(nil, 30, CFUserNotificationSecureTextField(0), nil, (CFDictionaryRef)dict);
556 }
557
558 /* Handle the notification queue. */
559 - (void)handleMachMessage:(void *)msg
560 {
561  
562         [notificationLock lock];
563  
564         while([notificationQueue count]) {
565                 NSNotification *notification = [[notificationQueue objectAtIndex:0] retain];
566                 [notificationQueue removeObjectAtIndex:0];
567                 [notificationLock unlock];
568                 [self agentFilledNotification:notification];
569                 [notification release];
570                 [notificationLock lock];
571         };
572  
573  
574         [notificationLock unlock];
575 }
576  
577 @end
Note: See TracBrowser for help on using the browser.