aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-06-04 01:44:16 +0000
committerrowanbeentje <rowan@beent.je>2009-06-04 01:44:16 +0000
commit31123c9ff149908120a5bca94e76f5023c0b7e1e (patch)
treef76933e4c19d63cd17f160919731fbb5c9162b75 /Source
parentfc748400d92a0b7874a19eba3fa573cdf1415ee5 (diff)
downloadsequelpro-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.m2
-rw-r--r--Source/KeyChain.h4
-rw-r--r--Source/KeyChain.m62
-rw-r--r--Source/SPPreferenceController.m63
-rw-r--r--Source/SPSSHTunnel.h17
-rw-r--r--Source/SPSSHTunnel.m87
-rw-r--r--Source/TableDocument.h3
-rw-r--r--Source/TableDocument.m118
-rw-r--r--Source/TunnelPassphraseRequester.m27
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) {