michael@1: /* michael@1: * Copyright (c) 2004 Angelo Laub michael@1: * Contributions by Dirk Theisen , michael@1: * Jens Ohlig, michael@1: * Waldemar Brodkorb michael@1: * michael@1: * This program is free software; you can redistribute it and/or modify michael@1: * it under the terms of the GNU General Public License version 2 michael@1: * as published by the Free Software Foundation. michael@1: * michael@1: * This program is distributed in the hope that it will be useful, michael@1: * but WITHOUT ANY WARRANTY; without even the implied warranty of michael@1: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the michael@1: * GNU General Public License for more details. michael@1: * michael@1: * You should have received a copy of the GNU General Public License michael@1: * along with this program (see the file COPYING included with this michael@1: * distribution); if not, write to the Free Software Foundation, Inc., michael@1: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA michael@1: */ michael@1: michael@1: michael@1: #import "MenuController.h" michael@1: #import "NSApplication+NetworkNotifications.h" michael@1: #import "helper.h" michael@1: michael@1: michael@1: #define NSAppKitVersionNumber10_0 577 michael@1: #define NSAppKitVersionNumber10_1 620 michael@1: #define NSAppKitVersionNumber10_2 663 michael@1: #define NSAppKitVersionNumber10_3 743 michael@1: michael@1: michael@1: michael@1: BOOL systemIsTigerOrNewer() michael@1: { michael@1: return (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_3) ; michael@1: } michael@1: michael@1: @interface NSStatusBar (NSStatusBar_Private) michael@1: - (id)_statusItemWithLength:(float)l withPriority:(int)p; michael@1: @end michael@1: michael@1: michael@1: @implementation MenuController michael@1: michael@1: - (void) createStatusItem michael@1: { michael@1: NSStatusBar *bar = [NSStatusBar systemStatusBar]; michael@1: int priority = INT32_MAX; michael@1: if (systemIsTigerOrNewer()) { michael@1: priority = MIN(priority, 2147483646); // found by experimenting - dirk michael@1: } michael@1: michael@1: if (!theItem) { michael@1: theItem = [[bar _statusItemWithLength: NSVariableStatusItemLength withPriority: priority] retain]; michael@1: //theItem = [[bar _statusItemWithLength: NSVariableStatusItemLength withPriority: 0] retain]; michael@1: } michael@1: // Dirk: For Tiger and up, re-insert item to place it correctly. michael@1: if ([bar respondsToSelector: @selector(_insertStatusItem:withPriority:)]) { michael@1: [bar removeStatusItem: theItem]; michael@1: [bar _insertStatusItem: theItem withPriority: priority]; michael@1: } michael@1: } michael@1: michael@1: -(id) init michael@1: { michael@1: if (self = [super init]) { michael@1: [self dmgCheck]; michael@1: michael@1: errorImage = [[NSImage imageNamed: @"error.tif"] retain]; michael@1: mainImage = [[NSImage imageNamed: @"00_closed.tif"] retain]; michael@1: connectedImage = [[NSImage imageNamed: @"connected.png"] retain]; michael@1: michael@1: michael@1: transitionalImage1 = [[NSImage imageNamed: @"01.tif"] retain]; michael@1: transitionalImage2 = [[NSImage imageNamed: @"02.tif"] retain]; michael@1: transitionalImage3 = [[NSImage imageNamed: @"03.tif"] retain]; michael@1: [NSApp setDelegate:self]; michael@1: michael@1: myVPNConnectionDictionary = [[NSMutableDictionary alloc] init]; michael@1: myVPNConnectionArray = [[[NSMutableArray alloc] init] retain]; michael@1: userDefaults = [[NSMutableDictionary alloc] init]; michael@1: michael@1: connectionArray = [[[NSMutableArray alloc] init] retain]; michael@1: appDefaults = [NSUserDefaults standardUserDefaults]; michael@1: [appDefaults registerDefaults:userDefaults]; michael@1: michael@1: michael@1: detailsItem = [[NSMenuItem alloc] init]; michael@1: [detailsItem setTitle: @"Details..."]; michael@1: [detailsItem setTarget: self]; michael@1: [detailsItem setAction: @selector(openLogWindow:)]; michael@1: michael@1: quitItem = [[NSMenuItem alloc] init]; michael@1: [quitItem setTitle: @"Quit"]; michael@1: [quitItem setTarget: self]; michael@1: [quitItem setAction: @selector(quit:)]; michael@1: michael@1: [self createStatusItem]; michael@1: michael@1: [self updateMenu]; michael@1: [self setState: @"EXITING"]; // synonym for "Disconnected" michael@1: michael@1: [[NSNotificationCenter defaultCenter] addObserver: self michael@1: selector: @selector(logNeedsScrolling:) michael@1: name: @"LogDidChange" michael@1: object: nil]; michael@1: michael@1: // In case the systemUIServer restarts, we observed this notification. michael@1: // We use it to prevent to end up with a statusItem right of Spotlight: michael@1: [[NSDistributedNotificationCenter defaultCenter] addObserver: self michael@1: selector: @selector(menuExtrasWereAdded:) michael@1: name: @"com.apple.menuextra.added" michael@1: object: nil]; michael@1: [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self michael@1: selector: @selector(willGoToSleep) michael@1: name: @"NSWorkspaceWillSleepNotification" michael@1: object:nil]; michael@1: michael@1: [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self michael@1: selector: @selector(wokeUpFromSleep) michael@1: name: @"NSWorkspaceDidWakeNotification" michael@1: object:nil]; michael@1: michael@1: NSString* vpnDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn/"]; michael@1: michael@1: UKKQueue* myQueue = [UKKQueue sharedQueue]; michael@1: [myQueue addPathToQueue: vpnDirectory]; michael@1: [myQueue setDelegate: self]; michael@1: [myQueue setAlwaysNotify: YES]; michael@1: michael@1: [NSThread detachNewThreadSelector:@selector(moveAllWindowsToForegroundThread) toTarget:self withObject:nil]; michael@1: michael@1: updater = [[SUUpdater alloc] init]; michael@1: michael@1: } michael@1: return self; michael@1: } michael@1: michael@1: -(void)moveAllWindowsToForegroundThread { michael@1: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; michael@1: sleep(3); michael@1: [self moveAllWindowsToForeground]; michael@1: // [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(moveAllWindowsToForeground) userInfo: nil repeats: YES]; michael@1: [pool release]; michael@1: michael@1: } michael@1: michael@1: - (void) menuExtrasWereAdded: (NSNotification*) n michael@1: { michael@1: [self createStatusItem]; michael@1: } michael@1: michael@1: michael@1: michael@1: - (IBAction) quit: (id) sender michael@1: { michael@1: // Remove us from the login items if terminates manually... michael@1: [NSApp setAutoLaunchOnLogin: NO]; michael@1: [NSApp terminate: sender]; michael@1: } michael@1: michael@1: michael@1: michael@1: - (void) awakeFromNib michael@1: { michael@1: [self createDefaultConfig]; michael@1: [self initialiseAnim]; michael@1: } michael@1: michael@1: - (void) initialiseAnim michael@1: { michael@1: NSAnimationProgress progMarks[] = { michael@1: 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: }; michael@1: michael@1: int i, count = 8; michael@1: // theAnim is an NSAnimation instance variable michael@1: theAnim = [[NSAnimation alloc] initWithDuration:2.0 michael@1: animationCurve:NSAnimationLinear]; michael@1: [theAnim setFrameRate:8.0]; michael@1: [theAnim setDelegate:self]; michael@1: michael@1: for (i=0; i= 0 ) { michael@1: if ( cTimeL < 3600 ) { michael@1: cTimeS = [NSString stringWithFormat:@" %li:%02li", cTimeL/60, cTimeL%60]; michael@1: } else { michael@1: cTimeS = [NSString stringWithFormat:@" %li:%02li:%02li", cTimeL/3600, (cTimeL/60) % 60, cTimeL%60]; michael@1: } michael@1: } michael@1: } michael@7: NSString *label = [NSString stringWithFormat:@"%@ (%@%@)",[myConnection configName],NSLocalizedString(cState, nil), cTimeS]; michael@1: [[tabView tabViewItemAtIndex:i] setLabel:label]; michael@1: i++; michael@1: } michael@1: } michael@1: michael@1: michael@1: - (void) updateUI michael@1: { michael@1: unsigned connectionNumber = [connectionArray count]; michael@1: NSString *myState; michael@1: if(connectionNumber == 1) { michael@1: myState = local(@"OpenVPN: 1 connection active."); michael@1: } else { michael@7: myState = [NSString stringWithFormat:NSLocalizedString(@"OpenVPN: %d connections active.", nil),connectionNumber]; michael@1: } michael@1: michael@1: [statusMenuItem setTitle: myState]; michael@1: [theItem setToolTip: myState]; michael@1: michael@1: if( (![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"]) ) { michael@1: // override while in transitional state michael@1: // Any other state shows "transitional" image: michael@1: //[theItem setImage: transitionalImage]; michael@1: if (![theAnim isAnimating]) michael@1: { michael@1: //NSLog(@"Starting Animation"); michael@1: [theAnim startAnimation]; michael@1: } michael@1: } else michael@1: { michael@1: if ([theAnim isAnimating]) michael@1: { michael@1: [theAnim stopAnimation]; michael@1: } michael@1: } michael@1: if (connectionNumber > 0 ) { michael@1: [theItem setImage: connectedImage]; michael@1: } else { michael@1: [theItem setImage: mainImage]; michael@1: } michael@1: } michael@1: michael@1: - (void)animationDidEnd:(NSAnimation*)animation michael@1: { michael@1: if ((![lastState isEqualToString:@"EXITING"]) && (![lastState isEqualToString:@"CONNECTED"])) michael@1: { michael@1: // NSLog(@"Starting Animation (2)"); michael@1: [theAnim startAnimation]; michael@1: } michael@1: if ([connectionArray count] > 0 ) { michael@1: [theItem setImage: connectedImage]; michael@1: } else { michael@1: [theItem setImage: mainImage]; michael@1: } michael@1: } michael@1: michael@1: - (void)animation:(NSAnimation *)animation michael@1: didReachProgressMark:(NSAnimationProgress)progress michael@1: { michael@1: if (animation == theAnim) michael@1: { michael@1: // NSLog(@"progress is %f %i", progress, lround(progress * 8)); michael@1: // Do an effect appropriate to progress mark. michael@1: switch(lround(progress * 8)) michael@1: { michael@1: case 1: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:mainImage waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 2: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 3: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 4: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 5: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:connectedImage waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 6: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage3 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 7: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage2 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: case 8: michael@1: [theItem performSelectorOnMainThread:@selector(setImage:) withObject:transitionalImage1 waitUntilDone:YES]; michael@1: break; michael@1: michael@1: default: michael@1: NSLog(@"Unknown progress mark %f selected by Tunnelblick animation", progress); michael@1: } michael@1: } michael@1: } michael@1: michael@1: - (void) tabView: (NSTabView*) inTabView willSelectTabViewItem: (NSTabViewItem*) tabViewItem michael@1: { michael@1: NSView* view = [[inTabView selectedTabViewItem] view]; michael@1: [tabViewItem setView: view]; michael@1: [[[self selectedLogView] textStorage] setDelegate: nil]; michael@1: } michael@1: michael@1: - (void) tabView: (NSTabView*) inTabView didSelectTabViewItem: (NSTabViewItem*) tabViewItem michael@1: { michael@1: VPNConnection* newConnection = [self selectedConnection]; michael@1: NSTextView* logView = [self selectedLogView]; michael@1: [[logView layoutManager] replaceTextStorage: [newConnection logStorage]]; michael@1: //[logView setSelectedRange: NSMakeRange([[logView textStorage] length],[[logView textStorage] length])]; michael@1: [logView scrollRangeToVisible: NSMakeRange([[logView string] length]-1, 0)]; michael@1: michael@1: [[logView textStorage] setDelegate: self]; michael@1: michael@1: [self validateLogButtons]; michael@1: } michael@1: - (void) textStorageDidProcessEditing: (NSNotification*) aNotification michael@1: { michael@1: NSNotification *notification = [NSNotification notificationWithName: @"LogDidChange" michael@1: object: [self selectedLogView]]; michael@1: [[NSNotificationQueue defaultQueue] enqueueNotification: notification michael@1: postingStyle: NSPostWhenIdle michael@1: coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender michael@1: forModes: nil]; michael@1: } michael@1: michael@1: - (void) logNeedsScrolling: (NSNotification*) aNotification michael@1: { michael@1: NSTextView* textView = [aNotification object]; michael@1: [textView scrollRangeToVisible: NSMakeRange([[textView string] length]-1, 0)]; michael@1: } michael@1: michael@1: - (NSTextView*) selectedLogView michael@1: { michael@1: NSTextView* result = [[[[[tabView selectedTabViewItem] view] subviews] lastObject] documentView]; michael@1: return result; michael@1: } michael@1: michael@1: - (IBAction) clearLog: (id) sender michael@1: { michael@1: NSString * versionInfo = [NSString stringWithFormat:local(@"Tunnelblick version %@"),[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; michael@1: NSCalendarDate* date = [NSCalendarDate date]; michael@1: NSString *dateText = [NSString stringWithFormat:@"%@ %@\n",[date descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S"],versionInfo]; michael@1: [[self selectedLogView] setString: [[[NSString alloc] initWithString: dateText] autorelease]]; michael@1: } michael@1: michael@1: - (VPNConnection*) selectedConnection michael@1: /*" Returns the connection associated with the currently selected log tab or nil on error. "*/ michael@1: { michael@1: if (![tabView selectedTabViewItem]) { michael@1: [tabView selectFirstTabViewItem: nil]; michael@1: } michael@1: michael@1: NSString* configPath = [[tabView selectedTabViewItem] identifier]; michael@1: VPNConnection* connection = [myVPNConnectionDictionary objectForKey:configPath]; michael@1: NSArray *allConnections = [myVPNConnectionDictionary allValues]; michael@1: if(connection) return connection; michael@1: else if([allConnections count]) return [allConnections objectAtIndex:0] ; michael@1: else return nil; michael@1: } michael@1: michael@1: michael@1: michael@1: michael@1: - (IBAction)connect:(id)sender michael@1: { michael@1: [[self selectedConnection] connect: sender]; michael@1: } michael@1: michael@1: - (IBAction)disconnect:(id)sender michael@1: { michael@1: [[self selectedConnection] disconnect: sender]; michael@1: } michael@1: michael@1: michael@1: - (IBAction) openLogWindow: (id) sender michael@1: { michael@1: if (logWindow != nil) { michael@1: [logWindow makeKeyAndOrderFront: self]; michael@1: [NSApp activateIgnoringOtherApps:YES]; michael@1: return; michael@1: } michael@1: michael@1: [NSBundle loadNibNamed: @"LogWindow" owner: self]; // also sets tabView etc. michael@1: michael@1: // Set the window's size and position from preferences (saved when window is closed) michael@1: // But only if the preference's version matches the TB version (since window size could be different in different versions of TB) michael@1: NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; michael@1: id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"]; michael@1: if (tmp != nil) { michael@1: if ( [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]] ) { michael@1: tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"]; michael@1: if(tmp != nil) { michael@1: NSString * frame = [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"]; michael@1: [logWindow setFrameFromString:frame]; michael@1: } michael@1: } michael@1: } michael@1: michael@1: [logWindow setDelegate:self]; michael@1: VPNConnection *myConnection = [self selectedConnection]; michael@1: NSTextStorage* store = [myConnection logStorage]; michael@1: [[[self selectedLogView] layoutManager] replaceTextStorage: store]; michael@1: michael@1: NSEnumerator* e = [[[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)] objectEnumerator]; michael@1: //id test = [[myVPNConnectionDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)]; michael@1: NSTabViewItem* initialItem; michael@1: VPNConnection* connection = [myVPNConnectionDictionary objectForKey: [e nextObject]]; michael@1: if (connection) { michael@1: initialItem = [tabView tabViewItemAtIndex: 0]; michael@1: [initialItem setIdentifier: [connection configPath]]; michael@1: [initialItem setLabel: [connection configName]]; michael@1: michael@1: int curTabIndex = 0; michael@1: [tabView selectTabViewItemAtIndex:0]; michael@1: BOOL haveOpenConnection = ! [connection isDisconnected]; michael@1: while (connection = [myVPNConnectionDictionary objectForKey: [e nextObject]]) { michael@1: NSTabViewItem* newItem = [[NSTabViewItem alloc] init]; michael@1: [newItem setIdentifier: [connection configPath]]; michael@1: [newItem setLabel: [connection configName]]; michael@1: [tabView addTabViewItem: newItem]; michael@1: ++curTabIndex; michael@1: if ( ( ! haveOpenConnection ) && ( ! [connection isDisconnected] ) ) { michael@1: [tabView selectTabViewItemAtIndex:curTabIndex]; michael@1: haveOpenConnection = YES; michael@1: } michael@1: } michael@1: } michael@1: [self tabView:tabView didSelectTabViewItem:initialItem]; michael@1: [self validateLogButtons]; michael@1: [self updateTabLabels]; michael@1: michael@1: // Set up a timer to update the tab labels with connections' duration times michael@1: BOOL showAllDurations = FALSE; michael@1: BOOL showConnectedDurations = TRUE; michael@1: tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showAllDurations"]; michael@1: if(tmp != nil) { michael@1: showAllDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllDurations"]; michael@1: } michael@1: tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"showConnectedDurations"]; michael@1: if(tmp != nil) { michael@1: showConnectedDurations = [[NSUserDefaults standardUserDefaults] boolForKey:@"showConnectedDurations"]; michael@1: } michael@1: michael@1: if ( (showDurationsTimer == nil) && (showAllDurations || showConnectedDurations) ) { michael@1: showDurationsTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 michael@1: target:self michael@1: selector:@selector(updateTabLabels) michael@1: userInfo:nil michael@1: repeats:YES] retain]; michael@1: } michael@1: michael@1: // Localize Buttons michael@1: [clearButton setTitle:local([clearButton title])]; michael@1: [editButton setTitle:local([editButton title])]; michael@1: [connectButton setTitle:local([connectButton title])]; michael@1: [disconnectButton setTitle:local([disconnectButton title])]; michael@1: [useNameserverCheckbox setTitle:local([useNameserverCheckbox title])]; michael@1: [autoLaunchCheckbox setTitle:local([autoLaunchCheckbox title])]; michael@1: michael@1: [logWindow makeKeyAndOrderFront: self]; michael@1: [NSApp activateIgnoringOtherApps:YES]; michael@1: } michael@1: michael@1: // Invoked when the Details... window (logWindow) will close michael@1: - (void)windowWillClose:(NSNotification *)n michael@1: { michael@1: if ( [n object] == logWindow ) { michael@1: // Stop and release the timer used to update the duration displays michael@1: if (showDurationsTimer != nil) { michael@1: [showDurationsTimer invalidate]; michael@1: [showDurationsTimer release]; michael@1: showDurationsTimer = nil; michael@1: } michael@1: michael@1: // 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: NSString * frame = [logWindow stringWithSavedFrame]; michael@1: NSString * tbVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; michael@1: BOOL saveIt = TRUE; michael@1: id tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrame"]; michael@1: if(tmp != nil) { michael@1: tmp = [[NSUserDefaults standardUserDefaults] objectForKey:@"detailsWindowFrameVersion"]; michael@1: if (tmp != nil) { michael@1: if ( [tbVersion isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrameVersion"]] ) { michael@1: if ( [frame isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey:@"detailsWindowFrame"]] ) { michael@1: saveIt = FALSE; michael@1: } michael@1: } michael@1: } michael@1: } michael@1: michael@1: if (saveIt) { michael@1: [[NSUserDefaults standardUserDefaults] setObject: frame forKey: @"detailsWindowFrame"]; michael@1: [[NSUserDefaults standardUserDefaults] setObject: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] michael@1: forKey: @"detailsWindowFrameVersion"]; michael@1: [[NSUserDefaults standardUserDefaults] synchronize]; michael@1: } michael@1: } michael@1: } michael@1: michael@1: - (void) dealloc michael@1: { michael@1: [lastState release]; michael@1: [theItem release]; michael@1: [myConfigArray release]; michael@1: michael@1: #warning todo: release non-IB ivars here! michael@1: [statusMenuItem release]; michael@1: [myVPNMenu release]; michael@1: [userDefaults release]; michael@1: [appDefaults release]; michael@1: [theItem release]; michael@1: michael@1: [mainImage release]; michael@1: [connectedImage release]; michael@1: [errorImage release]; michael@1: [transitionalImage release]; michael@1: [connectionArray release]; michael@1: michael@1: michael@1: [super dealloc]; michael@1: } michael@1: michael@1: michael@1: -(void)killAllConnections michael@1: { michael@1: id connection; michael@1: NSEnumerator* e = [connectionArray objectEnumerator]; michael@1: michael@1: while (connection = [e nextObject]) { michael@1: [connection disconnect:self]; michael@1: if(NSDebugEnabled) NSLog(@"Killing connection.\n"); michael@1: } michael@1: } michael@1: michael@1: -(void)killAllOpenVPN michael@1: { michael@1: NSString* path = [[NSBundle mainBundle] pathForResource: @"openvpnstart" michael@1: ofType: nil]; michael@1: NSTask* task = [[[NSTask alloc] init] autorelease]; michael@1: [task setLaunchPath: path]; michael@1: michael@1: NSArray *arguments = [NSArray arrayWithObjects:@"killall", nil]; michael@1: [task setArguments:arguments]; michael@1: [task launch]; michael@1: [task waitUntilExit]; michael@1: } michael@1: michael@1: -(void)resetActiveConnections { michael@1: VPNConnection *connection; michael@1: NSEnumerator* e = [connectionArray objectEnumerator]; michael@1: michael@1: while (connection = [e nextObject]) { michael@1: if (NSDebugEnabled) NSLog(@"Connection %@ is connected for %f seconds\n",[connection configName],[[connection connectedSinceDate] timeIntervalSinceNow]); michael@1: if ([[connection connectedSinceDate] timeIntervalSinceNow] < -5) { michael@1: if (NSDebugEnabled) NSLog(@"Resetting connection: %@\n",[connection configName]); michael@1: [connection disconnect:self]; michael@1: [connection connect:self]; michael@1: } michael@1: else { michael@1: if (NSDebugEnabled) NSLog(@"Not Resetting connection: %@\n, waiting...",[connection configName]); michael@1: } michael@1: } michael@1: } michael@1: michael@1: -(void)createDefaultConfig michael@1: { michael@1: NSFileManager *fileManager = [NSFileManager defaultManager]; michael@1: NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"]; michael@1: NSString *confResource = [[NSBundle mainBundle] pathForResource: @"openvpn" michael@1: ofType: @"conf"]; michael@1: michael@1: if([[self getConfigs] count] == 0) { // if there are no config files, create a default one michael@1: [NSApp activateIgnoringOtherApps:YES]; michael@1: 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: exit (1); michael@1: } michael@1: else { michael@1: [fileManager createDirectoryAtPath:directoryPath attributes:nil]; michael@1: [fileManager copyPath:confResource toPath:[directoryPath stringByAppendingPathComponent:@"/openvpn.conf"] handler:nil]; michael@1: [self editConfig:self]; michael@1: } michael@1: michael@1: michael@1: } michael@1: } michael@1: michael@1: -(IBAction)editConfig:(id)sender michael@1: { michael@1: VPNConnection *connection = [self selectedConnection]; michael@1: NSString *directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/openvpn"]; michael@1: NSString *configPath = [connection configPath]; michael@1: if(configPath == nil) configPath = @"/openvpn.conf"; michael@1: michael@1: // NSString *helper = @"/usr/sbin/chown"; michael@1: // NSString *userString = [NSString stringWithFormat:@"%d",getuid()]; michael@1: // NSArray *arguments = [NSArray arrayWithObjects:userString,configPath,nil]; michael@1: // AuthorizationRef authRef = [NSApplication getAuthorizationRef]; michael@1: // [NSApplication executeAuthorized:helper withArguments:arguments withAuthorizationRef:authRef]; michael@1: // AuthorizationFree(authRef,kAuthorizationFlagDefaults); michael@1: michael@1: [[NSWorkspace sharedWorkspace] openFile:[directoryPath stringByAppendingPathComponent:configPath] withApplication:@"TextEdit"]; michael@1: } michael@1: michael@1: michael@1: - (void) networkConfigurationDidChange michael@1: { michael@1: if (NSDebugEnabled) NSLog(@"Got networkConfigurationDidChange notification!!"); michael@1: [self resetActiveConnections]; michael@1: } michael@1: michael@1: - (void) applicationWillTerminate: (NSNotification*) notification michael@1: { michael@1: if (NSDebugEnabled) NSLog(@"App will terminate...\n"); michael@1: [self cleanup]; michael@1: } michael@1: michael@1: -(void)cleanup michael@1: { michael@1: [NSApp callDelegateOnNetworkChange: NO]; michael@1: [self killAllConnections]; michael@1: [self killAllOpenVPN]; michael@1: [[NSStatusBar systemStatusBar] removeStatusItem:theItem]; michael@1: } michael@1: michael@1: -(void)saveUseNameserverCheckboxState:(BOOL)inBool michael@1: { michael@1: VPNConnection* connection = [self selectedConnection]; michael@1: if(connection != nil) { michael@1: NSString* key = [[connection configName] stringByAppendingString: @"useDNS"]; michael@1: [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: key]; michael@1: [[NSUserDefaults standardUserDefaults] synchronize]; michael@1: } michael@1: michael@1: } michael@1: -(void)saveAutoLaunchCheckboxState:(BOOL)inBool michael@1: { michael@1: VPNConnection* connection = [self selectedConnection]; michael@1: if(connection != nil) { michael@1: NSString* autoConnectKey = [[connection configName] stringByAppendingString: @"autoConnect"]; michael@1: [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithBool: inBool] forKey: autoConnectKey]; michael@1: [[NSUserDefaults standardUserDefaults] synchronize]; michael@1: } michael@1: michael@1: } michael@1: michael@1: -(BOOL)getCurrentAutoLaunchSetting michael@1: { michael@1: VPNConnection *connection = [self selectedConnection]; michael@1: NSString *autoConnectKey = [[connection configName] stringByAppendingString:@"autoConnect"]; michael@1: return [[NSUserDefaults standardUserDefaults] boolForKey:autoConnectKey]; michael@1: } michael@1: michael@1: - (void) setState: (NSString*) newState michael@1: // Be sure to call this in main thread only michael@1: { michael@1: [newState retain]; michael@1: [lastState release]; michael@1: lastState = newState; michael@1: //[self updateUI]; michael@1: [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO]; michael@1: } michael@1: michael@1: -(void)addConnection:(id)sender michael@1: { michael@1: if(sender != nil) { michael@1: [connectionArray removeObject:sender]; michael@1: [connectionArray addObject:sender]; michael@1: } michael@1: } michael@1: michael@1: -(void)removeConnection:(id)sender michael@1: { michael@1: if(sender != nil) [connectionArray removeObject:sender]; michael@1: } michael@1: michael@1: static void signal_handler(int signalNumber) michael@1: { michael@1: printf("signal %d caught!\n",signalNumber); michael@1: michael@1: if (signalNumber == SIGHUP) { michael@1: printf("SIGHUP received. Restarting active connections...\n"); michael@1: [[NSApp delegate] resetActiveConnections]; michael@1: } else { michael@1: printf("Received fatal signal. Cleaning up...\n"); michael@1: [[NSApp delegate] cleanup]; michael@1: exit(0); michael@1: } michael@1: } michael@1: michael@1: michael@1: - (void) installSignalHandler michael@1: { michael@1: struct sigaction action; michael@1: michael@1: action.sa_handler = signal_handler; michael@1: sigemptyset(&action.sa_mask); michael@1: action.sa_flags = 0; michael@1: michael@1: if (sigaction(SIGHUP, &action, NULL) || michael@1: sigaction(SIGQUIT, &action, NULL) || michael@1: sigaction(SIGTERM, &action, NULL) || michael@1: sigaction(SIGBUS, &action, NULL) || michael@1: sigaction(SIGSEGV, &action, NULL) || michael@1: sigaction(SIGPIPE, &action, NULL)) { michael@1: NSLog(@"Warning: setting signal handler failed: %s", strerror(errno)); michael@1: } michael@1: } michael@1: - (void) applicationDidFinishLaunching: (NSNotification *)notification michael@1: { michael@1: [NSApp callDelegateOnNetworkChange: NO]; michael@1: [self installSignalHandler]; michael@1: [NSApp setAutoLaunchOnLogin: YES]; michael@1: [self activateStatusMenu]; michael@1: if(needsRepair()){ michael@1: if ([self repairPermissions] != TRUE) { michael@1: [NSApp terminate:self]; michael@1: } michael@1: } michael@1: michael@1: [updater checkForUpdatesInBackground]; michael@1: } michael@1: michael@1: -(void) dmgCheck michael@1: { michael@1: NSString *path = [[NSBundle mainBundle] bundlePath]; michael@1: if([path hasPrefix:@"/Volumes/Tunnelblick"]) { michael@1: 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: [panel setLevel:NSStatusWindowLevel]; michael@1: [panel makeKeyAndOrderFront:nil]; michael@1: [NSApp runModalForWindow:panel]; michael@1: exit(2); michael@1: } michael@1: } michael@1: michael@1: -(void)moveAllWindowsToForeground michael@1: { michael@1: NSArray *windows = [NSApp windows]; michael@1: NSEnumerator *e = [windows objectEnumerator]; michael@1: NSWindow *window = nil; michael@1: while(window = [e nextObject]) { michael@1: [window setLevel:NSStatusWindowLevel]; michael@1: } michael@1: } michael@1: michael@1: -(void) fileSystemHasChanged: (NSNotification*) n michael@1: { michael@1: if(NSDebugEnabled) NSLog(@"FileSystem has changed."); michael@1: [self performSelectorOnMainThread: @selector(activateStatusMenu) withObject: nil waitUntilDone: YES]; michael@1: } michael@1: -(void) kqueue: (UKKQueue*) kq receivedNotification: (NSString*) nm forFile: (NSString*) fpath { michael@1: michael@1: [self fileSystemHasChanged: nil]; michael@1: } michael@1: michael@1: -(BOOL)repairPermissions michael@1: { michael@1: NSBundle *thisBundle = [NSBundle mainBundle]; michael@1: NSString *installer = [thisBundle pathForResource:@"installer" ofType:nil]; michael@1: michael@1: AuthorizationRef authRef= [NSApplication getAuthorizationRef]; michael@1: michael@1: if(authRef == nil) michael@1: return FALSE; michael@1: michael@1: while(needsRepair()) { michael@1: NSLog(@"Repairing Application...\n"); michael@1: [NSApplication executeAuthorized:installer withArguments:nil withAuthorizationRef:authRef]; michael@1: sleep(1); michael@1: } michael@1: AuthorizationFree(authRef, kAuthorizationFlagDefaults); michael@1: return TRUE; michael@1: } michael@1: michael@1: michael@1: michael@1: michael@1: BOOL needsRepair() michael@1: { michael@1: NSBundle *thisBundle = [NSBundle mainBundle]; michael@1: NSString *helperPath = [thisBundle pathForResource:@"openvpnstart" ofType:nil]; michael@1: NSString *tunPath = [thisBundle pathForResource:@"tun.kext" ofType:nil]; michael@1: NSString *tapPath = [thisBundle pathForResource:@"tap.kext" ofType:nil]; michael@1: michael@1: NSString *tunExecutable = [tunPath stringByAppendingPathComponent:@"/Contents/MacOS/tun"]; michael@1: NSString *tapExecutable = [tapPath stringByAppendingPathComponent:@"/Contents/MacOS/tap"]; michael@1: NSString *openvpnPath = [thisBundle pathForResource:@"openvpn" ofType:nil]; michael@1: michael@1: michael@1: // check setuid helper michael@1: const char *path = [helperPath UTF8String]; michael@1: struct stat sb; michael@1: if(stat(path,&sb)) runUnrecoverableErrorPanel(); michael@1: michael@1: if (!( (sb.st_mode & S_ISUID) // set uid bit is set michael@1: && (sb.st_mode & S_IXUSR) // owner may execute it michael@1: && (sb.st_uid == 0) // is owned by root michael@1: )) { michael@1: NSLog(@"openvpnstart helper has missing set uid bit"); michael@1: return YES; michael@1: } michael@1: michael@1: // check files which should be only accessible by root michael@1: NSArray *inaccessibleObjects = [NSArray arrayWithObjects:tunExecutable,tapExecutable,openvpnPath,nil]; michael@1: NSEnumerator *e = [inaccessibleObjects objectEnumerator]; michael@1: NSString *currentPath; michael@1: NSFileManager *fileManager = [NSFileManager defaultManager]; michael@1: while(currentPath = [e nextObject]) { michael@1: NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:currentPath traverseLink:YES]; michael@1: unsigned long perms = [fileAttributes filePosixPermissions]; michael@1: NSString *octalString = [NSString stringWithFormat:@"%o",perms]; michael@1: NSNumber *fileOwner = [fileAttributes fileOwnerAccountID]; michael@1: michael@6: if ( (![octalString isEqualToString:@"755"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) { michael@1: NSLog(@"File %@ has permissions: %@, is owned by %@ and needs repair...\n",currentPath,octalString,fileOwner); michael@1: return YES; michael@1: } michael@1: } michael@1: michael@1: // check tun and tap driver packages michael@1: NSArray *filesToCheck = [NSArray arrayWithObjects:tunPath,tapPath,nil]; michael@1: NSEnumerator *enumerator = [filesToCheck objectEnumerator]; michael@1: NSString *file; michael@1: while(file = [enumerator nextObject]) { michael@1: NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:file traverseLink:YES]; michael@1: unsigned long perms = [fileAttributes filePosixPermissions]; michael@1: NSString *octalString = [NSString stringWithFormat:@"%o",perms]; michael@1: if ( (![octalString isEqualToString:@"755"]) ) { michael@1: NSLog(@"File %@ has permissions: %@ and needs repair...\n",currentPath,octalString); michael@1: return YES; michael@1: } michael@1: } michael@1: return NO; michael@1: } michael@1: michael@1: -(void)willGoToSleep michael@1: { michael@1: if(NSDebugEnabled) NSLog(@"Computer will go to sleep...\n"); michael@1: connectionsToRestore = [connectionArray mutableCopy]; michael@1: [self killAllConnections]; michael@1: } michael@1: -(void)wokeUpFromSleep michael@1: { michael@1: if(NSDebugEnabled) NSLog(@"Computer just woke up from sleep...\n"); michael@1: michael@1: NSEnumerator *e = [connectionsToRestore objectEnumerator]; michael@1: VPNConnection *connection; michael@1: while(connection = [e nextObject]) { michael@1: if(NSDebugEnabled) NSLog(@"Restoring Connection %@",[connection configName]); michael@1: [connection connect:self]; michael@1: } michael@1: } michael@1: int runUnrecoverableErrorPanel(void) michael@1: { michael@1: 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: [panel setLevel:NSStatusWindowLevel]; michael@1: [panel makeKeyAndOrderFront:nil]; michael@1: if( [NSApp runModalForWindow:panel] != NSAlertDefaultReturn ) { michael@1: exit(2); michael@1: } else { michael@1: [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://tunnelblick.net/"]]; michael@1: exit(2); michael@1: } michael@1: } michael@1: michael@1: -(IBAction) autoLaunchPrefButtonWasClicked: (id) sender michael@1: { michael@1: if([sender state]) { michael@1: [self saveAutoLaunchCheckboxState:TRUE]; michael@1: } else { michael@1: [self saveAutoLaunchCheckboxState:FALSE]; michael@1: } michael@1: } michael@1: michael@1: -(IBAction) nameserverPrefButtonWasClicked: (id) sender michael@1: { michael@1: if([sender state]) { michael@1: [self saveUseNameserverCheckboxState:TRUE]; michael@1: } else { michael@1: [self saveUseNameserverCheckboxState:FALSE]; michael@1: } michael@1: } michael@1: michael@1: michael@1: @end