diff options
author | rowanbeentje <rowan@beent.je> | 2009-06-04 01:44:16 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-06-04 01:44:16 +0000 |
commit | 31123c9ff149908120a5bca94e76f5023c0b7e1e (patch) | |
tree | f76933e4c19d63cd17f160919731fbb5c9162b75 /Source | |
parent | fc748400d92a0b7874a19eba3fa573cdf1415ee5 (diff) | |
download | sequelpro-31123c9ff149908120a5bca94e76f5023c0b7e1e.tar.gz sequelpro-31123c9ff149908120a5bca94e76f5023c0b7e1e.tar.bz2 sequelpro-31123c9ff149908120a5bca94e76f5023c0b7e1e.zip |
Further SSH tunnel improvements, password handling improvements, and minor bugfixes:
- SSH tunnels can now correctly show dialogs for ssh queries, eg host key mismatches
- SSH tunnels are now correctly closed by the document for connection failures
- Keychain password item name and account generation has been moved to within the keychain class, to centralise generation for consistency
- Keychain item names and accounts now correctly deal with nil values, allowing more keychain items to be read
- "Add to favorites" button and menu item now correctly store passwords and SSH tunnel settings
- Duplicating favorites in preferences now selects the newly created favorite instead of deselecting everything
- Fixes an occasional crasher sometimes encountered in keychain usage
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CMMCPConnection.m | 2 | ||||
-rw-r--r-- | Source/KeyChain.h | 4 | ||||
-rw-r--r-- | Source/KeyChain.m | 62 | ||||
-rw-r--r-- | Source/SPPreferenceController.m | 63 | ||||
-rw-r--r-- | Source/SPSSHTunnel.h | 17 | ||||
-rw-r--r-- | Source/SPSSHTunnel.m | 87 | ||||
-rw-r--r-- | Source/TableDocument.h | 3 | ||||
-rw-r--r-- | Source/TableDocument.m | 118 | ||||
-rw-r--r-- | Source/TunnelPassphraseRequester.m | 27 |
9 files changed, 273 insertions, 110 deletions
diff --git a/Source/CMMCPConnection.m b/Source/CMMCPConnection.m index aa2dd430..4ee9263e 100644 --- a/Source/CMMCPConnection.m +++ b/Source/CMMCPConnection.m @@ -239,6 +239,7 @@ static void forcePingTimeout(int signalNumber); theRet = mysql_real_connect(mConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); thePass = NULL; if (theRet != mConnection) { + if (connectionTunnel) [connectionTunnel disconnect]; return mConnected = NO; } @@ -576,6 +577,7 @@ static void forcePingTimeout(int signalNumber); [self startKeepAliveTimerResettingState:YES]; return YES; } + if (connectionTunnel) [connectionTunnel disconnect]; return NO; } diff --git a/Source/KeyChain.h b/Source/KeyChain.h index 086ee6a7..966a8c04 100644 --- a/Source/KeyChain.h +++ b/Source/KeyChain.h @@ -32,5 +32,9 @@ - (NSString *)getPasswordForName:(NSString *)name account:(NSString *)account; - (void)deletePasswordForName:(NSString *)name account:(NSString *)account; - (BOOL)passwordExistsForName:(NSString *)name account:(NSString *)account; +- (NSString *)nameForFavoriteName:(NSString *)theName id:(NSString *)theID; +- (NSString *)accountForUser:(NSString *)theUser host:(NSString *)theHost database:(NSString *)theDatabase; +- (NSString *)nameForSSHForFavoriteName:(NSString *)theName id:(NSString *)theID; +- (NSString *)accountForSSHUser:(NSString *)theSSHUser sshHost:(NSString *)theSSHHost; @end diff --git a/Source/KeyChain.m b/Source/KeyChain.m index 032db61e..da3e761c 100644 --- a/Source/KeyChain.m +++ b/Source/KeyChain.m @@ -164,9 +164,8 @@ - (BOOL)passwordExistsForName:(NSString *)name account:(NSString *)account { SecKeychainItemRef item; - SecKeychainSearchRef search; + SecKeychainSearchRef search = NULL; int numberOfItemsFound = 0; - SecKeychainAttributeList list; SecKeychainAttribute attributes[2]; @@ -188,9 +187,66 @@ } } - CFRelease(search); + if (search) CFRelease(search); return (numberOfItemsFound > 0); } +/** + * Retrieve the keychain item name for a supplied name and id. + */ +- (NSString *)nameForFavoriteName:(NSString *)theName id:(NSString *)theID +{ + NSString *keychainItemName; + + keychainItemName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", + theName, + [theID intValue]]; + + return keychainItemName; +} + +/** + * Retrieve the keychain item account for a supplied user, host, and database - which can be nil. + */ +- (NSString *)accountForUser:(NSString *)theUser host:(NSString *)theHost database:(NSString *)theDatabase +{ + NSString *keychainItemAccount; + + keychainItemAccount = [NSString stringWithFormat:@"%@@%@/%@", + theUser?theUser:@"", + theHost?theHost:@"", + theDatabase?theDatabase:@""]; + + return keychainItemAccount; +} + +/** + * Retrieve the keychain SSH item name for a supplied name and id. + */ +- (NSString *)nameForSSHForFavoriteName:(NSString *)theName id:(NSString *)theID +{ + NSString *sshKeychainItemName; + + sshKeychainItemName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", + theName, + [theID intValue]]; + + return sshKeychainItemName; +} + +/** + * Retrieve the keychain SSH item account for a supplied SSH user and host - which can be nil. + */ +- (NSString *)accountForSSHUser:(NSString *)theSSHUser sshHost:(NSString *)theSSHHost +{ + NSString *sshKeychainItemAccount; + + sshKeychainItemAccount = [NSString stringWithFormat:@"%@@%@", + theSSHUser?theSSHUser:@"", + theSSHHost?theSSHHost:@""]; + + return sshKeychainItemAccount; +} + @end diff --git a/Source/SPPreferenceController.m b/Source/SPPreferenceController.m index ce05117e..b6395c3c 100644 --- a/Source/SPPreferenceController.m +++ b/Source/SPPreferenceController.m @@ -259,13 +259,13 @@ NSString *database = [favoritesController valueForKeyPath:@"selection.database"]; NSString *sshUser = [favoritesController valueForKeyPath:@"selection.sshUser"]; NSString *sshHost = [favoritesController valueForKeyPath:@"selection.sshHost"]; - int favoriteid = [[favoritesController valueForKeyPath:@"selection.id"] intValue]; + NSString *favoriteid = [favoritesController valueForKeyPath:@"selection.id"]; // Remove passwords from the Keychain - [keychain deletePasswordForName:[NSString stringWithFormat:@"Sequel Pro : %@ (%i)", name, favoriteid] - account:[NSString stringWithFormat:@"%@@%@/%@", user, host, database]]; - [keychain deletePasswordForName:[NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", name, favoriteid] - account:[NSString stringWithFormat:@"%@@%@", sshUser, sshHost]]; + [keychain deletePasswordForName:[keychain nameForFavoriteName:name id:favoriteid] + account:[keychain accountForUser:user host:host database:database]]; + [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:name id:favoriteid] + account:[keychain accountForSSHUser:sshUser sshHost:sshHost]]; // Reset last used favorite if ([favoritesTableView selectedRow] == [prefs integerForKey:@"LastFavoriteIndex"]) { @@ -295,12 +295,11 @@ NSNumber *favoriteid = [NSNumber numberWithInt:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; // Select the keychain passwords for duplication - keychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [favorite objectForKey:@"name"], [[favorite objectForKey:@"id"] intValue]]; - keychainAccount = [NSString stringWithFormat:@"%@@%@/%@", - [favorite objectForKey:@"user"], [favorite objectForKey:@"host"], [favorite objectForKey:@"database"]]; + keychainName = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]]; + keychainAccount = [keychain accountForUser:[favorite objectForKey:@"user"] host:[favorite objectForKey:@"host"] database:[favorite objectForKey:@"database"]]; password = [keychain getPasswordForName:keychainName account:keychainAccount]; - keychainSSHName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [favorite objectForKey:@"name"], [[favorite objectForKey:@"id"] intValue]]; - keychainSSHAccount = [NSString stringWithFormat:@"%@@%@", [favorite objectForKey:@"sshUser"], [favorite objectForKey:@"sshHost"]]; + keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]]; + keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:@"sshUser"] sshHost:[favorite objectForKey:@"sshHost"]]; sshPassword = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]; // Update the unique ID @@ -311,16 +310,17 @@ // Create new keychain items if appropriate if (password && [password length]) { - keychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [favorite objectForKey:@"name"], [[favorite objectForKey:@"id"] intValue]]; + keychainName = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]]; [keychain addPassword:password forName:keychainName account:keychainAccount]; } if (sshPassword && [sshPassword length]) { - keychainSSHName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [favorite objectForKey:@"name"], [[favorite objectForKey:@"id"] intValue]]; + keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]]; [keychain addPassword:sshPassword forName:keychainSSHName account:keychainSSHAccount]; } password = nil, sshPassword = nil; [favoritesController addObject:favorite]; + [favoritesController setSelectionIndex:[[favoritesController arrangedObjects] count]-1]; [favoritesTableView reloadData]; [self updateDefaultFavoritePopup]; @@ -561,19 +561,14 @@ } // Otherwise retrieve and set the password. - NSString *keychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [favoritesController valueForKeyPath:@"selection.name"], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - NSString *keychainAccount = [NSString stringWithFormat:@"%@@%@/%@", - [favoritesController valueForKeyPath:@"selection.user"], - [favoritesController valueForKeyPath:@"selection.host"], - [favoritesController valueForKeyPath:@"selection.database"]]; + NSString *keychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + NSString *keychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; [passwordField setStringValue:[keychain getPasswordForName:keychainName account:keychainAccount]]; // Retrieve the SSH keychain password if appropriate. - NSString *keychainSSHName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [favoritesController valueForKeyPath:@"selection.name"], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - NSString *keychainSSHAccount = [NSString stringWithFormat:@"%@@%@", - [favoritesController valueForKeyPath:@"selection.sshUser"], - [favoritesController valueForKeyPath:@"selection.sshHost"]]; + NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + NSString *keychainSSHAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]]; [sshPasswordField setStringValue:[keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]]; } @@ -683,18 +678,12 @@ || control == passwordField) { // Get the current keychain name and account strings - oldKeychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [favoritesController valueForKeyPath:@"selection.name"], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - oldKeychainAccount = [NSString stringWithFormat:@"%@@%@/%@", - [favoritesController valueForKeyPath:@"selection.user"], - [favoritesController valueForKeyPath:@"selection.host"], - [favoritesController valueForKeyPath:@"selection.database"]]; + oldKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + oldKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; // Set up the new keychain name and account strings - newKeychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [nameField stringValue], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - newKeychainAccount = [NSString stringWithFormat:@"%@@%@/%@", - [userField stringValue], - [hostField stringValue], - [databaseField stringValue]]; + newKeychainName = [keychain nameForFavoriteName:[nameField stringValue] id:[favoritesController valueForKeyPath:@"selection.id"]]; + newKeychainAccount = [keychain accountForUser:[userField stringValue] host:[hostField stringValue] database:[databaseField stringValue]]; // Delete the old keychain item [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; @@ -712,16 +701,12 @@ || control == sshPasswordField) { // Get the current keychain name and account strings - oldKeychainName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [favoritesController valueForKeyPath:@"selection.name"], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - oldKeychainAccount = [NSString stringWithFormat:@"%@@%@", - [favoritesController valueForKeyPath:@"selection.sshUser"], - [favoritesController valueForKeyPath:@"selection.sshHost"]]; + oldKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + oldKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]]; // Set up the new keychain name and account strings - newKeychainName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [nameField stringValue], [[favoritesController valueForKeyPath:@"selection.id"] intValue]]; - newKeychainAccount = [NSString stringWithFormat:@"%@@%@", - [sshUserField stringValue], - [sshHostField stringValue]]; + newKeychainName = [keychain nameForFavoriteName:[nameField stringValue] id:[favoritesController valueForKeyPath:@"selection.id"]]; + newKeychainAccount = [keychain accountForSSHUser:[sshUserField stringValue] sshHost:[sshHostField stringValue]]; // Delete the old keychain item [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; diff --git a/Source/SPSSHTunnel.h b/Source/SPSSHTunnel.h index 91bd08de..612ef595 100644 --- a/Source/SPSSHTunnel.h +++ b/Source/SPSSHTunnel.h @@ -17,14 +17,18 @@ enum spsshtunnel_password_modes @interface SPSSHTunnel : NSObject { + IBOutlet NSWindow *sshQuestionDialog; + IBOutlet NSTextField *sshQuestionText; + + NSWindow *parentWindow; NSTask *task; NSPipe *standardError; id delegate; SEL stateChangeSelector; - NSConnection *passwordConnection; + NSConnection *tunnelConnection; NSString *lastError; - NSString *passwordConnectionName; - NSString *passwordConnectionVerifyHash; + NSString *tunnelConnectionName; + NSString *tunnelConnectionVerifyHash; NSString *sshHost; NSString *sshLogin; NSString *remoteHost; @@ -40,15 +44,18 @@ enum spsshtunnel_password_modes - (id) initToHost:(NSString *) theHost port:(int) thePort login:(NSString *) theLogin tunnellingToPort:(int) targetPort onHost:(NSString *) targetHost; - (BOOL) setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate; -- (BOOL) setPassword:(NSString *)thePassword; +- (void) setParentWindow:(NSWindow *)theWindow; - (BOOL) setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount; +- (BOOL) setPassword:(NSString *)thePassword; - (int) state; - (NSString *) lastError; - (int) localPort; - (void) connect; - (void) launchTask:(id) dummy; -- (void)disconnect; +- (void) disconnect; - (void) standardErrorHandler:(NSNotification*)aNotification; - (NSString *) getPasswordWithVerificationHash:(NSString *)theHash; +- (BOOL) getResponseForQuestion:(NSString *)theQuestion; +- (IBAction) closeSheet:(id)sender; @end diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 433ccad4..c4c01783 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -53,7 +53,17 @@ stateChangeSelector = nil; lastError = nil; - passwordConnection = nil; + // Set up a connection for use by the tunnel process + tunnelConnectionName = [NSString stringWithFormat:@"SequelPro-%f", [[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; + tunnelConnection = [[NSConnection defaultConnection] retain]; + [tunnelConnection runInNewThread]; + [tunnelConnection removeRunLoop:[NSRunLoop currentRunLoop]]; + [tunnelConnection setRootObject:self]; + if ([tunnelConnection registerName:tunnelConnectionName] == NO) { + return nil; + } + + parentWindow = nil; password = nil; keychainName = nil; keychainAccount = nil; @@ -78,6 +88,18 @@ } /* + * Set the parent window of the connection for use with dialogs. + */ +- (void)setParentWindow:(NSWindow *)theWindow +{ + parentWindow = theWindow; + if (![NSBundle loadNibNamed:@"SSHQuestionDialog" owner:self]) { + NSLog(@"SSH query dialog could not be loaded; SSH tunnels will not function correctly."); + parentWindow = nil; + } +} + +/* * Sets the password to be stored (and returned to the tunnel authenticator) locally. * Providing a keychain name is much more secure. */ @@ -85,16 +107,7 @@ { if (passwordInKeychain) return NO; password = [[NSString alloc] initWithString:thePassword]; - passwordConnection = [[NSConnection defaultConnection] retain]; - [passwordConnection runInNewThread]; - [passwordConnection removeRunLoop:[NSRunLoop currentRunLoop]]; - [passwordConnection setRootObject:self]; - passwordConnectionName = [NSString stringWithFormat:@"SequelPro-%f", [[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; - passwordConnectionVerifyHash = [NSString stringWithFormat:@"%f", [[NSString stringWithFormat:@"%f%i", [[NSDate date] timeIntervalSince1970]] hash]]; - if ([passwordConnection registerName:passwordConnectionName] == NO) { - [password release], password = nil; - return NO; - } + tunnelConnectionVerifyHash = [NSString stringWithFormat:@"%f", [[NSString stringWithFormat:@"%f%i", [[NSDate date] timeIntervalSince1970]] hash]]; return YES; } @@ -105,7 +118,6 @@ */ - (BOOL) setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount { - if (passwordConnection) [passwordConnection release], passwordConnection = nil; if (password) [password release], password = nil; passwordInKeychain = YES; @@ -157,6 +169,16 @@ connectionState = SPSSH_STATE_CONNECTING; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; + // Enforce a parent window being present for dialogs + if (!parentWindow) { + connectionState = SPSSH_STATE_IDLE; + if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; + if (lastError) [lastError release]; + lastError = [[NSString alloc] initWithString:@"SSH Tunnel started without a parent window. A parent window must be present."]; + [pool release]; + return; + } + int connectionTimeout = [[[NSUserDefaults standardUserDefaults] objectForKey:@"ConnectionTimeout"] intValue]; if (!connectionTimeout) connectionTimeout = 10; BOOL useKeepAlive = [[[NSUserDefaults standardUserDefaults] objectForKey:@"UseKeepAlive"] doubleValue]; @@ -221,14 +243,14 @@ [taskEnvironment removeObjectForKey: @"SSH_AUTH_SOCK"]; [taskEnvironment setObject:authenticationAppPath forKey:@"SSH_ASKPASS"]; [taskEnvironment setObject:@":0" forKey:@"DISPLAY"]; + [taskEnvironment setObject:tunnelConnectionName forKey:@"SP_CONNECTION_NAME"]; if (passwordInKeychain) { [taskEnvironment setObject:[[NSNumber numberWithInt:SPSSH_PASSWORD_USES_KEYCHAIN] stringValue] forKey:@"SP_PASSWORD_METHOD"]; [taskEnvironment setObject:keychainName forKey:@"SP_KEYCHAIN_ITEM_NAME"]; [taskEnvironment setObject:keychainAccount forKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; } else { [taskEnvironment setObject:[[NSNumber numberWithInt:SPSSH_PASSWORD_ASKS_UI] stringValue] forKey:@"SP_PASSWORD_METHOD"]; - [taskEnvironment setObject:passwordConnectionName forKey:@"SP_CONNECTION_NAME"]; - [taskEnvironment setObject:passwordConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; + [taskEnvironment setObject:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; } [task setEnvironment:taskEnvironment]; @@ -360,8 +382,43 @@ - (NSString *)getPasswordWithVerificationHash:(NSString *)theHash { if (passwordInKeychain) return nil; - if (![theHash isEqualToString:passwordConnectionVerifyHash]) return nil; + if (![theHash isEqualToString:tunnelConnectionVerifyHash]) return nil; return password; } +/* + * Method to allow an SSH tunnel to request the response to a question, returning the response as + * a boolean. This is used by the SSH_ASKPASS environment setting to deal with situations like + * host key mismatches. + */ +- (BOOL) getResponseForQuestion:(NSString *)theQuestion +{ + + // Ask how to proceed + [sshQuestionText setStringValue:theQuestion]; + [NSApp beginSheet:sshQuestionDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; + int sshQueryResponseCode = [NSApp runModalForWindow:sshQuestionDialog]; + [NSApp endSheet:sshQuestionDialog]; + [sshQuestionDialog orderOut:nil]; + + switch (sshQueryResponseCode) { + + // Yes + case 1: + return YES; + + // No + default: + return NO; + } +} + +/* + * Ends an existing modal session + */ +- (IBAction) closeSheet:(id)sender +{ + [NSApp stopModalWithCode:[sender tag]]; +} + @end diff --git a/Source/TableDocument.h b/Source/TableDocument.h index e71e80ac..c996a0bd 100644 --- a/Source/TableDocument.h +++ b/Source/TableDocument.h @@ -92,6 +92,7 @@ IBOutlet NSWindow *createTableSyntaxWindow; CMMCPConnection *mySQLConnection; + SPSSHTunnel *sshTunnel; NSArray *variables; NSString *selectedDatabase; @@ -121,7 +122,7 @@ - (IBAction)initiateConnection:(id)sender; - (void)initiateSSHTunnelConnection; - (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel; -- (void)initiateMySQLConnection:(SPSSHTunnel *)theTunnel; +- (void)initiateMySQLConnection; - (void)failConnectionWithErrorMessage:(NSString *)theErrorMessage; - (IBAction)cancelConnectSheet:(id)sender; - (IBAction)closeSheet:(id)sender; diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 5ed89519..d0b04d5e 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -73,6 +73,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum connectionSSHKeychainItemName = nil; connectionSSHKeychainItemAccount = nil; selectedDatabase = nil; + sshTunnel = nil; printWebView = [[WebView alloc] init]; [printWebView setFrameLoadDelegate:self]; @@ -277,8 +278,8 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum [self failConnectionWithErrorMessage:NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host or socket.", @"insufficient details informative message")]; return; } - if ([sshCheckbox state] == NSOnState && (![[sshHostField stringValue] length] || ![[sshUserField stringValue] length])) { - [self failConnectionWithErrorMessage:NSLocalizedString(@"Please enter the hostname and username for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete")]; + if ([sshCheckbox state] == NSOnState && ![[sshHostField stringValue] length]) { + [self failConnectionWithErrorMessage:NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete")]; return; } @@ -292,7 +293,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum // for increased security. if (connectionKeychainItemName) { if ([[keyChainInstance getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount] isEqualToString:[passwordField stringValue]]) { - [passwordField setStringValue:@""]; + [passwordField setStringValue:[[NSString string] stringByPaddingToLength:[[passwordField stringValue] length] withString:@"sp" startingAtIndex:0]]; [[self undoManager] removeAllActionsWithTarget:passwordField]; } else { [connectionKeychainItemName release], connectionKeychainItemName = nil; @@ -301,7 +302,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum } if (connectionSSHKeychainItemName) { if ([[keyChainInstance getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount] isEqualToString:[sshPasswordField stringValue]]) { - [sshPasswordField setStringValue:@""]; + [sshPasswordField setStringValue:[[NSString string] stringByPaddingToLength:[[sshPasswordField stringValue] length] withString:@"sp" startingAtIndex:0]]; [[self undoManager] removeAllActionsWithTarget:sshPasswordField]; } else { [connectionSSHKeychainItemName release], connectionSSHKeychainItemName = nil; @@ -316,7 +317,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum } // ...or start the MySQL connection process directly - [self initiateMySQLConnection:nil]; + [self initiateMySQLConnection]; } /* @@ -326,26 +327,26 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum */ - (void)initiateSSHTunnelConnection { - SPSSHTunnel *theTunnel; [connectProgressStatusText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; [connectProgressStatusText display]; // Set up the tunnel details - theTunnel = [[SPSSHTunnel alloc] initToHost:[sshHostField stringValue] port:([[sshPortField stringValue] length]?[sshPortField intValue]:22) login:[sshUserField stringValue] tunnellingToPort:([[portField stringValue] length]?[portField intValue]:3306) onHost:[hostField stringValue]]; + sshTunnel = [[SPSSHTunnel alloc] initToHost:[sshHostField stringValue] port:([[sshPortField stringValue] length]?[sshPortField intValue]:22) login:[sshUserField stringValue] tunnellingToPort:([[portField stringValue] length]?[portField intValue]:3306) onHost:[hostField stringValue]]; + [sshTunnel setParentWindow:tableWindow]; // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. if (connectionSSHKeychainItemName) { - [theTunnel setPasswordKeychainName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; + [sshTunnel setPasswordKeychainName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; } else { - [theTunnel setPassword:[sshPasswordField stringValue]]; + [sshTunnel setPassword:[sshPasswordField stringValue]]; } // Set the callback function on the tunnel - [theTunnel setConnectionStateChangeSelector:@selector(sshTunnelCallback:) delegate:self]; + [sshTunnel setConnectionStateChangeSelector:@selector(sshTunnelCallback:) delegate:self]; // Ask the tunnel to connect. This will call the callback below on success or failure, passing // itself as an argument - retain count should be one at this point. - [theTunnel connect]; + [sshTunnel connect]; } /* @@ -360,19 +361,19 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum if (newState == SPSSH_STATE_IDLE) { [self failConnectionWithErrorMessage:[theTunnel lastError]]; } else if (newState == SPSSH_STATE_CONNECTED) { - [self initiateMySQLConnection:theTunnel]; + [self initiateMySQLConnection]; } } /* * Set up the MySQL connection, either through a successful tunnel or directly. */ -- (void)initiateMySQLConnection:(SPSSHTunnel *)theTunnel +- (void)initiateMySQLConnection { CMMCPResult *theResult; id version; - if (theTunnel) + if (sshTunnel) [connectProgressStatusText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; else [connectProgressStatusText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; @@ -388,12 +389,12 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum // Otherwise, initialise to host, using tunnel if appropriate } else { - if (theTunnel) { + if (sshTunnel) { mySQLConnection = [[CMMCPConnection alloc] initToHost:@"127.0.0.1" withLogin:[userField stringValue] - usingPort:[theTunnel localPort]]; - [mySQLConnection setSSHTunnel:theTunnel]; - [theTunnel release]; + usingPort:[sshTunnel localPort]]; + [mySQLConnection setSSHTunnel:sshTunnel]; + [sshTunnel release], sshTunnel = nil; } else { mySQLConnection = [[CMMCPConnection alloc] initToHost:[hostField stringValue] withLogin:[userField stringValue] @@ -414,7 +415,6 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum if (![mySQLConnection isConnected]) { [self failConnectionWithErrorMessage:[NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@, or the request timed out.\n\nBe sure that the address is correct and that you have the necessary privileges, or try increasing the connection timeout (currently %i seconds).\n\nMySQL said: %@", @"message of panel when connection to host failed"), [hostField stringValue], [[prefs objectForKey:@"ConnectionTimeoutValue"] intValue], [mySQLConnection getLastErrorMessage]]]; - if (theTunnel) [theTunnel disconnect]; return; } if (![[databaseField stringValue] isEqualToString:@""]) { @@ -423,7 +423,6 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum selectedDatabase = [[databaseField stringValue] retain]; } else { [self failConnectionWithErrorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [databaseField stringValue], [mySQLConnection getLastErrorMessage]]]; - if (theTunnel) [theTunnel disconnect]; return; } } @@ -500,6 +499,9 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum // Stop the modal sheet [connectSheet orderOut:nil]; [NSApp endSheet:connectSheet]; + + // Release as appropriate + if (sshTunnel) [sshTunnel disconnect], [sshTunnel release], sshTunnel = nil; // Display the connection error message NSBeginAlertSheet(NSLocalizedString(@"Connection failed!", @"connection failed title"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, @selector(sheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); @@ -548,11 +550,8 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum // Check whether the password exists in the keychain, and if so add it; also record the // keychain details so we can pass around only those details if the password doesn't change - connectionKeychainItemName = [[NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [self valueForKeyPath:@"selectedFavorite.name"], [[self valueForKeyPath:@"selectedFavorite.id"] intValue]] retain]; - connectionKeychainItemAccount = [[NSString stringWithFormat:@"%@@%@/%@", - [self valueForKeyPath:@"selectedFavorite.user"], - [self valueForKeyPath:@"selectedFavorite.host"], - [self valueForKeyPath:@"selectedFavorite.database"]] retain]; + connectionKeychainItemName = [[keyChainInstance nameForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]] retain]; + connectionKeychainItemAccount = [[keyChainInstance accountForUser:[self valueForKeyPath:@"selectedFavorite.user"] host:[self valueForKeyPath:@"selectedFavorite.host"] database:[self valueForKeyPath:@"selectedFavorite.database"]] retain]; if ([keyChainInstance passwordExistsForName:connectionKeychainItemName account:connectionKeychainItemAccount]) { [passwordField setStringValue:[keyChainInstance getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]]; } else { @@ -562,10 +561,8 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum } // And the same for the SSH password - connectionSSHKeychainItemName = [[NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)", [self valueForKeyPath:@"selectedFavorite.name"], [[self valueForKeyPath:@"selectedFavorite.id"] intValue]] retain]; - connectionSSHKeychainItemAccount = [[NSString stringWithFormat:@"%@@%@", - [self valueForKeyPath:@"selectedFavorite.sshUser"], - [self valueForKeyPath:@"selectedFavorite.sshHost"]] retain]; + connectionSSHKeychainItemName = [[NSString alloc] initWithString:[keyChainInstance nameForSSHForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]]]; + connectionSSHKeychainItemAccount = [[NSString alloc] initWithString:[keyChainInstance accountForSSHUser:[self valueForKeyPath:@"selectedFavorite.sshUser"] sshHost:[self valueForKeyPath:@"selectedFavorite.sshHost"]]]; if ([keyChainInstance passwordExistsForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]) { [sshPasswordField setStringValue:[keyChainInstance getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]]; } else { @@ -619,7 +616,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum - (void)connectSheetAddToFavorites:(id)sender { - [self addToFavoritesName:[nameField stringValue] host:[hostField stringValue] socket:[socketField stringValue] user:[userField stringValue] password:[passwordField stringValue] port:[portField stringValue] database:[databaseField stringValue] useSSH:false sshHost:@"" sshUser:@"" sshPassword:@"" sshPort:@""]; + [self addToFavoritesName:[nameField stringValue] host:[hostField stringValue] socket:[socketField stringValue] user:[userField stringValue] password:[passwordField stringValue] port:[portField stringValue] database:[databaseField stringValue] useSSH:([sshCheckbox state] == NSOnState) sshHost:[sshHostField stringValue] sshUser:[sshUserField stringValue] sshPassword:[sshPasswordField stringValue] sshPort:[sshPortField stringValue]]; } /** @@ -628,31 +625,50 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum - (void)addToFavoritesName:(NSString *)name host:(NSString *)host socket:(NSString *)socket user:(NSString *)user password:(NSString *)password port:(NSString *)port database:(NSString *)database - useSSH:(BOOL)useSSH // no-longer in use - sshHost:(NSString *)sshHost // no-longer in use - sshUser:(NSString *)sshUser // no-longer in use - sshPassword:(NSString *)sshPassword // no-longer in use - sshPort:(NSString *)sshPort // no-longer in use + useSSH:(BOOL)useSSH + sshHost:(NSString *)sshHost sshUser:(NSString *)sshUser + sshPassword:(NSString *)sshPassword sshPort:(NSString *)sshPort { NSString *favoriteName = [name length]?name:[NSString stringWithFormat:@"%@@%@", user, host]; NSNumber *favoriteid = [NSNumber numberWithInt:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; if (![name length] && ![database isEqualToString:@""]) favoriteName = [NSString stringWithFormat:@"%@ %@", database, favoriteName]; - // test if host and socket are not nil + // Ensure that host and socket are not nil if ([host isEqualToString:@""] && [socket isEqualToString:@""]) { NSRunAlertPanel(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host or socket.", @"insufficient details informative message"), NSLocalizedString(@"OK", @"OK button"), nil, nil); - return; } - // write favorites and password - NSMutableDictionary *newFavorite = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:favoriteName, host, socket, user, port, database, favoriteid, nil] - forKeys:[NSArray arrayWithObjects:@"name", @"host", @"socket", @"user", @"port", @"database", @"id", nil]]; + // If SSH is enabled, ensure that the SSH host is not nil + if ([sshCheckbox state] == NSOnState && ![[sshHostField stringValue] length]) { + NSRunAlertPanel(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete"), NSLocalizedString(@"OK", @"OK button"), nil, nil); + return; + } + + // Write favorites and password(s) + NSDictionary *newFavorite = [NSDictionary dictionaryWithObjectsAndKeys: + favoriteName, @"name", + host, @"host", + socket, @"socket", + user, @"user", + port, @"port", + database, @"database", + [NSNumber numberWithBool:useSSH], @"useSSH", + sshHost, @"sshHost", + sshUser, @"sshUser", + sshPort, @"sshPort", + favoriteid, @"id", + nil]; if (![password isEqualToString:@""]) { [keyChainInstance addPassword:password - forName:[NSString stringWithFormat:@"Sequel Pro : %@ (%i)", favoriteName, [favoriteid intValue]] - account:[NSString stringWithFormat:@"%@@%@/%@", user, host, database]]; + forName:[keyChainInstance nameForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%i", [favoriteid intValue]]] + account:[keyChainInstance accountForUser:user host:host database:database]]; + } + if (![sshPassword isEqualToString:@""]) { + [keyChainInstance addPassword:password + forName:[keyChainInstance nameForSSHForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%i", [favoriteid intValue]]] + account:[keyChainInstance accountForSSHUser:sshUser sshHost:sshHost]]; } [favoritesController addObject:newFavorite]; @@ -1568,7 +1584,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum - (void)closeConnection { [mySQLConnection disconnect]; - + // Disconnected Growl notification [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Disconnected" description:[NSString stringWithFormat:NSLocalizedString(@"Disconnected from %@",@"description for disconnected growl notification"), [tableWindow title]] @@ -1858,8 +1874,19 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum return; } - // Add current connection to favorites using the same method as used on the connection sheet to provide consistency. - [self connectSheetAddToFavorites:self]; + // Add current connection to favorites + NSString *password, *sshPassword; + if (connectionKeychainItemName) { + password = [keyChainInstance getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]; + } else { + password = [passwordField stringValue]; + } + if (connectionSSHKeychainItemName) { + sshPassword = [keyChainInstance getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; + } else { + sshPassword = [sshPasswordField stringValue]; + } + [self addToFavoritesName:[nameField stringValue] host:[hostField stringValue] socket:[socketField stringValue] user:[userField stringValue] password:password port:[portField stringValue] database:[databaseField stringValue] useSSH:([sshCheckbox state] == NSOnState) sshHost:[sshHostField stringValue] sshUser:[sshUserField stringValue] sshPassword:sshPassword sshPort:[sshPortField stringValue]]; } /** @@ -2151,6 +2178,7 @@ NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocum - (void)windowWillClose:(NSNotification *)aNotification { if ([mySQLConnection isConnected]) [self closeConnection]; + if (sshTunnel) [sshTunnel disconnect], [sshTunnel release], sshTunnel = nil; if ([[[SPQueryConsole sharedQueryConsole] window] isVisible]) [self toggleConsole:self]; [[customQueryInstance helpWebViewWindow] release]; [createTableSyntaxWindow release]; diff --git a/Source/TunnelPassphraseRequester.m b/Source/TunnelPassphraseRequester.m index a39c4fd1..4391e3e7 100644 --- a/Source/TunnelPassphraseRequester.m +++ b/Source/TunnelPassphraseRequester.m @@ -28,12 +28,37 @@ int main(int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + NSString *argument = nil; + SPSSHTunnel *sequelProTunnel; + NSString *connectionName = [environment objectForKey:@"SP_CONNECTION_NAME"]; if (![environment objectForKey:@"SP_PASSWORD_METHOD"]) { [pool release]; return 1; } + if (argc > 1) { + argument = [[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding]; + } + + // Check if we're being asked a question and respond if so + if (argument && [argument rangeOfString:@" (yes/no)?"].location != NSNotFound) { + sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; + if (!sequelProTunnel) { + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); + [pool release]; + return 1; + } + BOOL response = [sequelProTunnel getResponseForQuestion:argument]; + if (response) { + printf("yes\n"); + } else { + printf("no\n"); + } + [pool release]; + return 0; + } + // If the password method is set to use the keychain, use the supplied keychain name to // request the password if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] intValue] == SPSSH_PASSWORD_USES_KEYCHAIN) { @@ -61,9 +86,7 @@ int main(int argc, const char *argv[]) // If the password method is set to request the password from the tunnel instance, do so. if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] intValue] == SPSSH_PASSWORD_ASKS_UI) { - SPSSHTunnel *sequelProTunnel; NSString *password; - NSString *connectionName = [environment objectForKey:@"SP_CONNECTION_NAME"]; NSString *verificationHash = [environment objectForKey:@"SP_CONNECTION_VERIFY_HASH"]; if (!connectionName || !verificationHash) { |