Wed, 10 Feb 2010 21:25:01 +0100
Extend uac_auth() of the UAC module to workaround CSEQ problems.
This logic is meant to complement that of changeset 17, which
added rich authentication credentials to the gw table and its
associated logic in the LCR module.
1 /*
2 * Copyright (c) 2004 Angelo Laub
3 * Contributions by Dirk Theisen <dirk@objectpark.org>,
4 * Jens Ohlig,
5 * Waldemar Brodkorb
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program (see the file COPYING included with this
18 * distribution); if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
23 #import "MenuController.h"
24 #import "NSApplication+NetworkNotifications.h"
25 #import "helper.h"
28 #define NSAppKitVersionNumber10_0 577
29 #define NSAppKitVersionNumber10_1 620
30 #define NSAppKitVersionNumber10_2 663
31 #define NSAppKitVersionNumber10_3 743
35 BOOL systemIsTigerOrNewer()
36 {
37 return (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_3) ;
38 }
40 @interface NSStatusBar (NSStatusBar_Private)
41 - (id)_statusItemWithLength:(float)l withPriority:(int)p;
42 @end
45 @implementation MenuController
47 - (void) createStatusItem
48 {
49 NSStatusBar *bar = [NSStatusBar systemStatusBar];
50 int priority = INT32_MAX;
51 if (systemIsTigerOrNewer()) {
52 priority = MIN(priority, 2147483646); // found by experimenting - dirk
53 }
55 if (!theItem) {
56 theItem = [[bar statusItemWithLength: NSVariableStatusItemLength] retain];
57 //theItem = [[bar _statusItemWithLength: NSVariableStatusItemLength withPriority: 0] retain];
58 }
59 // Dirk: For Tiger and up, re-insert item to place it correctly.
60 if ([bar respondsToSelector: @selector(_insertStatusItem:withPriority:)]) {
61 [bar removeStatusItem: theItem];
62 [bar _insertStatusItem: theItem withPriority: priority];
63 }
64 }
66 -(id) init
67 {
68 if (self = [super init]) {
69 [self dmgCheck];
71 errorImage = [[NSImage imageNamed: @"error.tif"] retain];
72 mainImage = [[NSImage imageNamed: @"00_closed.tif"] retain];
73 connectedImage = [[NSImage imageNamed: @"connected.png"] retain];
76 transitionalImage1 = [[NSImage imageNamed: @"01.tif"] retain];
77 transitionalImage2 = [[NSImage imageNamed: @"02.tif"] retain];
78 transitionalImage3 = [[NSImage imageNamed: @"03.tif"] retain];
79 [NSApp setDelegate:self];
81 myVPNConnectionDictionary = [[NSMutableDictionary alloc] init];
82 myVPNConnectionArray = [[[NSMutableArray alloc] init] retain];
83 userDefaults = [[NSMutableDictionary alloc] init];
85 connectionArray = [[[NSMutableArray alloc] init] retain];
86 appDefaults = [NSUserDefaults standardUserDefaults];
87 [appDefaults registerDefaults:userDefaults];
90 detailsItem = [[NSMenuItem alloc] init];
91 [detailsItem setTitle: @"Details..."];
92 [detailsItem setTarget: self];
93 [detailsItem setAction: @selector(openLogWindow:)];
95 quitItem = [[NSMenuItem alloc] init];
96 [quitItem setTitle: @"Quit"];
97 [quitItem setTarget: self];
98 [quitItem setAction: @selector(quit:)];
100 [self createStatusItem];
102 [self updateMenu];
103 [self setState: @"EXITING"]; // synonym for "Disconnected"
105 [[NSNotificationCenter defaultCenter] addObserver: self
106 selector: @selector(logNeedsScrolling:)
107 name: @"LogDidChange"
108 object: nil];
110 // In case the systemUIServer restarts, we observed this notification.
111 // We use it to prevent to end up with a statusItem right of Spotlight:
112 [[NSDistributedNotificationCenter defaultCenter] addObserver: self
113 selector: @selector(menuExtrasWereAdded:)
114 name: @"com.apple.menuextra.added"
115 object: nil];
116 [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
117 selector: @selector(willGoToSleep)
118 name: @"NSWorkspaceWillSleepNotification"
119 object:nil];
121 [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
122 selector: @selector(wokeUpFromSleep)
123 name: @"NSWorkspaceDidWakeNotification"
124 object:nil];
126 NSString* vpnDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn/"];
128 UKKQueue* myQueue = [UKKQueue sharedQueue];
129 [myQueue addPathToQueue: vpnDirectory];
130 [myQueue setDelegate: self];
131 [myQueue setAlwaysNotify: YES];
133 [NSThread detachNewThreadSelector:@selector(moveAllWindowsToForegroundThread) toTarget:self withObject:nil];
135 updater = [[SUUpdater alloc] init];
137 }
138 return self;
139 }
141 -(void)moveAllWindowsToForegroundThread {
142 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
143 sleep(3);
144 [self moveAllWindowsToForeground];
145 // [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(moveAllWindowsToForeground) userInfo: nil repeats: YES];
146 [pool release];
148 }
150 - (void) menuExtrasWereAdded: (NSNotification*) n
151 {
152 [self createStatusItem];
153 }
157 - (IBAction) quit: (id) sender
158 {
159 // Remove us from the login items if terminates manually...
160 [NSApp setAutoLaunchOnLogin: NO];
161 [NSApp terminate: sender];
162 }
166 - (void) awakeFromNib
167 {
168 [self createDefaultConfig];
169 [self initialiseAnim];
170 }
172 - (void) initialiseAnim
173 {
174 NSAnimationProgress progMarks[] = {
175 1.0/8.0, 2.0/8.0, 3.0/8.0, 4.0/8.0, 5.0/8.0, 6.0/8.0, 7.0/8.0, 8.0/8.0
176 };
178 int i, count = 8;
179 // theAnim is an NSAnimation instance variable
180 theAnim = [[NSAnimation alloc] initWithDuration:2.0
181 animationCurve:NSAnimationLinear];
182 [theAnim setFrameRate:8.0];
183 [theAnim setDelegate:self];
185 for (i=0; i<count; i++)
186 [theAnim addProgressMark:progMarks[i]];
188 [theAnim setAnimationBlockingMode: NSAnimationNonblocking];
189 }
191 -(void) updateMenu
192 {
193 [theItem setHighlightMode:YES];
194 [theItem setMenu:nil];
195 [myVPNMenu dealloc]; myVPNMenu = nil;
196 [[myVPNConnectionDictionary allValues] makeObjectsPerformSelector:@selector(disconnect:) withObject:self];
197 [myVPNConnectionDictionary removeAllObjects];
199 myVPNMenu = [[NSMenu alloc] init];
200 [myVPNMenu setDelegate:self];
202 [theItem setMenu: myVPNMenu];
204 statusMenuItem = [[NSMenuItem alloc] init];
205 [myVPNMenu addItem:statusMenuItem];
206 [myVPNMenu addItem:[NSMenuItem separatorItem]];
208 [myConfigArray release];
209 myConfigArray = [[[self getConfigs] sortedArrayUsingSelector:@selector(compare:)] retain];
210 [myConfigModDatesArray release];
211 myConfigModDatesArray = [[self getModDates:myConfigArray] retain];
213 NSEnumerator *m = [myConfigArray objectEnumerator];
214 NSString *configString;
215 int i = 2; // we start at MenuItem #2
217 while (configString = [m nextObject])
218 {
219 NSMenuItem *connectionItem = [[[NSMenuItem alloc] init] autorelease];
221 // configure connection object:
222 VPNConnection* myConnection = [[VPNConnection alloc] initWithConfig: configString]; // initialize VPN Connection with config
223 [myConnection setState:@"EXITING"];
224 [myConnection setDelegate:self];
226 // handle autoconnection:
227 NSString *autoConnectKey = [[myConnection configName] stringByAppendingString: @"autoConnect"];
228 if([[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey])
229 {
230 if(![myConnection isConnected]) [myConnection connect:self];
231 }
233 [myVPNConnectionDictionary setObject: myConnection forKey:configString];
235 // Note: The item's title will be set on demand in -validateMenuItem
236 [connectionItem setTarget:myConnection];
237 [connectionItem setAction:@selector(toggle:)];
239 [myVPNMenu insertItem:connectionItem atIndex:i];
240 i++;
241 }
243 [myVPNMenu addItem: [NSMenuItem separatorItem]];
244 [myVPNMenu addItem: detailsItem];
245 [myVPNMenu addItem: quitItem];
247 // Localize all menu items:
248 NSMenuItem *item;
249 NSEnumerator *e = [[myVPNMenu itemArray] objectEnumerator];
251 while (item = [e nextObject])
252 {
253 [item setTitle:NSLocalizedString([item title], nil)];
254 }
255 }
257 - (void)activateStatusMenu
258 {
259 //[theItem retain];
260 [self updateUI];
262 // If any config files were changed/added/deleted, update the menu
263 // We don't do it UNLESS files were changed/added/deleted because all connections are reset when the menu is updated.
264 // activateStatusMenu is called whenever anything changes in the config directory, even the file-accessed date,
265 // so a backup of the directory, for example, would cause disconnects if we always updated the menu.
266 NSArray * curConfigsArray = [[self getConfigs] sortedArrayUsingSelector:@selector(compare:)];
267 NSArray * curModDatesArray = [self getModDates:curConfigsArray];
269 if ( ! ( [myConfigArray isEqualToArray:curConfigsArray]
270 && [myConfigModDatesArray isEqualToArray:curModDatesArray] ) ) {
271 NSLog(@"One or more configuration files were changed, added, or deleted. All connections will be closed.\n");
272 [self updateMenu];
273 }
274 }
276 - (void)connectionStateDidChange:(id)connection
277 {
278 [self updateTabLabels];
279 if (connection == [self selectedConnection])
280 {
281 [self validateLogButtons];
282 }
283 }
285 -(NSArray *)getConfigs {
286 int i = 0;
287 NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
288 NSString *file;
289 NSString *confDir = [NSHomeDirectory() stringByAppendingPathComponent: @"/Library/openvpn"];
290 NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: confDir];
291 while (file = [dirEnum nextObject]) {
292 if ([[file pathExtension] isEqualToString: @"conf"] || [[file pathExtension] isEqualToString: @"ovpn"]) {
293 [array insertObject:file atIndex:i];
294 //if(NSDebugEnabled) NSLog(@"Object: %@ atIndex: %d\n",file,i);
295 i++;
296 }
297 }
298 return array;
299 }
301 // Returns an array of modification date strings
302 // Each entry in the array is the modification date of the file in the corresponding entry in fileArray
303 -(NSArray *)getModDates:(NSArray *)fileArray {
304 int i;
305 NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
306 NSString *file;
307 NSString *cfgDirSlash = [NSHomeDirectory() stringByAppendingString: @"/Library/openvpn/"];
308 NSString *filePath;
309 NSDate *modDate;
310 NSString *modDateS;
311 NSFileManager *fileManager = [NSFileManager defaultManager];
312 for (i=0; i<[fileArray count]; i++) {
313 file = [fileArray objectAtIndex:i];
314 filePath = [cfgDirSlash stringByAppendingString:file];
315 modDate = [[fileManager fileAttributesAtPath:filePath traverseLink:YES] fileModificationDate];
316 if (modDate == nil) {
317 modDateS = @"";
318 } else if ( (modDateS = [modDate description]) == nil ) {
319 modDateS = @"";
320 }
321 [array insertObject:modDateS atIndex:i];
322 }
323 return array;
324 }
326 - (IBAction)validateLogButtons
327 {
328 //NSLog(@"validating log buttons");
329 VPNConnection* connection = [self selectedConnection];
330 [connectButton setEnabled:[connection isDisconnected]];
331 [disconnectButton setEnabled:(![connection isDisconnected])];
332 [[NSUserDefaults standardUserDefaults] synchronize];
333 NSString *autoConnectKey = [[connection configName] stringByAppendingString:@"autoConnect"];
334 if([[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey]) {
335 [autoLaunchCheckbox setState:NSOnState];
336 } else {
337 [autoLaunchCheckbox setState:NSOffState];
338 }
340 BOOL lol = useDNSStatus(connection);
341 if(lol) {
342 [useNameserverCheckbox setState:NSOnState];
343 } else {
344 [useNameserverCheckbox setState:NSOffState];
345 }
346 }
348 -(void)updateTabLabels
349 {
350 NSArray *keyArray = [[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)];
351 NSArray *myConnectionArray = [myVPNConnectionDictionary objectsForKeys:keyArray notFoundMarker:[NSNull null]];
352 NSEnumerator *connectionEnumerator = [myConnectionArray objectEnumerator];
353 VPNConnection *myConnection;
355 // Get preferences for showing duration times
356 BOOL showAllDurations = FALSE;
357 BOOL showConnectedDurations = TRUE;
358 id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showAllDurations"];
359 if(tmp != nil) {
360 showAllDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllDurations"];
361 }
362 tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showConnectedDurations"];
363 if(tmp != nil) {
364 showConnectedDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showConnectedDurations"];
365 }
367 int i = 0;
368 while(myConnection = [connectionEnumerator nextObject]) {
369 //NSLog(@"configName: %@\nconnectionState: %@\n",[myConnection configName],[myConnection state]);
370 NSString * cState = [myConnection state];
371 NSString * cTimeS = @"";
373 // Get connection duration if preferences say to
374 if ( showAllDurations || ( showConnectedDurations && [cState isEqualToString: @"CONNECTED"] ) ) {
375 NSDate * csd = [myConnection connectedSinceDate];
376 NSTimeInterval ti = [csd timeIntervalSinceNow];
377 long cTimeL = (long) round(-ti);
378 if ( cTimeL >= 0 ) {
379 if ( cTimeL < 3600 ) {
380 cTimeS = [NSString stringWithFormat:@" %li:%02li", cTimeL/60, cTimeL%60];
381 } else {
382 cTimeS = [NSString stringWithFormat:@" %li:%02li:%02li", cTimeL/3600, (cTimeL/60) % 60, cTimeL%60];
383 }
384 }
385 }
386 NSString *label = [NSString stringWithFormat:@"%@ (%@%@)",[myConnection configName],NSLocalizedString(cState, nil), cTimeS];
387 [[tabView tabViewItemAtIndex:i] setLabel:label];
388 i++;
389 }
390 }
393 - (void) updateUI
394 {
395 unsigned connectionNumber = [connectionArray count];
396 NSString *myState;
397 if(connectionNumber == 1) {
398 myState = local(@"OpenVPN: 1 connection active.");
399 } else {
400 myState = [NSString stringWithFormat:NSLocalizedString(@"OpenVPN: %d connections active.", nil),connectionNumber];
401 }
403 [statusMenuItem setTitle: myState];
404 [theItem setToolTip: myState];
406 if( (![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"]) ) {
407 // override while in transitional state
408 // Any other state shows "transitional" image:
409 //[theItem setImage: transitionalImage];
410 if (![theAnim isAnimating])
411 {
412 //NSLog(@"Starting Animation");
413 [theAnim startAnimation];
414 }
415 } else
416 {
417 if ([theAnim isAnimating])
418 {
419 [theAnim stopAnimation];
420 }
421 }
422 if (connectionNumber > 0 ) {
423 [theItem setImage: connectedImage];
424 } else {
425 [theItem setImage: mainImage];
426 }
427 }
429 - (void)animationDidEnd:(NSAnimation*)animation
430 {
431 if ((![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"]))
432 {
433 // NSLog(@"Starting Animation (2)");
434 [theAnim startAnimation];
435 }
436 if ([connectionArray count] > 0 ) {
437 [theItem setImage: connectedImage];
438 } else {
439 [theItem setImage: mainImage];
440 }
441 }
443 - (void)animation:(NSAnimation *)animation
444 didReachProgressMark:(NSAnimationProgress)progress
445 {
446 if (animation == theAnim)
447 {
448 // NSLog(@"progress is %f %i", progress, lround(progress * 8));
449 // Do an effect appropriate to progress mark.
450 switch(lround(progress * 8))
451 {
452 case 1:
453 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:mainImage waitUntilDone:YES];
454 break;
456 case 2:
457 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES];
458 break;
460 case 3:
461 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES];
462 break;
464 case 4:
465 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES];
466 break;
468 case 5:
469 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:connectedImage waitUntilDone:YES];
470 break;
472 case 6:
473 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES];
474 break;
476 case 7:
477 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES];
478 break;
480 case 8:
481 [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES];
482 break;
484 default:
485 NSLog(@"Unknown progress mark %f selected by Tunnelblick animation", progress);
486 }
487 }
488 }
490 - (void) tabView: (NSTabView*) inTabView willSelectTabViewItem: (NSTabViewItem*) tabViewItem
491 {
492 NSView* view = [[inTabView selectedTabViewItem] view];
493 [tabViewItem setView: view];
494 [[[self selectedLogView] textStorage] setDelegate: nil];
495 }
497 - (void) tabView: (NSTabView*) inTabView didSelectTabViewItem: (NSTabViewItem*) tabViewItem
498 {
499 VPNConnection* newConnection = [self selectedConnection];
500 NSTextView* logView = [self selectedLogView];
501 [[logView layoutManager] replaceTextStorage: [newConnection logStorage]];
502 //[logView setSelectedRange: NSMakeRange([[logView textStorage] length],[[logView textStorage] length])];
503 [logView scrollRangeToVisible: NSMakeRange([[logView string] length]-1, 0)];
505 [[logView textStorage] setDelegate: self];
507 [self validateLogButtons];
508 }
509 - (void) textStorageDidProcessEditing: (NSNotification*) aNotification
510 {
511 NSNotification *notification = [NSNotification notificationWithName: @"LogDidChange"
512 object: [self selectedLogView]];
513 [[NSNotificationQueue defaultQueue] enqueueNotification: notification
514 postingStyle: NSPostWhenIdle
515 coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender
516 forModes: nil];
517 }
519 - (void) logNeedsScrolling: (NSNotification*) aNotification
520 {
521 NSTextView* textView = [aNotification object];
522 [textView scrollRangeToVisible: NSMakeRange([[textView string] length]-1, 0)];
523 }
525 - (NSTextView*) selectedLogView
526 {
527 NSTextView* result = [[[[[tabView selectedTabViewItem] view] subviews] lastObject] documentView];
528 return result;
529 }
531 - (IBAction) clearLog: (id) sender
532 {
533 NSString * versionInfo = [NSString stringWithFormat:local(@"Tunnelblick version %@"),[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
534 NSCalendarDate* date = [NSCalendarDate date];
535 NSString *dateText = [NSString stringWithFormat:@"%@ %@\n",[date descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S"],versionInfo];
536 [[self selectedLogView] setString: [[[NSString alloc] initWithString: dateText] autorelease]];
537 }
539 - (VPNConnection*) selectedConnection
540 /*" Returns the connection associated with the currently selected log tab or nil on error. "*/
541 {
542 if (![tabView selectedTabViewItem]) {
543 [tabView selectFirstTabViewItem: nil];
544 }
546 NSString* configPath = [[tabView selectedTabViewItem] identifier];
547 VPNConnection* connection = [myVPNConnectionDictionary objectForKey:configPath];
548 NSArray *allConnections = [myVPNConnectionDictionary allValues];
549 if(connection) return connection;
550 else if([allConnections count]) return [allConnections objectAtIndex:0] ;
551 else return nil;
552 }
557 - (IBAction)connect:(id)sender
558 {
559 [[self selectedConnection] connect: sender];
560 }
562 - (IBAction)disconnect:(id)sender
563 {
564 [[self selectedConnection] disconnect: sender];
565 }
568 - (IBAction) openLogWindow: (id) sender
569 {
570 if (logWindow != nil) {
571 [logWindow makeKeyAndOrderFront: self];
572 [NSApp activateIgnoringOtherApps:YES];
573 return;
574 }
576 [NSBundle loadNibNamed: @"LogWindow" owner: self]; // also sets tabView etc.
578 // Set the window's size and position from preferences (saved when window is closed)
579 // But only if the preference's version matches the TB version (since window size could be different in different versions of TB)
580 NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
581 id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"];
582 if (tmp != nil) {
583 if ( [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]] ) {
584 tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"];
585 if(tmp != nil) {
586 NSString * frame = [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"];
587 [logWindow setFrameFromString:frame];
588 }
589 }
590 }
592 [logWindow setDelegate:self];
593 VPNConnection *myConnection = [self selectedConnection];
594 NSTextStorage* store = [myConnection logStorage];
595 [[[self selectedLogView] layoutManager] replaceTextStorage: store];
597 NSEnumerator* e = [[[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)] objectEnumerator];
598 //id test = [[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)];
599 NSTabViewItem* initialItem;
600 VPNConnection* connection = [myVPNConnectionDictionary objectForKey: [e nextObject]];
601 if (connection) {
602 initialItem = [tabView tabViewItemAtIndex: 0];
603 [initialItem setIdentifier: [connection configPath]];
604 [initialItem setLabel: [connection configName]];
606 int curTabIndex = 0;
607 [tabView selectTabViewItemAtIndex:0];
608 BOOL haveOpenConnection = ! [connection isDisconnected];
609 while (connection = [myVPNConnectionDictionary objectForKey: [e nextObject]]) {
610 NSTabViewItem* newItem = [[NSTabViewItem alloc] init];
611 [newItem setIdentifier: [connection configPath]];
612 [newItem setLabel: [connection configName]];
613 [tabView addTabViewItem: newItem];
614 ++curTabIndex;
615 if ( ( ! haveOpenConnection ) && ( ! [connection isDisconnected] ) ) {
616 [tabView selectTabViewItemAtIndex:curTabIndex];
617 haveOpenConnection = YES;
618 }
619 }
620 }
621 [self tabView:tabView didSelectTabViewItem:initialItem];
622 [self validateLogButtons];
623 [self updateTabLabels];
625 // Set up a timer to update the tab labels with connections' duration times
626 BOOL showAllDurations = FALSE;
627 BOOL showConnectedDurations = TRUE;
628 tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showAllDurations"];
629 if(tmp != nil) {
630 showAllDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllDurations"];
631 }
632 tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showConnectedDurations"];
633 if(tmp != nil) {
634 showConnectedDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showConnectedDurations"];
635 }
637 if ( (showDurationsTimer == nil) && (showAllDurations || showConnectedDurations) ) {
638 showDurationsTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0
639 target:self
640 selector:@selector(updateTabLabels)
641 userInfo:nil
642 repeats:YES] retain];
643 }
645 // Localize Buttons
646 [clearButton setTitle:local([clearButton title])];
647 [editButton setTitle:local([editButton title])];
648 [connectButton setTitle:local([connectButton title])];
649 [disconnectButton setTitle:local([disconnectButton title])];
650 [useNameserverCheckbox setTitle:local([useNameserverCheckbox title])];
651 [autoLaunchCheckbox setTitle:local([autoLaunchCheckbox title])];
653 [logWindow makeKeyAndOrderFront: self];
654 [NSApp activateIgnoringOtherApps:YES];
655 }
657 // Invoked when the Details... window (logWindow) will close
658 - (void)windowWillClose:(NSNotification *)n
659 {
660 if ( [n object] == logWindow ) {
661 // Stop and release the timer used to update the duration displays
662 if (showDurationsTimer != nil) {
663 [showDurationsTimer invalidate];
664 [showDurationsTimer release];
665 showDurationsTimer = nil;
666 }
668 // Save the window's size and position in the preferences and save the TB version that saved them, BUT ONLY IF anything has changed
669 NSString * frame = [logWindow stringWithSavedFrame];
670 NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
671 BOOL saveIt = TRUE;
672 id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"];
673 if(tmp != nil) {
674 tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"];
675 if (tmp != nil) {
676 if ( [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]] ) {
677 if ( [frame isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"]] ) {
678 saveIt = FALSE;
679 }
680 }
681 }
682 }
684 if (saveIt) {
685 [[NSUserDefaults standardUserDefaults] setObject: frame forKey: @"detailsWindowFrame"];
686 [[NSUserDefaults standardUserDefaults] setObject: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
687 forKey: @"detailsWindowFrameVersion"];
688 [[NSUserDefaults standardUserDefaults] synchronize];
689 }
690 }
691 }
693 - (void) dealloc
694 {
695 [lastState release];
696 [theItem release];
697 [myConfigArray release];
699 #warning todo: release non-IB ivars here!
700 [statusMenuItem release];
701 [myVPNMenu release];
702 [userDefaults release];
703 [appDefaults release];
704 [theItem release];
706 [mainImage release];
707 [connectedImage release];
708 [errorImage release];
709 [transitionalImage release];
710 [connectionArray release];
713 [super dealloc];
714 }
717 -(void)killAllConnections
718 {
719 id connection;
720 NSEnumerator* e = [connectionArray objectEnumerator];
722 while (connection = [e nextObject]) {
723 [connection disconnect:self];
724 if(NSDebugEnabled) NSLog(@"Killing connection.\n");
725 }
726 }
728 -(void)killAllOpenVPN
729 {
730 NSString* path = [[NSBundle mainBundle] pathForResource: @"openvpnstart"
731 ofType: nil];
732 NSTask* task = [[[NSTask alloc] init] autorelease];
733 [task setLaunchPath: path];
735 NSArray *arguments = [NSArray arrayWithObjects:@"killall", nil];
736 [task setArguments:arguments];
737 [task launch];
738 [task waitUntilExit];
739 }
741 -(void)resetActiveConnections {
742 VPNConnection *connection;
743 NSEnumerator* e = [connectionArray objectEnumerator];
745 while (connection = [e nextObject]) {
746 if (NSDebugEnabled) NSLog(@"Connection %@ is connected for %f seconds\n",[connection configName],[[connection connectedSinceDate] timeIntervalSinceNow]);
747 if ([[connection connectedSinceDate] timeIntervalSinceNow] < -5) {
748 if (NSDebugEnabled) NSLog(@"Resetting connection: %@\n",[connection configName]);
749 [connection disconnect:self];
750 [connection connect:self];
751 }
752 else {
753 if (NSDebugEnabled) NSLog(@"Not Resetting connection: %@\n, waiting...",[connection configName]);
754 }
755 }
756 }
758 -(void)createDefaultConfig
759 {
760 NSFileManager *fileManager = [NSFileManager defaultManager];
761 NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"];
762 NSString *confResource = [[NSBundle mainBundle] pathForResource: @"openvpn"
763 ofType: @"conf"];
765 if([[self getConfigs] count] == 0) { // if there are no config files, create a default one
766 [NSApp activateIgnoringOtherApps:YES];
767 if(NSRunCriticalAlertPanel(local(@"Welcome to OpenVPN on Mac OS X: Please put your config file (e.g. openvpn.conf) to '~/Library/openvpn/'."), local(@"You can also continue and Tunnelblick will create an example config at the right place that you can customize or replace."),local(@"Quit"),local(@"Continue"),nil) == NSAlertDefaultReturn) {
768 exit (1);
769 }
770 else {
771 [fileManager createDirectoryAtPath:directoryPath attributes:nil];
772 [fileManager copyPath:confResource toPath:[directoryPath stringByAppendingPathComponent:@"/openvpn.conf"] handler:nil];
773 [self editConfig:self];
774 }
777 }
778 }
780 -(IBAction)editConfig:(id)sender
781 {
782 VPNConnection *connection = [self selectedConnection];
783 NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"];
784 NSString *configPath = [connection configPath];
785 if(configPath == nil) configPath = @"/openvpn.conf";
787 // NSString *helper = @"/usr/sbin/chown";
788 // NSString *userString = [NSString stringWithFormat:@"%d",getuid()];
789 // NSArray *arguments = [NSArray arrayWithObjects:userString,configPath,nil];
790 // AuthorizationRef authRef = [NSApplication getAuthorizationRef];
791 // [NSApplication executeAuthorized:helper withArguments:arguments withAuthorizationRef:authRef];
792 // AuthorizationFree(authRef,kAuthorizationFlagDefaults);
794 [[NSWorkspace sharedWorkspace] openFile:[directoryPath stringByAppendingPathComponent:configPath] withApplication:@"TextEdit"];
795 }
798 - (void) networkConfigurationDidChange
799 {
800 if (NSDebugEnabled) NSLog(@"Got networkConfigurationDidChange notification!!");
801 [self resetActiveConnections];
802 }
804 - (void) applicationWillTerminate: (NSNotification*) notification
805 {
806 if (NSDebugEnabled) NSLog(@"App will terminate...\n");
807 [self cleanup];
808 }
810 -(void)cleanup
811 {
812 [NSApp callDelegateOnNetworkChange: NO];
813 [self killAllConnections];
814 [self killAllOpenVPN];
815 [[NSStatusBar systemStatusBar] removeStatusItem:theItem];
816 }
818 -(void)saveUseNameserverCheckboxState:(BOOL)inBool
819 {
820 VPNConnection* connection = [self selectedConnection];
821 if(connection != nil) {
822 NSString* key = [[connection configName] stringByAppendingString: @"useDNS"];
823 [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: key];
824 [[NSUserDefaults standardUserDefaults] synchronize];
825 }
827 }
828 -(void)saveAutoLaunchCheckboxState:(BOOL)inBool
829 {
830 VPNConnection* connection = [self selectedConnection];
831 if(connection != nil) {
832 NSString* autoConnectKey = [[connection configName] stringByAppendingString: @"autoConnect"];
833 [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: autoConnectKey];
834 [[NSUserDefaults standardUserDefaults] synchronize];
835 }
837 }
839 -(BOOL)getCurrentAutoLaunchSetting
840 {
841 VPNConnection *connection = [self selectedConnection];
842 NSString *autoConnectKey = [[connection configName] stringByAppendingString:@"autoConnect"];
843 return [[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey];
844 }
846 - (void) setState: (NSString*) newState
847 // Be sure to call this in main thread only
848 {
849 [newState retain];
850 [lastState release];
851 lastState = newState;
852 //[self updateUI];
853 [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
854 }
856 -(void)addConnection:(id)sender
857 {
858 if(sender != nil) {
859 [connectionArray removeObject:sender];
860 [connectionArray addObject:sender];
861 }
862 }
864 -(void)removeConnection:(id)sender
865 {
866 if(sender != nil) [connectionArray removeObject:sender];
867 }
869 static void signal_handler(int signalNumber)
870 {
871 printf("signal %d caught!\n",signalNumber);
873 if (signalNumber == SIGHUP) {
874 printf("SIGHUP received. Restarting active connections...\n");
875 [[NSApp delegate] resetActiveConnections];
876 } else {
877 printf("Received fatal signal. Cleaning up...\n");
878 [[NSApp delegate] cleanup];
879 exit(0);
880 }
881 }
884 - (void) installSignalHandler
885 {
886 struct sigaction action;
888 action.sa_handler = signal_handler;
889 sigemptyset(&action.sa_mask);
890 action.sa_flags = 0;
892 if (sigaction(SIGHUP, &action, NULL) ||
893 sigaction(SIGQUIT, &action, NULL) ||
894 sigaction(SIGTERM, &action, NULL) ||
895 sigaction(SIGBUS, &action, NULL) ||
896 sigaction(SIGSEGV, &action, NULL) ||
897 sigaction(SIGPIPE, &action, NULL)) {
898 NSLog(@"Warning: setting signal handler failed: %s", strerror(errno));
899 }
900 }
901 - (void) applicationDidFinishLaunching: (NSNotification *)notification
902 {
903 [NSApp callDelegateOnNetworkChange: NO];
904 [self installSignalHandler];
905 [NSApp setAutoLaunchOnLogin: YES];
906 [self activateStatusMenu];
907 if(needsRepair()){
908 if ([self repairPermissions] != TRUE) {
909 [NSApp terminate:self];
910 }
911 }
913 [updater checkForUpdatesInBackground];
914 }
916 -(void) dmgCheck
917 {
918 NSString *path = [[NSBundle mainBundle] bundlePath];
919 if([path hasPrefix:@"/Volumes/Tunnelblick"]) {
920 NSPanel *panel = NSGetAlertPanel(local(@"You're trying to launch Tunnelblick from the disk image"),local(@"Please copy Tunnelblick.app to your Harddisk before launching it."),local(@"Cancel"),nil,nil);
921 [panel setLevel:NSStatusWindowLevel];
922 [panel makeKeyAndOrderFront:nil];
923 [NSApp runModalForWindow:panel];
924 exit(2);
925 }
926 }
928 -(void)moveAllWindowsToForeground
929 {
930 NSArray *windows = [NSApp windows];
931 NSEnumerator *e = [windows objectEnumerator];
932 NSWindow *window = nil;
933 while(window = [e nextObject]) {
934 [window setLevel:NSStatusWindowLevel];
935 }
936 }
938 -(void) fileSystemHasChanged: (NSNotification*) n
939 {
940 if(NSDebugEnabled) NSLog(@"FileSystem has changed.");
941 [self performSelectorOnMainThread: @selector(activateStatusMenu) withObject: nil waitUntilDone: YES];
942 }
943 -(void) kqueue: (UKKQueue*) kq receivedNotification: (NSString*) nm forFile: (NSString*) fpath {
945 [self fileSystemHasChanged: nil];
946 }
948 -(BOOL)repairPermissions
949 {
950 NSBundle *thisBundle = [NSBundle mainBundle];
951 NSString *installer = [thisBundle pathForResource:@"installer" ofType:nil];
953 AuthorizationRef authRef= [NSApplication getAuthorizationRef];
955 if(authRef == nil)
956 return FALSE;
958 while(needsRepair()) {
959 NSLog(@"Repairing Application...\n");
960 [NSApplication executeAuthorized:installer withArguments:nil withAuthorizationRef:authRef];
961 sleep(1);
962 }
963 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
964 return TRUE;
965 }
970 BOOL needsRepair()
971 {
972 NSBundle *thisBundle = [NSBundle mainBundle];
973 NSString *helperPath = [thisBundle pathForResource:@"openvpnstart" ofType:nil];
974 NSString *tunPath = [thisBundle pathForResource:@"tun.kext" ofType:nil];
975 NSString *tapPath = [thisBundle pathForResource:@"tap.kext" ofType:nil];
977 NSString *tunExecutable = [tunPath stringByAppendingPathComponent:@"/Contents/MacOS/tun"];
978 NSString *tapExecutable = [tapPath stringByAppendingPathComponent:@"/Contents/MacOS/tap"];
979 NSString *openvpnPath = [thisBundle pathForResource:@"openvpn" ofType:nil];
982 // check setuid helper
983 const char *path = [helperPath UTF8String];
984 struct stat sb;
985 if(stat(path,&sb)) runUnrecoverableErrorPanel();
987 if (!( (sb.st_mode & S_ISUID) // set uid bit is set
988 && (sb.st_mode & S_IXUSR) // owner may execute it
989 && (sb.st_uid == 0) // is owned by root
990 )) {
991 NSLog(@"openvpnstart helper has missing set uid bit");
992 return YES;
993 }
995 // check files which should be only accessible by root
996 NSArray *inaccessibleObjects = [NSArray arrayWithObjects:tunExecutable,tapExecutable,openvpnPath,nil];
997 NSEnumerator *e = [inaccessibleObjects objectEnumerator];
998 NSString *currentPath;
999 NSFileManager *fileManager = [NSFileManager defaultManager];
1000 while(currentPath = [e nextObject]) {
1001 NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:currentPath traverseLink:YES];
1002 unsigned long perms = [fileAttributes filePosixPermissions];
1003 NSString *octalString = [NSString stringWithFormat:@"%o",perms];
1004 NSNumber *fileOwner = [fileAttributes fileOwnerAccountID];
1006 if ( (![octalString isEqualToString:@"755"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) {
1007 NSLog(@"File %@ has permissions: %@, is owned by %@ and needs repair...\n",currentPath,octalString,fileOwner);
1008 return YES;
1009 }
1010 }
1012 // check tun and tap driver packages
1013 NSArray *filesToCheck = [NSArray arrayWithObjects:tunPath,tapPath,nil];
1014 NSEnumerator *enumerator = [filesToCheck objectEnumerator];
1015 NSString *file;
1016 while(file = [enumerator nextObject]) {
1017 NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:file traverseLink:YES];
1018 unsigned long perms = [fileAttributes filePosixPermissions];
1019 NSString *octalString = [NSString stringWithFormat:@"%o",perms];
1020 if ( (![octalString isEqualToString:@"755"]) ) {
1021 NSLog(@"File %@ has permissions: %@ and needs repair...\n",currentPath,octalString);
1022 return YES;
1023 }
1024 }
1025 return NO;
1026 }
1028 -(void)willGoToSleep
1029 {
1030 if(NSDebugEnabled) NSLog(@"Computer will go to sleep...\n");
1031 connectionsToRestore = [connectionArray mutableCopy];
1032 [self killAllConnections];
1033 }
1034 -(void)wokeUpFromSleep
1035 {
1036 if(NSDebugEnabled) NSLog(@"Computer just woke up from sleep...\n");
1038 NSEnumerator *e = [connectionsToRestore objectEnumerator];
1039 VPNConnection *connection;
1040 while(connection = [e nextObject]) {
1041 if(NSDebugEnabled) NSLog(@"Restoring Connection %@",[connection configName]);
1042 [connection connect:self];
1043 }
1044 }
1045 int runUnrecoverableErrorPanel(void)
1046 {
1047 NSPanel *panel = NSGetAlertPanel(local(@"Tunnelblick Error"),local(@"It seems like you need to reinstall Tunnelblick. Please move Tunnelblick to the Trash and download a fresh copy."),local(@"Download"),local(@"Quit"),nil);
1048 [panel setLevel:NSStatusWindowLevel];
1049 [panel makeKeyAndOrderFront:nil];
1050 if( [NSApp runModalForWindow:panel] != NSAlertDefaultReturn ) {
1051 exit(2);
1052 } else {
1053 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://tunnelblick.net/"]];
1054 exit(2);
1055 }
1056 }
1058 -(IBAction) autoLaunchPrefButtonWasClicked: (id) sender
1059 {
1060 if([sender state]) {
1061 [self saveAutoLaunchCheckboxState:TRUE];
1062 } else {
1063 [self saveAutoLaunchCheckboxState:FALSE];
1064 }
1065 }
1067 -(IBAction) nameserverPrefButtonWasClicked: (id) sender
1068 {
1069 if([sender state]) {
1070 [self saveUseNameserverCheckboxState:TRUE];
1071 } else {
1072 [self saveUseNameserverCheckboxState:FALSE];
1073 }
1074 }
1077 @end