tunblick/MenuController.m

Wed, 10 Feb 2010 21:25:01 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 10 Feb 2010 21:25:01 +0100
changeset 18
8ec65b8f6e2c
parent 7
0c0e4024a98e
permissions
-rw-r--r--

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;
  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;
  1025 	return NO;
  1028 -(void)willGoToSleep
  1030 	if(NSDebugEnabled) NSLog(@"Computer will go to sleep...\n");
  1031 	connectionsToRestore = [connectionArray mutableCopy];
  1032 	[self killAllConnections];
  1034 -(void)wokeUpFromSleep 
  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];
  1045 int runUnrecoverableErrorPanel(void) 
  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);
  1058 -(IBAction) autoLaunchPrefButtonWasClicked: (id) sender
  1060 	if([sender state]) {
  1061 		[self saveAutoLaunchCheckboxState:TRUE];
  1062 	} else {
  1063 		[self saveAutoLaunchCheckboxState:FALSE];
  1067 -(IBAction) nameserverPrefButtonWasClicked: (id) sender
  1069 	if([sender state]) {
  1070 		[self saveUseNameserverCheckboxState:TRUE];
  1071 	} else {
  1072 		[self saveUseNameserverCheckboxState:FALSE];
  1077 @end

mercurial