tunblick/MenuController.m

Mon, 16 Jan 2012 23:08:14 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 16 Jan 2012 23:08:14 +0100
changeset 23
d783b433388d
parent 7
0c0e4024a98e
permissions
-rw-r--r--

Inconclusively complete possibly missing fields. This change introduces
inconsistencies difficult to correct given incomplete documentation of
IPKG and OPKG packaging standards.

     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