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.

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

mercurial