Mon, 16 Jan 2012 23:08:14 +0100
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 |