diff options
author | rowanbeentje <rowan@beent.je> | 2009-06-05 00:32:42 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-06-05 00:32:42 +0000 |
commit | 4e6c8e557f6dd132e159ff4841900d1b72331eea (patch) | |
tree | bd972c23c05f09be246f436b3bd511c4f5dbf23e /Source | |
parent | 905dd61bd91f3dce22655bf942302739544dd86c (diff) | |
download | sequelpro-4e6c8e557f6dd132e159ff4841900d1b72331eea.tar.gz sequelpro-4e6c8e557f6dd132e159ff4841900d1b72331eea.tar.bz2 sequelpro-4e6c8e557f6dd132e159ff4841900d1b72331eea.zip |
Further SSH tunnel improvements:
- SSH Public/private keys are now supported, even if they are password-protected. The user and password fields can be left blank where appropriate.
- SSH yes/no queries (ie host key mismatch) and password requests (eg key passphrases) dialogs now automatically resize to match the content from the SSH process.
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPSSHTunnel.h | 4 | ||||
-rw-r--r-- | Source/SPSSHTunnel.m | 61 | ||||
-rw-r--r-- | Source/TunnelPassphraseRequester.m | 95 |
3 files changed, 122 insertions, 38 deletions
diff --git a/Source/SPSSHTunnel.h b/Source/SPSSHTunnel.h index 612ef595..4486685a 100644 --- a/Source/SPSSHTunnel.h +++ b/Source/SPSSHTunnel.h @@ -19,6 +19,9 @@ enum spsshtunnel_password_modes { IBOutlet NSWindow *sshQuestionDialog; IBOutlet NSTextField *sshQuestionText; + IBOutlet NSWindow *sshPasswordDialog; + IBOutlet NSTextField *sshPasswordText; + IBOutlet NSSecureTextField *sshPasswordField; NSWindow *parentWindow; NSTask *task; @@ -56,6 +59,7 @@ enum spsshtunnel_password_modes - (void) standardErrorHandler:(NSNotification*)aNotification; - (NSString *) getPasswordWithVerificationHash:(NSString *)theHash; - (BOOL) getResponseForQuestion:(NSString *)theQuestion; +- (NSString *) getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash; - (IBAction) closeSheet:(id)sender; @end diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 83beb8a7..79ea780c 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -55,6 +55,7 @@ // Set up a connection for use by the tunnel process tunnelConnectionName = [NSString stringWithFormat:@"SequelPro-%f", [[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; + tunnelConnectionVerifyHash = [NSString stringWithFormat:@"%f", [[NSString stringWithFormat:@"%f%i", [[NSDate date] timeIntervalSince1970]] hash]]; tunnelConnection = [[NSConnection defaultConnection] retain]; [tunnelConnection runInNewThread]; [tunnelConnection removeRunLoop:[NSRunLoop currentRunLoop]]; @@ -107,7 +108,6 @@ { if (passwordInKeychain) return NO; password = [[NSString alloc] initWithString:thePassword]; - tunnelConnectionVerifyHash = [NSString stringWithFormat:@"%f", [[NSString stringWithFormat:@"%f%i", [[NSDate date] timeIntervalSince1970]] hash]]; return YES; } @@ -225,7 +225,6 @@ // [taskArguments addObject:@"-C"]; // TODO: compression? [taskArguments addObject:@"-o ExitOnForwardFailure=yes"]; [taskArguments addObject:[NSString stringWithFormat:@"-o ConnectTimeout=%i", connectionTimeout]]; - [taskArguments addObject:@"-o PubkeyAuthentication=yes"]; [taskArguments addObject:@"-o NumberOfPasswordPrompts=1"]; if (useKeepAlive && keepAliveInterval) { [taskArguments addObject:@"-o TCPKeepAlive=no"]; @@ -233,7 +232,11 @@ [taskArguments addObject:@"-o ServerAliveCountMax=1"]; } [taskArguments addObject:[NSString stringWithFormat:@"-p %i", sshPort]]; - [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", sshLogin, sshHost]]; + if ([sshLogin length]) { + [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", sshLogin, sshHost]]; + } else { + [taskArguments addObject:sshHost]; + } [taskArguments addObject:[NSString stringWithFormat:@"-L %i/%@/%i", localPort, remoteHost, remotePort]]; [task setArguments:taskArguments]; @@ -245,13 +248,13 @@ [taskEnvironment setObject:authenticationAppPath forKey:@"SSH_ASKPASS"]; [taskEnvironment setObject:@":0" forKey:@"DISPLAY"]; [taskEnvironment setObject:tunnelConnectionName forKey:@"SP_CONNECTION_NAME"]; + [taskEnvironment setObject:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; 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:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; } [task setEnvironment:taskEnvironment]; @@ -282,6 +285,8 @@ // Listen for output [task waitUntilExit]; + + // If the task closed unexpectedly, alert appropriately if (connectionState != SPSSH_STATE_IDLE) { connectionState = SPSSH_STATE_IDLE; lastError = [[NSString alloc] initWithString:NSLocalizedString(@"The SSH Tunnel has unexpectedly closed.", @"SSH tunnel unexpectedly closed")]; @@ -343,7 +348,7 @@ lastError = [[NSString alloc] initWithString:NSLocalizedString(@"The SSH Tunnel was closed 'by the remote host'. This may indicate a networking issue or a network timeout.", @"SSH tunnel was closed by remote host message")]; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; } - if ([message rangeOfString:@"Permission denied (" ].location != NSNotFound) { + if ([message rangeOfString:@"Permission denied (" ].location != NSNotFound || [message rangeOfString:@"No more authentication methods to try" ].location != NSNotFound) { connectionState = SPSSH_STATE_IDLE; [task terminate]; if (lastError) [lastError release]; @@ -394,9 +399,15 @@ */ - (BOOL) getResponseForQuestion:(NSString *)theQuestion { + NSSize questionTextSize; + NSRect windowFrameRect; - // Ask how to proceed + // Ask how to proceed, sizing the window appropriately to fit the question [sshQuestionText setStringValue:theQuestion]; + questionTextSize = [[sshQuestionText cell] cellSizeForBounds:NSMakeRect(0, 0, [sshQuestionText bounds].size.width, 500)]; + windowFrameRect = [sshQuestionDialog frame]; + windowFrameRect.size.height = ((questionTextSize.height < 100)?100:questionTextSize.height) + 90; + [sshQuestionDialog setFrame:windowFrameRect display:NO]; [NSApp beginSheet:sshQuestionDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; int sshQueryResponseCode = [NSApp runModalForWindow:sshQuestionDialog]; [NSApp endSheet:sshQuestionDialog]; @@ -415,6 +426,44 @@ } /* + * Method to allow an SSH tunnel to request a password. This is used by the program set by the + * SSH_ASKPASS environment setting to request passphrases for SSH keys. + */ +- (NSString *) getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash +{ + if (![theHash isEqualToString:tunnelConnectionVerifyHash]) return nil; + + NSSize queryTextSize; + NSRect windowFrameRect; + NSString *thePassword; + + // Request the password, sizing the window appropriately to fit the query + [sshPasswordText setStringValue:theQuery]; + queryTextSize = [[sshPasswordText cell] cellSizeForBounds:NSMakeRect(0, 0, [sshPasswordText bounds].size.width, 500)]; + windowFrameRect = [sshPasswordDialog frame]; + windowFrameRect.size.height = ((queryTextSize.height < 40)?40:queryTextSize.height) + 143; + [sshPasswordDialog setFrame:windowFrameRect display:NO]; + [NSApp beginSheet:sshPasswordDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; + int sshQueryResponseCode = [NSApp runModalForWindow:sshPasswordDialog]; + [NSApp endSheet:sshPasswordDialog]; + [sshPasswordDialog orderOut:nil]; + + switch (sshQueryResponseCode) { + + // OK + case 1: + thePassword = [NSString stringWithString:[sshPasswordField stringValue]]; + [sshPasswordField setStringValue:@""]; + [[delegate undoManager] removeAllActionsWithTarget:sshPasswordField]; + return thePassword; + + // Cancel + default: + return nil; + } +} + +/* * Ends an existing modal session */ - (IBAction) closeSheet:(id)sender diff --git a/Source/TunnelPassphraseRequester.m b/Source/TunnelPassphraseRequester.m index 4391e3e7..360af00d 100644 --- a/Source/TunnelPassphraseRequester.m +++ b/Source/TunnelPassphraseRequester.m @@ -31,6 +31,7 @@ int main(int argc, const char *argv[]) NSString *argument = nil; SPSSHTunnel *sequelProTunnel; NSString *connectionName = [environment objectForKey:@"SP_CONNECTION_NAME"]; + NSString *verificationHash = [environment objectForKey:@"SP_CONNECTION_VERIFY_HASH"]; if (![environment objectForKey:@"SP_PASSWORD_METHOD"]) { [pool release]; @@ -38,7 +39,7 @@ int main(int argc, const char *argv[]) } if (argc > 1) { - argument = [[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding]; + argument = [[[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding] autorelease]; } // Check if we're being asked a question and respond if so @@ -59,57 +60,87 @@ int main(int argc, const char *argv[]) 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) { - KeyChain *keychain; - NSString *keychainName = [environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"]; - NSString *keychainAccount = [environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; - - if (!keychainName || !keychainAccount) { - NSLog(@"SSH Tunnel: keychain authentication specified but insufficient internal details supplied"); + // Check whether we're being asked for a standard SSH password - if so, use the app-entered value. + if (argument && [[argument lowercaseString] rangeOfString:@"password:"].location != NSNotFound ) { + + // 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) { + KeyChain *keychain; + NSString *keychainName = [environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"]; + NSString *keychainAccount = [environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; + + if (!keychainName || !keychainAccount) { + NSLog(@"SSH Tunnel: keychain authentication specified but insufficient internal details supplied"); + [pool release]; + return 1; + } + + keychain = [[KeyChain alloc] init]; + if (![keychain passwordExistsForName:keychainName account:keychainAccount]) { + NSLog(@"SSH Tunnel: specified keychain password not found"); + [pool release]; + return 1; + } + + printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); [pool release]; - return 1; + return 0; } - keychain = [[KeyChain alloc] init]; - if (![keychain passwordExistsForName:keychainName account:keychainAccount]) { - NSLog(@"SSH Tunnel: specified keychain password not found"); + // 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) { + NSString *password; + + if (!connectionName || !verificationHash) { + NSLog(@"SSH Tunnel: internal authentication specified but insufficient details supplied"); + [pool release]; + return 1; + } + + sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; + if (!sequelProTunnel) { + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro for internal authentication"); + [pool release]; + return 1; + } + + password = [sequelProTunnel getPasswordWithVerificationHash:verificationHash]; + if (!password) { + NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); + [pool release]; + return 1; + } + + printf("%s\n", [password UTF8String]); [pool release]; - return 1; + return 0; } - - printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); - [pool release]; - return 0; } - // 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) { - NSString *password; - NSString *verificationHash = [environment objectForKey:@"SP_CONNECTION_VERIFY_HASH"]; - - if (!connectionName || !verificationHash) { - NSLog(@"SSH Tunnel: internal authentication specified but insufficient details supplied"); + // Check whether we're being asked for a SSH key passphrase, forward requests to the GUI + if (argument && [[argument lowercaseString] rangeOfString:@"enter passphrase for"].location != NSNotFound ) { + NSString *passphrase; + + if (!verificationHash) { + NSLog(@"SSH Tunnel: key passphrase authentication required but insufficient details supplied to connect to GUI"); [pool release]; return 1; } sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; if (!sequelProTunnel) { - NSLog(@"SSH Tunnel: unable to connect to Sequel Pro for internal authentication"); + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); [pool release]; return 1; } - - password = [sequelProTunnel getPasswordWithVerificationHash:verificationHash]; - if (!password) { - NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); + passphrase = [sequelProTunnel getPasswordForQuery:argument verificationHash:verificationHash]; + if (!passphrase) { [pool release]; return 1; } - printf("%s\n", [password UTF8String]); + printf("%s\n", [passphrase UTF8String]); [pool release]; return 0; } |