From 4e6c8e557f6dd132e159ff4841900d1b72331eea Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Fri, 5 Jun 2009 00:32:42 +0000 Subject: 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. --- Interfaces/SSHQuestionDialog.xib | 390 +++++++++++++++++++++++++++++++++++-- Source/SPSSHTunnel.h | 4 + Source/SPSSHTunnel.m | 61 +++++- Source/TunnelPassphraseRequester.m | 95 ++++++--- 4 files changed, 494 insertions(+), 56 deletions(-) diff --git a/Interfaces/SSHQuestionDialog.xib b/Interfaces/SSHQuestionDialog.xib index bbb00fe3..3b18a91c 100644 --- a/Interfaces/SSHQuestionDialog.xib +++ b/Interfaces/SSHQuestionDialog.xib @@ -8,7 +8,8 @@ 353.00 YES - + + YES @@ -38,7 +39,7 @@ 1 2 - {{196, 301}, {536, 209}} + {{196, 301}, {620, 209}} 603979776 SSH Tunnel Query NSWindow @@ -52,20 +53,20 @@ 274 - {{126, 46}, {393, 143}} + {{126, 60}, {477, 129}} YES 67239424 272891904 - + LucidaGrande 1.300000e+01 16 - + 6 System controlColor @@ -74,11 +75,11 @@ MC42NjY2NjY2OQA - + 6 System controlTextColor - + 3 MAA @@ -88,7 +89,7 @@ 289 - {{426, 12}, {96, 32}} + {{510, 12}, {96, 32}} 1 YES @@ -112,7 +113,7 @@ - 256 + 268 YES @@ -131,7 +132,7 @@ 130560 33554432 - + NSImage appicon @@ -145,7 +146,7 @@ 289 - {{330, 12}, {96, 32}} + {{414, 12}, {96, 32}} YES @@ -164,7 +165,7 @@ - 256 + 268 YES @@ -195,12 +196,185 @@ YES - {536, 209} + {620, 209} {{0, 0}, {1920, 1178}} {3.40282e+38, 3.40282e+38} + + 1 + 2 + {{196, 301}, {620, 209}} + 603979776 + SSH Tunnel Password Query + NSWindow + + {3.40282e+38, 3.40282e+38} + + + 256 + + YES + + + 274 + {{126, 113}, {477, 76}} + + YES + + 67239424 + 272891904 + + + + + + + + + + 289 + {{510, 12}, {96, 32}} + + 1 + YES + + 67239424 + 134217728 + OK + + + -2038284033 + 129 + + DQ + 200 + 25 + + + + + 268 + + YES + + YES + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple PNG pasteboard type + NSFilenamesPboardType + NeXT Encapsulated PostScript v1.2 pasteboard type + NeXT TIFF v4.0 pasteboard type + + + {{20, 115}, {75, 74}} + + YES + + 130560 + 33554432 + + 0 + 0 + 0 + YES + + YES + + + + 289 + {{414, 12}, {96, 32}} + + YES + + 67239424 + 134217728 + Cancel + + + -2038284033 + 129 + + + 200 + 25 + + + + + 268 + + YES + + YES + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple PNG pasteboard type + NSFilenamesPboardType + NeXT Encapsulated PostScript v1.2 pasteboard type + NeXT TIFF v4.0 pasteboard type + + + {{68, 113}, {32, 32}} + + YES + + 130560 + 33554432 + + NSImage + toolbar-preferences-network + + 0 + 0 + 0 + YES + + YES + + + + 294 + {{129, 70}, {261, 22}} + + YES + + 343014976 + 272630784 + + + + YES + + 6 + System + textBackgroundColor + + 3 + MQA + + + + 6 + System + textColor + + + + YES + NSAllRomanInputSourcesLocaleIdentifier + + + + + {620, 209} + + + {{0, 0}, {1440, 878}} + {3.40282e+38, 3.40282e+38} + @@ -237,6 +411,46 @@ 467 + + + closeSheet: + + + + 480 + + + + closeSheet: + + + + 481 + + + + sshPasswordDialog + + + + 482 + + + + sshPasswordText + + + + 483 + + + + sshPasswordField + + + + 488 + @@ -283,9 +497,9 @@ YES + - @@ -359,6 +573,113 @@ + + 468 + + + YES + + + + + + 469 + + + YES + + + + + + + + + + + 470 + + + YES + + + + + + 471 + + + YES + + + + + + 472 + + + YES + + + + + + 473 + + + YES + + + + + + 474 + + + YES + + + + + + 475 + + + + + 476 + + + + + 477 + + + + + 478 + + + + + 479 + + + + + 484 + + + YES + + + + + + 485 + + + @@ -379,15 +700,28 @@ 459.IBPluginDependency 460.IBPluginDependency 461.IBPluginDependency + 468.IBEditorWindowLastContentRect + 468.IBWindowTemplateEditedContentRect + 468.NSWindowTemplate.visibleAtLaunch + 468.editorWindowContentRectSynchronizationRect + 469.IBPluginDependency + 470.IBPluginDependency + 471.IBPluginDependency + 473.IBPluginDependency + 476.IBPluginDependency + 478.IBPluginDependency + 479.IBPluginDependency + 484.IBPluginDependency + 485.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilderKit com.apple.InterfaceBuilderKit - {{187, 404}, {536, 209}} - {{187, 404}, {536, 209}} - + {{187, 404}, {620, 209}} + {{187, 404}, {620, 209}} + {{11, 666}, {480, 270}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -396,6 +730,19 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + {{187, 404}, {620, 209}} + {{187, 404}, {620, 209}} + + {{11, 666}, {480, 270}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -418,13 +765,14 @@ - 467 + 488 YES NSApplication + NSResponder YES @@ -467,12 +815,18 @@ YES YES + sshPasswordDialog + sshPasswordField + sshPasswordText sshQuestionDialog sshQuestionText YES NSWindow + NSSecureTextField + NSTextField + NSWindow NSTextField 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]; @@ -414,6 +425,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 */ 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; } -- cgit v1.2.3