tunblick/MenuController.m

changeset 1
1a5334dfb21d
child 6
fe330a707a9f
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/tunblick/MenuController.m	Wed Jul 29 11:23:17 2009 +0200
     1.3 @@ -0,0 +1,1077 @@
     1.4 +/*
     1.5 + * Copyright (c) 2004 Angelo Laub
     1.6 + * Contributions by Dirk Theisen <dirk@objectpark.org>, 
     1.7 + *                  Jens Ohlig, 
     1.8 + *                  Waldemar Brodkorb
     1.9 + *
    1.10 + *  This program is free software; you can redistribute it and/or modify
    1.11 + *  it under the terms of the GNU General Public License version 2
    1.12 + *  as published by the Free Software Foundation.
    1.13 + *
    1.14 + *  This program is distributed in the hope that it will be useful,
    1.15 + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.16 + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.17 + *  GNU General Public License for more details.
    1.18 + *
    1.19 + *  You should have received a copy of the GNU General Public License
    1.20 + *  along with this program (see the file COPYING included with this
    1.21 + *  distribution); if not, write to the Free Software Foundation, Inc.,
    1.22 + *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    1.23 + */
    1.24 +
    1.25 +
    1.26 +#import "MenuController.h"
    1.27 +#import "NSApplication+NetworkNotifications.h"
    1.28 +#import "helper.h"
    1.29 +
    1.30 +
    1.31 +#define NSAppKitVersionNumber10_0 577
    1.32 +#define NSAppKitVersionNumber10_1 620
    1.33 +#define NSAppKitVersionNumber10_2 663
    1.34 +#define NSAppKitVersionNumber10_3 743
    1.35 +
    1.36 +
    1.37 +
    1.38 +BOOL systemIsTigerOrNewer()
    1.39 +{
    1.40 +    return (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_3) ;
    1.41 +}
    1.42 +
    1.43 +@interface NSStatusBar (NSStatusBar_Private)
    1.44 +- (id)_statusItemWithLength:(float)l withPriority:(int)p;
    1.45 +@end
    1.46 +
    1.47 +
    1.48 +@implementation MenuController
    1.49 +
    1.50 +- (void) createStatusItem
    1.51 +{
    1.52 +	NSStatusBar *bar = [NSStatusBar systemStatusBar];
    1.53 +	int priority = INT32_MAX;
    1.54 +	if (systemIsTigerOrNewer()) {
    1.55 +		priority = MIN(priority, 2147483646); // found by experimenting - dirk
    1.56 +	}
    1.57 +	
    1.58 +	if (!theItem) {
    1.59 +		theItem = [[bar _statusItemWithLength: NSVariableStatusItemLength withPriority: priority] retain];
    1.60 +		//theItem = [[bar _statusItemWithLength: NSVariableStatusItemLength withPriority: 0] retain];
    1.61 +	}
    1.62 +	// Dirk: For Tiger and up, re-insert item to place it correctly.
    1.63 +	if ([bar respondsToSelector: @selector(_insertStatusItem:withPriority:)]) {
    1.64 +		[bar removeStatusItem: theItem];
    1.65 +		[bar _insertStatusItem: theItem withPriority: priority];
    1.66 +	}	
    1.67 +}
    1.68 +
    1.69 +-(id) init
    1.70 +{	
    1.71 +    if (self = [super init]) {
    1.72 +        [self dmgCheck];
    1.73 +		
    1.74 +        errorImage = [[NSImage imageNamed: @"error.tif"] retain];
    1.75 +        mainImage = [[NSImage imageNamed: @"00_closed.tif"] retain];
    1.76 +        connectedImage = [[NSImage imageNamed: @"connected.png"] retain];
    1.77 +        
    1.78 +		
    1.79 +		transitionalImage1 = [[NSImage imageNamed: @"01.tif"] retain];
    1.80 +		transitionalImage2 = [[NSImage imageNamed: @"02.tif"] retain];
    1.81 +		transitionalImage3 = [[NSImage imageNamed: @"03.tif"] retain];
    1.82 +		[NSApp setDelegate:self];
    1.83 +		
    1.84 +        myVPNConnectionDictionary = [[NSMutableDictionary alloc] init];
    1.85 +        myVPNConnectionArray = [[[NSMutableArray alloc] init] retain];
    1.86 +        userDefaults = [[NSMutableDictionary alloc] init];
    1.87 +        
    1.88 +        connectionArray = [[[NSMutableArray alloc] init] retain];
    1.89 +        appDefaults = [NSUserDefaults standardUserDefaults];
    1.90 +        [appDefaults registerDefaults:userDefaults];
    1.91 +		
    1.92 +		
    1.93 +		detailsItem = [[NSMenuItem alloc] init];
    1.94 +		[detailsItem setTitle: @"Details..."];
    1.95 +		[detailsItem setTarget: self];
    1.96 +		[detailsItem setAction: @selector(openLogWindow:)];
    1.97 +		
    1.98 +		quitItem = [[NSMenuItem alloc] init];
    1.99 +		[quitItem setTitle: @"Quit"]; 
   1.100 +		[quitItem setTarget: self];
   1.101 +		[quitItem setAction: @selector(quit:)];
   1.102 +        
   1.103 +		[self createStatusItem];
   1.104 +		
   1.105 +		[self updateMenu];
   1.106 +        [self setState: @"EXITING"]; // synonym for "Disconnected"
   1.107 +        
   1.108 +        [[NSNotificationCenter defaultCenter] addObserver: self 
   1.109 +                                                 selector: @selector(logNeedsScrolling:) 
   1.110 +                                                     name: @"LogDidChange" 
   1.111 +                                                   object: nil];
   1.112 +		
   1.113 +		// In case the systemUIServer restarts, we observed this notification.
   1.114 +		// We use it to prevent to end up with a statusItem right of Spotlight:
   1.115 +		[[NSDistributedNotificationCenter defaultCenter] addObserver: self 
   1.116 +															selector: @selector(menuExtrasWereAdded:) 
   1.117 +																name: @"com.apple.menuextra.added" 
   1.118 +															  object: nil];
   1.119 +		[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
   1.120 +															   selector: @selector(willGoToSleep)
   1.121 +																   name: @"NSWorkspaceWillSleepNotification"
   1.122 +																 object:nil];
   1.123 +		
   1.124 +		[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
   1.125 +															   selector: @selector(wokeUpFromSleep)
   1.126 +																   name: @"NSWorkspaceDidWakeNotification"
   1.127 +																 object:nil];
   1.128 +		
   1.129 +		NSString* vpnDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn/"];
   1.130 +		
   1.131 +		UKKQueue* myQueue = [UKKQueue sharedQueue];
   1.132 +		[myQueue addPathToQueue: vpnDirectory];
   1.133 +		[myQueue setDelegate: self];
   1.134 +		[myQueue setAlwaysNotify: YES];
   1.135 +		
   1.136 +		[NSThread detachNewThreadSelector:@selector(moveAllWindowsToForegroundThread) toTarget:self withObject:nil];
   1.137 +		
   1.138 +		updater = [[SUUpdater alloc] init];
   1.139 +
   1.140 +	}
   1.141 +    return self;
   1.142 +}
   1.143 +
   1.144 +-(void)moveAllWindowsToForegroundThread {
   1.145 +	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   1.146 +	sleep(3);
   1.147 +	[self moveAllWindowsToForeground];
   1.148 +//	[NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(moveAllWindowsToForeground) userInfo: nil repeats: YES];
   1.149 +	[pool release];
   1.150 +	
   1.151 +}
   1.152 +
   1.153 +- (void) menuExtrasWereAdded: (NSNotification*) n
   1.154 +{
   1.155 +	[self createStatusItem];
   1.156 +}
   1.157 +
   1.158 +
   1.159 +
   1.160 +- (IBAction) quit: (id) sender
   1.161 +{
   1.162 +    // Remove us from the login items if terminates manually...
   1.163 +    [NSApp setAutoLaunchOnLogin: NO];
   1.164 +    [NSApp terminate: sender];
   1.165 +}
   1.166 +
   1.167 +
   1.168 +
   1.169 +- (void) awakeFromNib
   1.170 +{
   1.171 +	[self createDefaultConfig];
   1.172 +	[self initialiseAnim];
   1.173 +}
   1.174 +
   1.175 +- (void) initialiseAnim
   1.176 +{
   1.177 +	NSAnimationProgress progMarks[] = {
   1.178 +		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
   1.179 +	};
   1.180 +	
   1.181 +	int i, count = 8;
   1.182 +	// theAnim is an NSAnimation instance variable
   1.183 +	theAnim = [[NSAnimation alloc] initWithDuration:2.0
   1.184 +									 animationCurve:NSAnimationLinear];
   1.185 +	[theAnim setFrameRate:8.0];
   1.186 +	[theAnim setDelegate:self];
   1.187 +	
   1.188 +	for (i=0; i<count; i++)
   1.189 +		[theAnim addProgressMark:progMarks[i]];
   1.190 +	
   1.191 +	[theAnim setAnimationBlockingMode:  NSAnimationNonblocking];
   1.192 +}
   1.193 +
   1.194 +-(void) updateMenu 
   1.195 +{	
   1.196 +    [theItem setHighlightMode:YES];
   1.197 +    [theItem setMenu:nil];
   1.198 +	[myVPNMenu dealloc]; myVPNMenu = nil;
   1.199 +	[[myVPNConnectionDictionary allValues] makeObjectsPerformSelector:@selector(disconnect:) withObject:self];
   1.200 +	[myVPNConnectionDictionary removeAllObjects];
   1.201 +	
   1.202 +	myVPNMenu = [[NSMenu alloc] init];
   1.203 +    [myVPNMenu setDelegate:self];
   1.204 +    
   1.205 +	[theItem setMenu: myVPNMenu];
   1.206 +	
   1.207 +	statusMenuItem = [[NSMenuItem alloc] init];
   1.208 +	[myVPNMenu addItem:statusMenuItem];
   1.209 +	[myVPNMenu addItem:[NSMenuItem separatorItem]];
   1.210 +
   1.211 +	[myConfigArray release];
   1.212 +    myConfigArray = [[[self getConfigs] sortedArrayUsingSelector:@selector(compare:)] retain];
   1.213 +    [myConfigModDatesArray release];
   1.214 +    myConfigModDatesArray = [[self getModDates:myConfigArray] retain];
   1.215 +    
   1.216 +	NSEnumerator *m = [myConfigArray objectEnumerator];
   1.217 +	NSString *configString;
   1.218 +    int i = 2; // we start at MenuItem #2
   1.219 +	
   1.220 +    while (configString = [m nextObject]) 
   1.221 +    {
   1.222 +		NSMenuItem *connectionItem = [[[NSMenuItem alloc] init] autorelease];
   1.223 +		
   1.224 +        // configure connection object:
   1.225 +		VPNConnection* myConnection = [[VPNConnection alloc] initWithConfig: configString]; // initialize VPN Connection with config	
   1.226 +		[myConnection setState:@"EXITING"];
   1.227 +		[myConnection setDelegate:self];
   1.228 +        
   1.229 +        // handle autoconnection:
   1.230 +		NSString *autoConnectKey = [[myConnection configName] stringByAppendingString: @"autoConnect"];
   1.231 +		if([[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey]) 
   1.232 +        {
   1.233 +			if(![myConnection isConnected]) [myConnection connect:self];
   1.234 +        }
   1.235 +        
   1.236 +		[myVPNConnectionDictionary setObject: myConnection forKey:configString];
   1.237 +		
   1.238 +        // Note: The item's title will be set on demand in -validateMenuItem
   1.239 +		[connectionItem setTarget:myConnection]; 
   1.240 +		[connectionItem setAction:@selector(toggle:)];
   1.241 +		
   1.242 +		[myVPNMenu insertItem:connectionItem atIndex:i];
   1.243 +		i++;
   1.244 +	}
   1.245 +	
   1.246 +	[myVPNMenu addItem: [NSMenuItem separatorItem]];
   1.247 +	[myVPNMenu addItem: detailsItem];
   1.248 +	[myVPNMenu addItem: quitItem];
   1.249 +    
   1.250 +    // Localize all menu items:
   1.251 +    NSMenuItem *item;
   1.252 +    NSEnumerator *e = [[myVPNMenu itemArray] objectEnumerator];
   1.253 +    
   1.254 +    while (item = [e nextObject]) 
   1.255 +    {
   1.256 +        [item setTitle:local([item title])];
   1.257 +    }
   1.258 +}
   1.259 +
   1.260 +- (void)activateStatusMenu
   1.261 +{
   1.262 +	//[theItem retain];
   1.263 +    [self updateUI];
   1.264 +    
   1.265 +	// If any config files were changed/added/deleted, update the menu
   1.266 +    // We don't do it UNLESS files were changed/added/deleted because all connections are reset when the menu is updated.
   1.267 +    // activateStatusMenu is called whenever anything changes in the config directory, even the file-accessed date,
   1.268 +    // so a backup of the directory, for example, would cause disconnects if we always updated the menu.
   1.269 +    NSArray * curConfigsArray = [[self getConfigs] sortedArrayUsingSelector:@selector(compare:)];
   1.270 +    NSArray * curModDatesArray = [self getModDates:curConfigsArray];
   1.271 +    
   1.272 +    if ( ! (   [myConfigArray isEqualToArray:curConfigsArray]
   1.273 +            && [myConfigModDatesArray isEqualToArray:curModDatesArray]  )  ) {
   1.274 +        NSLog(@"One or more configuration files were changed, added, or deleted. All connections will be closed.\n");
   1.275 +        [self updateMenu];
   1.276 +    }
   1.277 +}
   1.278 +
   1.279 +- (void)connectionStateDidChange:(id)connection
   1.280 +{
   1.281 +	[self updateTabLabels];
   1.282 +    if (connection == [self selectedConnection]) 
   1.283 +	{
   1.284 +		[self validateLogButtons];
   1.285 +	}	
   1.286 +}
   1.287 +
   1.288 +-(NSArray *)getConfigs {
   1.289 +    int i = 0;  	
   1.290 +    NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
   1.291 +    NSString *file;
   1.292 +    NSString *confDir = [NSHomeDirectory() stringByAppendingPathComponent: @"/Library/openvpn"];
   1.293 +    NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: confDir];
   1.294 +    while (file = [dirEnum nextObject]) {
   1.295 +        if ([[file pathExtension] isEqualToString: @"conf"] || [[file pathExtension] isEqualToString: @"ovpn"]) {
   1.296 +			[array insertObject:file atIndex:i];
   1.297 +			//if(NSDebugEnabled) NSLog(@"Object: %@ atIndex: %d\n",file,i);
   1.298 +			i++;
   1.299 +        }
   1.300 +    }
   1.301 +    return array;
   1.302 +}
   1.303 +
   1.304 +// Returns an array of modification date strings
   1.305 +// Each entry in the array is the modification date of the file in the corresponding entry in fileArray
   1.306 +-(NSArray *)getModDates:(NSArray *)fileArray {
   1.307 +    int i;
   1.308 +    NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
   1.309 +    NSString *file;
   1.310 +    NSString *cfgDirSlash = [NSHomeDirectory() stringByAppendingString: @"/Library/openvpn/"];
   1.311 +    NSString *filePath;
   1.312 +    NSDate *modDate;
   1.313 +    NSString *modDateS;
   1.314 +	NSFileManager *fileManager = [NSFileManager defaultManager];
   1.315 +    for (i=0; i<[fileArray count]; i++) {
   1.316 +		file = [fileArray objectAtIndex:i];
   1.317 +        filePath = [cfgDirSlash stringByAppendingString:file];
   1.318 +        modDate = [[fileManager fileAttributesAtPath:filePath traverseLink:YES] fileModificationDate];
   1.319 +        if (modDate == nil) {
   1.320 +            modDateS = @"";
   1.321 +        } else if (   (modDateS = [modDate description]) == nil  )  {
   1.322 +            modDateS = @"";
   1.323 +        }
   1.324 +        [array insertObject:modDateS atIndex:i];
   1.325 +    }
   1.326 +    return array;
   1.327 +}
   1.328 +
   1.329 +- (IBAction)validateLogButtons
   1.330 +{
   1.331 +    //NSLog(@"validating log buttons");
   1.332 +    VPNConnection* connection = [self selectedConnection];
   1.333 +    [connectButton setEnabled:[connection isDisconnected]];
   1.334 +    [disconnectButton setEnabled:(![connection isDisconnected])];
   1.335 +	[[NSUserDefaults standardUserDefaults] synchronize];
   1.336 +	NSString *autoConnectKey = [[connection configName] stringByAppendingString:@"autoConnect"];
   1.337 +	if([[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey]) {
   1.338 +		[autoLaunchCheckbox setState:NSOnState];
   1.339 +	} else {
   1.340 +		[autoLaunchCheckbox setState:NSOffState];
   1.341 +	}
   1.342 +	
   1.343 +	BOOL lol = useDNSStatus(connection);
   1.344 +	if(lol) {
   1.345 +		[useNameserverCheckbox setState:NSOnState];
   1.346 +	} else {
   1.347 +		[useNameserverCheckbox setState:NSOffState];
   1.348 +	}
   1.349 +}
   1.350 +
   1.351 +-(void)updateTabLabels
   1.352 +{
   1.353 +	NSArray *keyArray = [[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)];
   1.354 +	NSArray *myConnectionArray = [myVPNConnectionDictionary objectsForKeys:keyArray notFoundMarker:[NSNull null]];
   1.355 +	NSEnumerator *connectionEnumerator = [myConnectionArray objectEnumerator];
   1.356 +	VPNConnection *myConnection;
   1.357 +
   1.358 +    // Get preferences for showing duration times
   1.359 +    BOOL showAllDurations = FALSE;
   1.360 +    BOOL showConnectedDurations = TRUE;
   1.361 +    id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showAllDurations"];
   1.362 +    if(tmp != nil) {
   1.363 +        showAllDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllDurations"];
   1.364 +    }
   1.365 +    tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showConnectedDurations"];
   1.366 +    if(tmp != nil) {
   1.367 +        showConnectedDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showConnectedDurations"];
   1.368 +    }
   1.369 +        
   1.370 +	int i = 0;
   1.371 +	while(myConnection = [connectionEnumerator nextObject]) {
   1.372 +		//NSLog(@"configName: %@\nconnectionState: %@\n",[myConnection configName],[myConnection state]);
   1.373 +        NSString * cState = [myConnection state];
   1.374 +        NSString * cTimeS = @"";
   1.375 +
   1.376 +        // Get connection duration if preferences say to 
   1.377 +        if (    showAllDurations ||  (  showConnectedDurations && [cState isEqualToString: @"CONNECTED"]  )    ) {
   1.378 +            NSDate * csd = [myConnection connectedSinceDate];
   1.379 +            NSTimeInterval ti = [csd timeIntervalSinceNow];
   1.380 +            long cTimeL = (long) round(-ti);
   1.381 +            if ( cTimeL >= 0 ) {
   1.382 +                if ( cTimeL < 3600 ) {
   1.383 +                    cTimeS = [NSString stringWithFormat:@" %li:%02li", cTimeL/60, cTimeL%60];
   1.384 +                } else {
   1.385 +                    cTimeS = [NSString stringWithFormat:@" %li:%02li:%02li", cTimeL/3600, (cTimeL/60) % 60, cTimeL%60];
   1.386 +                }
   1.387 +            }
   1.388 +        }
   1.389 +		NSString *label = [NSString stringWithFormat:@"%@ (%@%@)",[myConnection configName],local(cState), cTimeS];
   1.390 +		[[tabView tabViewItemAtIndex:i] setLabel:label];
   1.391 +		i++;
   1.392 +	}
   1.393 +}
   1.394 +
   1.395 +
   1.396 +- (void) updateUI
   1.397 +{
   1.398 +	unsigned connectionNumber = [connectionArray count];
   1.399 +	NSString *myState;
   1.400 +	if(connectionNumber == 1) {
   1.401 +		myState = local(@"OpenVPN: 1 connection active.");
   1.402 +	} else {
   1.403 +		myState = [NSString stringWithFormat:local(@"OpenVPN: %d connections active."),connectionNumber];
   1.404 +	}
   1.405 +	
   1.406 +    [statusMenuItem setTitle: myState];
   1.407 +    [theItem setToolTip: myState];
   1.408 +	
   1.409 +	if( (![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"]) ) { 
   1.410 +		// override while in transitional state
   1.411 +		// Any other state shows "transitional" image:
   1.412 +		//[theItem setImage: transitionalImage];
   1.413 +		if (![theAnim isAnimating])
   1.414 +		{
   1.415 +			//NSLog(@"Starting Animation");
   1.416 +			[theAnim startAnimation];
   1.417 +		}
   1.418 +	} else
   1.419 +	{
   1.420 +		if ([theAnim isAnimating])
   1.421 +		{
   1.422 +			[theAnim stopAnimation];
   1.423 +		}
   1.424 +	}
   1.425 +	if (connectionNumber > 0 ) {
   1.426 +		[theItem setImage: connectedImage];
   1.427 +	} else {
   1.428 +		[theItem setImage: mainImage];
   1.429 +	} 
   1.430 +}
   1.431 +
   1.432 +- (void)animationDidEnd:(NSAnimation*)animation
   1.433 +{
   1.434 +	if ((![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"]))
   1.435 +	{
   1.436 +		// NSLog(@"Starting Animation (2)");
   1.437 +		[theAnim startAnimation];
   1.438 +	}
   1.439 +	if ([connectionArray count] > 0 ) {
   1.440 +        [theItem setImage: connectedImage];
   1.441 +    } else {
   1.442 +        [theItem setImage: mainImage];
   1.443 +    }
   1.444 +}
   1.445 +
   1.446 +- (void)animation:(NSAnimation *)animation
   1.447 +            didReachProgressMark:(NSAnimationProgress)progress
   1.448 +{
   1.449 +	if (animation == theAnim)
   1.450 +	{
   1.451 +		// NSLog(@"progress is %f %i", progress, lround(progress * 8));
   1.452 +		// Do an effect appropriate to progress mark.
   1.453 +		switch(lround(progress * 8))
   1.454 +		{
   1.455 +			case 1:
   1.456 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:mainImage waitUntilDone:YES];
   1.457 +				break;
   1.458 +				
   1.459 +			case 2:
   1.460 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES];
   1.461 +				break;
   1.462 +				
   1.463 +			case 3:
   1.464 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES];
   1.465 +				break;
   1.466 +				
   1.467 +			case 4:
   1.468 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES];
   1.469 +				break;
   1.470 +				
   1.471 +			case 5:
   1.472 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:connectedImage waitUntilDone:YES];
   1.473 +				break;
   1.474 +				
   1.475 +			case 6:
   1.476 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES];
   1.477 +				break;
   1.478 +				
   1.479 +			case 7:
   1.480 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES];
   1.481 +				break;
   1.482 +				
   1.483 +			case 8:
   1.484 +				[theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES];
   1.485 +				break;
   1.486 +				
   1.487 +			default:
   1.488 +				NSLog(@"Unknown progress mark %f selected by Tunnelblick animation", progress);
   1.489 +		}
   1.490 +	}
   1.491 +}
   1.492 +
   1.493 +- (void) tabView: (NSTabView*) inTabView willSelectTabViewItem: (NSTabViewItem*) tabViewItem
   1.494 +{
   1.495 +    NSView* view = [[inTabView selectedTabViewItem] view];
   1.496 +    [tabViewItem setView: view];
   1.497 +    [[[self selectedLogView] textStorage] setDelegate: nil];
   1.498 +}
   1.499 +
   1.500 +- (void) tabView: (NSTabView*) inTabView didSelectTabViewItem: (NSTabViewItem*) tabViewItem
   1.501 +{
   1.502 +    VPNConnection* newConnection = [self selectedConnection];
   1.503 +    NSTextView* logView = [self selectedLogView];
   1.504 +    [[logView layoutManager] replaceTextStorage: [newConnection logStorage]];
   1.505 +    //[logView setSelectedRange: NSMakeRange([[logView textStorage] length],[[logView textStorage] length])];
   1.506 +	[logView scrollRangeToVisible: NSMakeRange([[logView string] length]-1, 0)];
   1.507 +	
   1.508 +    [[logView textStorage] setDelegate: self];
   1.509 +	
   1.510 +    [self validateLogButtons];
   1.511 +}
   1.512 +- (void) textStorageDidProcessEditing: (NSNotification*) aNotification
   1.513 +{
   1.514 +    NSNotification *notification = [NSNotification notificationWithName: @"LogDidChange" 
   1.515 +                                                                 object: [self selectedLogView]];
   1.516 +    [[NSNotificationQueue defaultQueue] enqueueNotification: notification 
   1.517 +                                               postingStyle: NSPostWhenIdle
   1.518 +                                               coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender 
   1.519 +                                                   forModes: nil];
   1.520 +}
   1.521 +
   1.522 +- (void) logNeedsScrolling: (NSNotification*) aNotification
   1.523 +{
   1.524 +    NSTextView* textView = [aNotification object];
   1.525 +    [textView scrollRangeToVisible: NSMakeRange([[textView string] length]-1, 0)];
   1.526 +}
   1.527 +
   1.528 +- (NSTextView*) selectedLogView
   1.529 +{
   1.530 +    NSTextView* result = [[[[[tabView selectedTabViewItem] view] subviews] lastObject] documentView];
   1.531 +    return result;
   1.532 +}
   1.533 +
   1.534 +- (IBAction) clearLog: (id) sender
   1.535 +{
   1.536 +	NSString * versionInfo = [NSString stringWithFormat:local(@"Tunnelblick version %@"),[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
   1.537 +	NSCalendarDate* date = [NSCalendarDate date];
   1.538 +	NSString *dateText = [NSString stringWithFormat:@"%@ %@\n",[date descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S"],versionInfo];
   1.539 +	[[self selectedLogView] setString: [[[NSString alloc] initWithString: dateText] autorelease]];
   1.540 +}
   1.541 +
   1.542 +- (VPNConnection*) selectedConnection
   1.543 +	/*" Returns the connection associated with the currently selected log tab or nil on error. "*/
   1.544 +{
   1.545 +	if (![tabView selectedTabViewItem]) {
   1.546 +		[tabView selectFirstTabViewItem: nil];
   1.547 +	}
   1.548 +	
   1.549 +    NSString* configPath = [[tabView selectedTabViewItem] identifier];
   1.550 +	VPNConnection* connection = [myVPNConnectionDictionary objectForKey:configPath];
   1.551 +	NSArray *allConnections = [myVPNConnectionDictionary allValues];
   1.552 +	if(connection) return connection;
   1.553 +	else if([allConnections count]) return [allConnections objectAtIndex:0] ; 
   1.554 +	else return nil;
   1.555 +}
   1.556 +
   1.557 +
   1.558 +
   1.559 +
   1.560 +- (IBAction)connect:(id)sender
   1.561 +{
   1.562 +	[[self selectedConnection] connect: sender]; 
   1.563 +}
   1.564 +
   1.565 +- (IBAction)disconnect:(id)sender
   1.566 +{
   1.567 +    [[self selectedConnection] disconnect: sender];      
   1.568 +}
   1.569 +
   1.570 +
   1.571 +- (IBAction) openLogWindow: (id) sender
   1.572 +{
   1.573 +    if (logWindow != nil) {
   1.574 +        [logWindow makeKeyAndOrderFront: self];
   1.575 +        [NSApp activateIgnoringOtherApps:YES];
   1.576 +        return;
   1.577 +    }
   1.578 +    
   1.579 +    [NSBundle loadNibNamed: @"LogWindow" owner: self]; // also sets tabView etc.
   1.580 +
   1.581 +    // Set the window's size and position from preferences (saved when window is closed)
   1.582 +    // But only if the preference's version matches the TB version (since window size could be different in different versions of TB)
   1.583 +    NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
   1.584 +    id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"];
   1.585 +    if (tmp != nil) {
   1.586 +        if (  [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]]    ) {
   1.587 +            tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"];
   1.588 +            if(tmp != nil) {
   1.589 +                NSString * frame = [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"];
   1.590 +                [logWindow setFrameFromString:frame];
   1.591 +            }
   1.592 +        }
   1.593 +    }
   1.594 +
   1.595 +	[logWindow setDelegate:self];
   1.596 +	VPNConnection *myConnection = [self selectedConnection];
   1.597 +	NSTextStorage* store = [myConnection logStorage];
   1.598 +	[[[self selectedLogView] layoutManager] replaceTextStorage: store];
   1.599 +	
   1.600 +	NSEnumerator* e = [[[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)] objectEnumerator];
   1.601 +	//id test = [[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)];
   1.602 +	NSTabViewItem* initialItem;
   1.603 +	VPNConnection* connection = [myVPNConnectionDictionary objectForKey: [e nextObject]];
   1.604 +	if (connection) {
   1.605 +		initialItem = [tabView tabViewItemAtIndex: 0];
   1.606 +		[initialItem setIdentifier: [connection configPath]];
   1.607 +		[initialItem setLabel: [connection configName]];
   1.608 +		
   1.609 +		int curTabIndex = 0;
   1.610 +		[tabView selectTabViewItemAtIndex:0];
   1.611 +		BOOL haveOpenConnection = ! [connection isDisconnected];
   1.612 +		while (connection = [myVPNConnectionDictionary objectForKey: [e nextObject]]) {
   1.613 +			NSTabViewItem* newItem = [[NSTabViewItem alloc] init];
   1.614 +			[newItem setIdentifier: [connection configPath]];
   1.615 +			[newItem setLabel: [connection configName]];
   1.616 +			[tabView addTabViewItem: newItem];
   1.617 +			++curTabIndex;
   1.618 +			if (  ( ! haveOpenConnection ) && ( ! [connection isDisconnected] )  ) {
   1.619 +				[tabView selectTabViewItemAtIndex:curTabIndex];
   1.620 +				haveOpenConnection = YES;
   1.621 +			}
   1.622 +		}
   1.623 +	}
   1.624 +	[self tabView:tabView didSelectTabViewItem:initialItem];
   1.625 +	[self validateLogButtons];
   1.626 +	[self updateTabLabels];
   1.627 +    
   1.628 +    // Set up a timer to update the tab labels with connections' duration times
   1.629 +    BOOL showAllDurations = FALSE;
   1.630 +    BOOL showConnectedDurations = TRUE;
   1.631 +    tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showAllDurations"];
   1.632 +    if(tmp != nil) {
   1.633 +        showAllDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllDurations"];
   1.634 +    }
   1.635 +    tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showConnectedDurations"];
   1.636 +    if(tmp != nil) {
   1.637 +        showConnectedDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showConnectedDurations"];
   1.638 +    }
   1.639 +    
   1.640 +    if (    (showDurationsTimer == nil)  && (showAllDurations || showConnectedDurations)    ) {
   1.641 +        showDurationsTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0
   1.642 +                                                               target:self
   1.643 +                                                             selector:@selector(updateTabLabels)
   1.644 +                                                             userInfo:nil
   1.645 +                                                              repeats:YES] retain];
   1.646 +    }
   1.647 +	
   1.648 +	// Localize Buttons
   1.649 +	[clearButton setTitle:local([clearButton title])];
   1.650 +	[editButton setTitle:local([editButton title])];
   1.651 +	[connectButton setTitle:local([connectButton title])];
   1.652 +	[disconnectButton setTitle:local([disconnectButton title])];
   1.653 +	[useNameserverCheckbox setTitle:local([useNameserverCheckbox title])];
   1.654 +	[autoLaunchCheckbox setTitle:local([autoLaunchCheckbox title])];
   1.655 +
   1.656 +    [logWindow makeKeyAndOrderFront: self];
   1.657 +    [NSApp activateIgnoringOtherApps:YES];
   1.658 +}
   1.659 +
   1.660 +// Invoked when the Details... window (logWindow) will close
   1.661 +- (void)windowWillClose:(NSNotification *)n
   1.662 +{
   1.663 +    if ( [n object] == logWindow ) {
   1.664 +        // Stop and release the timer used to update the duration displays
   1.665 +        if (showDurationsTimer != nil) {
   1.666 +            [showDurationsTimer invalidate];
   1.667 +            [showDurationsTimer release];
   1.668 +            showDurationsTimer = nil;
   1.669 +        }
   1.670 +
   1.671 +        // Save the window's size and position in the preferences and save the TB version that saved them, BUT ONLY IF anything has changed
   1.672 +        NSString * frame = [logWindow stringWithSavedFrame];
   1.673 +        NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
   1.674 +        BOOL saveIt = TRUE;
   1.675 +        id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"];
   1.676 +        if(tmp != nil) {
   1.677 +            tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"];
   1.678 +            if (tmp != nil) {
   1.679 +                if (  [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]]    ) {
   1.680 +                    if (   [frame isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"]]    ) {
   1.681 +                        saveIt = FALSE;
   1.682 +                    }
   1.683 +                }
   1.684 +            }
   1.685 +        }
   1.686 +
   1.687 +        if (saveIt) {
   1.688 +            [[NSUserDefaults standardUserDefaults] setObject: frame forKey: @"detailsWindowFrame"];
   1.689 +            [[NSUserDefaults standardUserDefaults] setObject: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
   1.690 +                                                      forKey: @"detailsWindowFrameVersion"];
   1.691 +            [[NSUserDefaults standardUserDefaults] synchronize];
   1.692 +        }
   1.693 +    }
   1.694 +}
   1.695 +
   1.696 +- (void) dealloc
   1.697 +{
   1.698 +    [lastState release];
   1.699 +    [theItem release];
   1.700 +    [myConfigArray release];
   1.701 +    
   1.702 +#warning todo: release non-IB ivars here!
   1.703 +    [statusMenuItem release];
   1.704 +    [myVPNMenu release];
   1.705 +    [userDefaults release];
   1.706 +    [appDefaults release];
   1.707 +    [theItem release]; 
   1.708 +    
   1.709 +    [mainImage release];
   1.710 +    [connectedImage release];
   1.711 +    [errorImage release];
   1.712 +    [transitionalImage release];
   1.713 +    [connectionArray release];
   1.714 +    
   1.715 +    
   1.716 +    [super dealloc];
   1.717 +}
   1.718 +
   1.719 +
   1.720 +-(void)killAllConnections
   1.721 +{
   1.722 +	id connection;
   1.723 +    NSEnumerator* e = [connectionArray objectEnumerator];
   1.724 +    
   1.725 +    while (connection = [e nextObject]) {
   1.726 +        [connection disconnect:self];
   1.727 +		if(NSDebugEnabled) NSLog(@"Killing connection.\n");
   1.728 +    }
   1.729 +}
   1.730 +
   1.731 +-(void)killAllOpenVPN 
   1.732 +{
   1.733 +	NSString* path = [[NSBundle mainBundle] pathForResource: @"openvpnstart" 
   1.734 +													 ofType: nil];
   1.735 +	NSTask* task = [[[NSTask alloc] init] autorelease];
   1.736 +	[task setLaunchPath: path]; 
   1.737 +	
   1.738 +	NSArray *arguments = [NSArray arrayWithObjects:@"killall", nil];
   1.739 +	[task setArguments:arguments];
   1.740 +	[task launch];
   1.741 +	[task waitUntilExit];
   1.742 +}
   1.743 +
   1.744 +-(void)resetActiveConnections {
   1.745 +	VPNConnection *connection;
   1.746 +	NSEnumerator* e = [connectionArray objectEnumerator];
   1.747 +	
   1.748 +	while (connection = [e nextObject]) {
   1.749 +		if (NSDebugEnabled) NSLog(@"Connection %@ is connected for %f seconds\n",[connection configName],[[connection connectedSinceDate] timeIntervalSinceNow]);
   1.750 +		if ([[connection connectedSinceDate] timeIntervalSinceNow] < -5) {
   1.751 +			if (NSDebugEnabled) NSLog(@"Resetting connection: %@\n",[connection configName]);
   1.752 +			[connection disconnect:self];
   1.753 +			[connection connect:self];
   1.754 +		}
   1.755 +		else {
   1.756 +			if (NSDebugEnabled) NSLog(@"Not Resetting connection: %@\n, waiting...",[connection configName]);
   1.757 +		}
   1.758 +	}
   1.759 +}
   1.760 +
   1.761 +-(void)createDefaultConfig 
   1.762 +{
   1.763 +	NSFileManager *fileManager = [NSFileManager defaultManager];
   1.764 +    NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"];
   1.765 +	NSString *confResource = [[NSBundle mainBundle] pathForResource: @"openvpn" 
   1.766 +															 ofType: @"conf"];
   1.767 +	
   1.768 +	if([[self getConfigs] count] == 0) { // if there are no config files, create a default one
   1.769 +		[NSApp activateIgnoringOtherApps:YES];
   1.770 +        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) {
   1.771 +            exit (1);
   1.772 +        }
   1.773 +        else {
   1.774 +			[fileManager createDirectoryAtPath:directoryPath attributes:nil];
   1.775 +			[fileManager copyPath:confResource toPath:[directoryPath stringByAppendingPathComponent:@"/openvpn.conf"] handler:nil];
   1.776 +            [self editConfig:self];
   1.777 +        }
   1.778 +		
   1.779 +		
   1.780 +	}
   1.781 +}
   1.782 +
   1.783 +-(IBAction)editConfig:(id)sender
   1.784 +{
   1.785 +	VPNConnection *connection = [self selectedConnection];
   1.786 +    NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"];
   1.787 +	NSString *configPath = [connection configPath];
   1.788 +    if(configPath == nil) configPath = @"/openvpn.conf";
   1.789 +	
   1.790 +	//	NSString *helper = @"/usr/sbin/chown";
   1.791 +	//	NSString *userString = [NSString stringWithFormat:@"%d",getuid()];
   1.792 +	//	NSArray *arguments = [NSArray arrayWithObjects:userString,configPath,nil];
   1.793 +	//	AuthorizationRef authRef = [NSApplication getAuthorizationRef];
   1.794 +	//	[NSApplication executeAuthorized:helper withArguments:arguments withAuthorizationRef:authRef];
   1.795 +	//	AuthorizationFree(authRef,kAuthorizationFlagDefaults);
   1.796 +	
   1.797 +    [[NSWorkspace sharedWorkspace] openFile:[directoryPath stringByAppendingPathComponent:configPath] withApplication:@"TextEdit"];
   1.798 +}
   1.799 +
   1.800 +
   1.801 +- (void) networkConfigurationDidChange
   1.802 +{
   1.803 +	if (NSDebugEnabled) NSLog(@"Got networkConfigurationDidChange notification!!");
   1.804 +	[self resetActiveConnections];
   1.805 +}
   1.806 +
   1.807 +- (void) applicationWillTerminate: (NSNotification*) notification 
   1.808 +{	
   1.809 +    if (NSDebugEnabled) NSLog(@"App will terminate...\n");
   1.810 +	[self cleanup];
   1.811 +}
   1.812 +
   1.813 +-(void)cleanup 
   1.814 +{
   1.815 +	[NSApp callDelegateOnNetworkChange: NO];
   1.816 +	[self killAllConnections];
   1.817 +	[self killAllOpenVPN];
   1.818 +	[[NSStatusBar systemStatusBar] removeStatusItem:theItem];
   1.819 +}
   1.820 +
   1.821 +-(void)saveUseNameserverCheckboxState:(BOOL)inBool
   1.822 +{
   1.823 +	VPNConnection* connection = [self selectedConnection];
   1.824 +	if(connection != nil) {
   1.825 +		NSString* key = [[connection configName] stringByAppendingString: @"useDNS"];
   1.826 +		[[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: key];
   1.827 +		[[NSUserDefaults standardUserDefaults] synchronize];
   1.828 +	}
   1.829 +	
   1.830 +}
   1.831 +-(void)saveAutoLaunchCheckboxState:(BOOL)inBool
   1.832 +{
   1.833 +	VPNConnection* connection = [self selectedConnection];
   1.834 +	if(connection != nil) {
   1.835 +		NSString* autoConnectKey = [[connection configName] stringByAppendingString: @"autoConnect"];
   1.836 +		[[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: autoConnectKey];
   1.837 +		[[NSUserDefaults standardUserDefaults] synchronize];
   1.838 +	}
   1.839 +	
   1.840 +}
   1.841 +
   1.842 +-(BOOL)getCurrentAutoLaunchSetting
   1.843 +{
   1.844 +	VPNConnection *connection = [self selectedConnection];
   1.845 +	NSString *autoConnectKey = [[connection configName] stringByAppendingString:@"autoConnect"];
   1.846 +	return [[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey];
   1.847 +}
   1.848 +
   1.849 +- (void) setState: (NSString*) newState
   1.850 +	// Be sure to call this in main thread only
   1.851 +{
   1.852 +    [newState retain];
   1.853 +    [lastState release];
   1.854 +    lastState = newState;
   1.855 +    //[self updateUI];
   1.856 +	[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
   1.857 +}
   1.858 +
   1.859 +-(void)addConnection:(id)sender 
   1.860 +{
   1.861 +	if(sender != nil) {
   1.862 +		[connectionArray removeObject:sender];
   1.863 +		[connectionArray addObject:sender];
   1.864 +	}
   1.865 +}
   1.866 +
   1.867 +-(void)removeConnection:(id)sender
   1.868 +{
   1.869 +	if(sender != nil) [connectionArray removeObject:sender];	
   1.870 +}
   1.871 +
   1.872 +static void signal_handler(int signalNumber)
   1.873 +{
   1.874 +    printf("signal %d caught!\n",signalNumber);
   1.875 +    
   1.876 +    if (signalNumber == SIGHUP) {
   1.877 +        printf("SIGHUP received. Restarting active connections...\n");
   1.878 +        [[NSApp delegate] resetActiveConnections];
   1.879 +    } else  {
   1.880 +        printf("Received fatal signal. Cleaning up...\n");
   1.881 +        [[NSApp delegate] cleanup];
   1.882 +        exit(0);	
   1.883 +    }
   1.884 +}
   1.885 +
   1.886 +
   1.887 +- (void) installSignalHandler
   1.888 +{
   1.889 +    struct sigaction action;
   1.890 +    
   1.891 +    action.sa_handler = signal_handler;
   1.892 +    sigemptyset(&action.sa_mask);
   1.893 +    action.sa_flags = 0;
   1.894 +    
   1.895 +    if (sigaction(SIGHUP, &action, NULL) || 
   1.896 +        sigaction(SIGQUIT, &action, NULL) || 
   1.897 +        sigaction(SIGTERM, &action, NULL) ||
   1.898 +        sigaction(SIGBUS, &action, NULL) ||
   1.899 +        sigaction(SIGSEGV, &action, NULL) ||
   1.900 +        sigaction(SIGPIPE, &action, NULL)) {
   1.901 +        NSLog(@"Warning: setting signal handler failed: %s", strerror(errno));
   1.902 +    }	
   1.903 +}
   1.904 +- (void) applicationDidFinishLaunching: (NSNotification *)notification
   1.905 +{
   1.906 +	[NSApp callDelegateOnNetworkChange: NO];
   1.907 +    [self installSignalHandler];    
   1.908 +    [NSApp setAutoLaunchOnLogin: YES];
   1.909 +    [self activateStatusMenu];
   1.910 +	if(needsRepair()){
   1.911 +		if ([self repairPermissions] != TRUE) {
   1.912 +			[NSApp terminate:self];
   1.913 +		}
   1.914 +	} 
   1.915 +
   1.916 +	[updater checkForUpdatesInBackground];
   1.917 +}
   1.918 +
   1.919 +-(void) dmgCheck
   1.920 +{
   1.921 +	NSString *path = [[NSBundle mainBundle] bundlePath];
   1.922 +	if([path hasPrefix:@"/Volumes/Tunnelblick"]) {
   1.923 +		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);
   1.924 +		[panel setLevel:NSStatusWindowLevel];
   1.925 +		[panel makeKeyAndOrderFront:nil];
   1.926 +		[NSApp runModalForWindow:panel];
   1.927 +		exit(2);
   1.928 +	}
   1.929 +}
   1.930 +
   1.931 +-(void)moveAllWindowsToForeground
   1.932 +{
   1.933 +	NSArray *windows = [NSApp windows];
   1.934 +	NSEnumerator *e = [windows objectEnumerator];
   1.935 +	NSWindow *window = nil;
   1.936 +	while(window = [e nextObject]) {
   1.937 +		[window setLevel:NSStatusWindowLevel];
   1.938 +	}
   1.939 +}
   1.940 +
   1.941 +-(void) fileSystemHasChanged: (NSNotification*) n
   1.942 +{
   1.943 +	if(NSDebugEnabled) NSLog(@"FileSystem has changed.");
   1.944 +	[self performSelectorOnMainThread: @selector(activateStatusMenu) withObject: nil waitUntilDone: YES];
   1.945 +}
   1.946 +-(void) kqueue: (UKKQueue*) kq receivedNotification: (NSString*) nm forFile: (NSString*) fpath {
   1.947 +	
   1.948 +	[self fileSystemHasChanged: nil];
   1.949 +}
   1.950 +
   1.951 +-(BOOL)repairPermissions
   1.952 +{
   1.953 +	NSBundle *thisBundle = [NSBundle mainBundle];
   1.954 +	NSString *installer = [thisBundle pathForResource:@"installer" ofType:nil];
   1.955 +	
   1.956 +	AuthorizationRef authRef= [NSApplication getAuthorizationRef];
   1.957 +	
   1.958 +	if(authRef == nil)
   1.959 +		return FALSE;
   1.960 +	
   1.961 +	while(needsRepair()) {
   1.962 +		NSLog(@"Repairing Application...\n");
   1.963 +		[NSApplication executeAuthorized:installer withArguments:nil withAuthorizationRef:authRef];
   1.964 +		sleep(1);
   1.965 +	}
   1.966 +	AuthorizationFree(authRef, kAuthorizationFlagDefaults);
   1.967 +	return TRUE;
   1.968 +}
   1.969 +
   1.970 +
   1.971 +
   1.972 +
   1.973 +BOOL needsRepair() 
   1.974 +{
   1.975 +	NSBundle *thisBundle = [NSBundle mainBundle];
   1.976 +	NSString *helperPath = [thisBundle pathForResource:@"openvpnstart" ofType:nil];
   1.977 +	NSString *tunPath = [thisBundle pathForResource:@"tun.kext" ofType:nil];
   1.978 +	NSString *tapPath = [thisBundle pathForResource:@"tap.kext" ofType:nil];
   1.979 +	
   1.980 +	NSString *tunExecutable = [tunPath stringByAppendingPathComponent:@"/Contents/MacOS/tun"];
   1.981 +	NSString *tapExecutable = [tapPath stringByAppendingPathComponent:@"/Contents/MacOS/tap"];
   1.982 +	NSString *openvpnPath = [thisBundle pathForResource:@"openvpn" ofType:nil];
   1.983 +	
   1.984 +	
   1.985 +	// check setuid helper
   1.986 +	const char *path = [helperPath UTF8String];
   1.987 +    struct stat sb;
   1.988 +	if(stat(path,&sb)) runUnrecoverableErrorPanel();
   1.989 +	
   1.990 +	if (!(			  (sb.st_mode & S_ISUID) // set uid bit is set
   1.991 +					  && (sb.st_mode & S_IXUSR) // owner may execute it
   1.992 +					  && (sb.st_uid == 0) // is owned by root
   1.993 +					  )) {
   1.994 +		NSLog(@"openvpnstart helper has missing set uid bit");
   1.995 +		return YES;		
   1.996 +	}
   1.997 +	
   1.998 +	// check files which should be only accessible by root
   1.999 +	NSArray *inaccessibleObjects = [NSArray arrayWithObjects:tunExecutable,tapExecutable,openvpnPath,nil];
  1.1000 +	NSEnumerator *e = [inaccessibleObjects objectEnumerator];
  1.1001 +	NSString *currentPath;
  1.1002 +	NSFileManager *fileManager = [NSFileManager defaultManager];
  1.1003 +	while(currentPath = [e nextObject]) {
  1.1004 +		NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:currentPath traverseLink:YES];
  1.1005 +		unsigned long perms = [fileAttributes filePosixPermissions];
  1.1006 +		NSString *octalString = [NSString stringWithFormat:@"%o",perms];
  1.1007 +		NSNumber *fileOwner = [fileAttributes fileOwnerAccountID];
  1.1008 +		
  1.1009 +		if ( (![octalString isEqualToString:@"744"])  || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) {
  1.1010 +			NSLog(@"File %@ has permissions: %@, is owned by %@ and needs repair...\n",currentPath,octalString,fileOwner);
  1.1011 +			return YES;
  1.1012 +		}
  1.1013 +	}
  1.1014 +	
  1.1015 +	// check tun and tap driver packages
  1.1016 +	NSArray *filesToCheck = [NSArray arrayWithObjects:tunPath,tapPath,nil];
  1.1017 +	NSEnumerator *enumerator = [filesToCheck objectEnumerator];
  1.1018 +	NSString *file;
  1.1019 +	while(file = [enumerator nextObject]) {
  1.1020 +		NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:file traverseLink:YES];
  1.1021 +		unsigned long perms = [fileAttributes filePosixPermissions];
  1.1022 +		NSString *octalString = [NSString stringWithFormat:@"%o",perms];
  1.1023 +		if ( (![octalString isEqualToString:@"755"])  ) {
  1.1024 +			NSLog(@"File %@ has permissions: %@ and needs repair...\n",currentPath,octalString);
  1.1025 +			return YES;
  1.1026 +		}
  1.1027 +	}
  1.1028 +	return NO;
  1.1029 +}
  1.1030 +
  1.1031 +-(void)willGoToSleep
  1.1032 +{
  1.1033 +	if(NSDebugEnabled) NSLog(@"Computer will go to sleep...\n");
  1.1034 +	connectionsToRestore = [connectionArray mutableCopy];
  1.1035 +	[self killAllConnections];
  1.1036 +}
  1.1037 +-(void)wokeUpFromSleep 
  1.1038 +{
  1.1039 +	if(NSDebugEnabled) NSLog(@"Computer just woke up from sleep...\n");
  1.1040 +	
  1.1041 +	NSEnumerator *e = [connectionsToRestore objectEnumerator];
  1.1042 +	VPNConnection *connection;
  1.1043 +	while(connection = [e nextObject]) {
  1.1044 +		if(NSDebugEnabled) NSLog(@"Restoring Connection %@",[connection configName]);
  1.1045 +		[connection connect:self];
  1.1046 +	}
  1.1047 +}
  1.1048 +int runUnrecoverableErrorPanel(void) 
  1.1049 +{
  1.1050 +	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);
  1.1051 +	[panel setLevel:NSStatusWindowLevel];
  1.1052 +	[panel makeKeyAndOrderFront:nil];
  1.1053 +	if( [NSApp runModalForWindow:panel] != NSAlertDefaultReturn ) {
  1.1054 +		exit(2);
  1.1055 +	} else {
  1.1056 +		[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://tunnelblick.net/"]];
  1.1057 +		exit(2);
  1.1058 +	}
  1.1059 +}
  1.1060 +
  1.1061 +-(IBAction) autoLaunchPrefButtonWasClicked: (id) sender
  1.1062 +{
  1.1063 +	if([sender state]) {
  1.1064 +		[self saveAutoLaunchCheckboxState:TRUE];
  1.1065 +	} else {
  1.1066 +		[self saveAutoLaunchCheckboxState:FALSE];
  1.1067 +	}
  1.1068 +}
  1.1069 +
  1.1070 +-(IBAction) nameserverPrefButtonWasClicked: (id) sender
  1.1071 +{
  1.1072 +	if([sender state]) {
  1.1073 +		[self saveUseNameserverCheckboxState:TRUE];
  1.1074 +	} else {
  1.1075 +		[self saveUseNameserverCheckboxState:FALSE];
  1.1076 +	}
  1.1077 +}
  1.1078 +
  1.1079 +
  1.1080 +@end

mercurial