diff options
-rw-r--r-- | Interfaces/SSHQuestionDialog.xib | 497 | ||||
-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 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 4 |
11 files changed, 774 insertions, 110 deletions
diff --git a/Interfaces/SSHQuestionDialog.xib b/Interfaces/SSHQuestionDialog.xib new file mode 100644 index 00000000..bbb00fe3 --- /dev/null +++ b/Interfaces/SSHQuestionDialog.xib @@ -0,0 +1,497 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03"> + <data> + <int key="IBDocument.SystemTarget">1050</int> + <string key="IBDocument.SystemVersion">9G55</string> + <string key="IBDocument.InterfaceBuilderVersion">677</string> + <string key="IBDocument.AppKitVersion">949.43</string> + <string key="IBDocument.HIToolboxVersion">353.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="367"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1021"> + <string key="NSClassName">NSObject</string> + </object> + <object class="NSCustomObject" id="1014"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1050"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="513744381"> + <int key="NSWindowStyleMask">1</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{196, 301}, {536, 209}}</string> + <int key="NSWTFlags">603979776</int> + <string key="NSWindowTitle">SSH Tunnel Query</string> + <string key="NSWindowClass">NSWindow</string> + <nil key="NSViewClass"/> + <string key="NSWindowContentMaxSize">{3.40282e+38, 3.40282e+38}</string> + <object class="NSView" key="NSWindowView" id="414427165"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSTextField" id="958459073"> + <reference key="NSNextResponder" ref="414427165"/> + <int key="NSvFlags">274</int> + <string key="NSFrame">{{126, 46}, {393, 143}}</string> + <reference key="NSSuperview" ref="414427165"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="296218965"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">272891904</int> + <string key="NSContents"/> + <object class="NSFont" key="NSSupport"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">1.300000e+01</double> + <int key="NSfFlags">16</int> + </object> + <reference key="NSControlView" ref="958459073"/> + <object class="NSColor" key="NSBackgroundColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MC42NjY2NjY2OQA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlTextColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MAA</bytes> + </object> + </object> + </object> + </object> + <object class="NSButton" id="819605912"> + <reference key="NSNextResponder" ref="414427165"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{426, 12}, {96, 32}}</string> + <reference key="NSSuperview" ref="414427165"/> + <int key="NSTag">1</int> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="915366670"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Yes</string> + <object class="NSFont" key="NSSupport" id="313221240"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">1.300000e+01</double> + <int key="NSfFlags">1044</int> + </object> + <reference key="NSControlView" ref="819605912"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string type="base64-UTF8" key="NSKeyEquivalent">DQ</string> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSImageView" id="346757277"> + <reference key="NSNextResponder" ref="414427165"/> + <int key="NSvFlags">256</int> + <object class="NSMutableSet" key="NSDragTypes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="set.sortedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>Apple PDF pasteboard type</string> + <string>Apple PICT pasteboard type</string> + <string>Apple PNG pasteboard type</string> + <string>NSFilenamesPboardType</string> + <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> + <string>NeXT TIFF v4.0 pasteboard type</string> + </object> + </object> + <string key="NSFrame">{{20, 115}, {75, 74}}</string> + <reference key="NSSuperview" ref="414427165"/> + <bool key="NSEnabled">YES</bool> + <object class="NSImageCell" key="NSCell" id="490596046"> + <int key="NSCellFlags">130560</int> + <int key="NSCellFlags2">33554432</int> + <object class="NSCustomResource" key="NSContents"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">appicon</string> + </object> + <int key="NSAlign">0</int> + <int key="NSScale">0</int> + <int key="NSStyle">0</int> + <bool key="NSAnimates">YES</bool> + </object> + <bool key="NSEditable">YES</bool> + </object> + <object class="NSButton" id="472545742"> + <reference key="NSNextResponder" ref="414427165"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{330, 12}, {96, 32}}</string> + <reference key="NSSuperview" ref="414427165"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="382904691"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">No</string> + <reference key="NSSupport" ref="313221240"/> + <reference key="NSControlView" ref="472545742"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSImageView" id="205963577"> + <reference key="NSNextResponder" ref="414427165"/> + <int key="NSvFlags">256</int> + <object class="NSMutableSet" key="NSDragTypes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="set.sortedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>Apple PDF pasteboard type</string> + <string>Apple PICT pasteboard type</string> + <string>Apple PNG pasteboard type</string> + <string>NSFilenamesPboardType</string> + <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> + <string>NeXT TIFF v4.0 pasteboard type</string> + </object> + </object> + <string key="NSFrame">{{68, 113}, {32, 32}}</string> + <reference key="NSSuperview" ref="414427165"/> + <bool key="NSEnabled">YES</bool> + <object class="NSImageCell" key="NSCell" id="406303847"> + <int key="NSCellFlags">130560</int> + <int key="NSCellFlags2">33554432</int> + <object class="NSCustomResource" key="NSContents"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">toolbar-preferences-network</string> + </object> + <int key="NSAlign">0</int> + <int key="NSScale">0</int> + <int key="NSStyle">0</int> + <bool key="NSAnimates">YES</bool> + </object> + <bool key="NSEditable">YES</bool> + </object> + </object> + <string key="NSFrameSize">{536, 209}</string> + <reference key="NSSuperview"/> + </object> + <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string> + <string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">sshQuestionText</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="958459073"/> + </object> + <int key="connectionID">464</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">sshQuestionDialog</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="513744381"/> + </object> + <int key="connectionID">465</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">closeSheet:</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="819605912"/> + </object> + <int key="connectionID">466</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">closeSheet:</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="472545742"/> + </object> + <int key="connectionID">467</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1049"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1048"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1021"/> + <reference key="parent" ref="1049"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1014"/> + <reference key="parent" ref="1049"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1050"/> + <reference key="parent" ref="1049"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">367</int> + <reference key="object" ref="513744381"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="414427165"/> + </object> + <reference key="parent" ref="1049"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">368</int> + <reference key="object" ref="414427165"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="205963577"/> + <reference ref="958459073"/> + <reference ref="819605912"/> + <reference ref="472545742"/> + <reference ref="346757277"/> + </object> + <reference key="parent" ref="513744381"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">450</int> + <reference key="object" ref="958459073"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="296218965"/> + </object> + <reference key="parent" ref="414427165"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">451</int> + <reference key="object" ref="296218965"/> + <reference key="parent" ref="958459073"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">456</int> + <reference key="object" ref="205963577"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="406303847"/> + </object> + <reference key="parent" ref="414427165"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">457</int> + <reference key="object" ref="406303847"/> + <reference key="parent" ref="205963577"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">458</int> + <reference key="object" ref="819605912"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="915366670"/> + </object> + <reference key="parent" ref="414427165"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">459</int> + <reference key="object" ref="915366670"/> + <reference key="parent" ref="819605912"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">460</int> + <reference key="object" ref="472545742"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="382904691"/> + </object> + <reference key="parent" ref="414427165"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">461</int> + <reference key="object" ref="382904691"/> + <reference key="parent" ref="472545742"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">462</int> + <reference key="object" ref="346757277"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="490596046"/> + </object> + <reference key="parent" ref="414427165"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">463</int> + <reference key="object" ref="490596046"/> + <reference key="parent" ref="346757277"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>367.IBEditorWindowLastContentRect</string> + <string>367.IBWindowTemplateEditedContentRect</string> + <string>367.NSWindowTemplate.visibleAtLaunch</string> + <string>367.editorWindowContentRectSynchronizationRect</string> + <string>368.IBPluginDependency</string> + <string>450.IBPluginDependency</string> + <string>451.IBPluginDependency</string> + <string>458.IBPluginDependency</string> + <string>459.IBPluginDependency</string> + <string>460.IBPluginDependency</string> + <string>461.IBPluginDependency</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>{{187, 404}, {536, 209}}</string> + <string>{{187, 404}, {536, 209}}</string> + <boolean value="NO"/> + <string>{{11, 666}, {480, 270}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">467</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="NSMutableDictionary" key="outlets"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>sshQuestionDialog</string> + <string>sshQuestionText</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSWindow</string> + <string>NSTextField</string> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">Source/CMImageView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">Source/CMMCPConnection.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <string key="NS.key.0">closeSheet:</string> + <string key="NS.object.0">id</string> + </object> + <object class="NSMutableDictionary" key="outlets"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>sshQuestionDialog</string> + <string>sshQuestionText</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSWindow</string> + <string>NSTextField</string> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSWindow</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">Source/SPWindowAdditions.h</string> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../sequel-pro.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> 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) { diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index dfb7735e..a6ae8294 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ 58186D210F4CB38900851FE9 /* ConnectionErrorDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */; }; 5841423F0F97E11000A34B47 /* NoodleLineNumberView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5841423E0F97E11000A34B47 /* NoodleLineNumberView.m */; }; 584F5F8F0F50ACD800036517 /* table-view-small.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 584F5F8E0F50ACD800036517 /* table-view-small.tiff */; }; + 586F432B0FD74CFC00B428D7 /* SSHQuestionDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 586F432A0FD74CFC00B428D7 /* SSHQuestionDialog.xib */; }; 5885940F0F7AEE6000ED0E67 /* sparkle-public-key.pem in Resources */ = {isa = PBXBuildFile; fileRef = 5885940E0F7AEE6000ED0E67 /* sparkle-public-key.pem */; }; 58C56EF50F438E120035701E /* SPDataCellFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C56EF40F438E120035701E /* SPDataCellFormatter.m */; }; 58CB20ED0F79A75D005EA204 /* button_edit_mode_selected.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 58CB20EC0F79A75D005EA204 /* button_edit_mode_selected.tiff */; }; @@ -318,6 +319,7 @@ 5841423D0F97E11000A34B47 /* NoodleLineNumberView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NoodleLineNumberView.h; sourceTree = "<group>"; }; 5841423E0F97E11000A34B47 /* NoodleLineNumberView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NoodleLineNumberView.m; sourceTree = "<group>"; }; 584F5F8E0F50ACD800036517 /* table-view-small.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "table-view-small.tiff"; sourceTree = "<group>"; }; + 586F432A0FD74CFC00B428D7 /* SSHQuestionDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SSHQuestionDialog.xib; sourceTree = "<group>"; }; 588593F30F7AEC9500ED0E67 /* package-application.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "package-application.sh"; sourceTree = "<group>"; }; 5885940E0F7AEE6000ED0E67 /* sparkle-public-key.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "sparkle-public-key.pem"; sourceTree = "<group>"; }; 58C56EF30F438E120035701E /* SPDataCellFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDataCellFormatter.h; sourceTree = "<group>"; }; @@ -746,6 +748,7 @@ 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */, B57747D00F7A891A003B34F9 /* Preferences.xib */, B5E92F170F75B2D100012500 /* ExportDialog.xib */, + 586F432A0FD74CFC00B428D7 /* SSHQuestionDialog.xib */, ); path = Interfaces; sourceTree = "<group>"; @@ -1009,6 +1012,7 @@ 177E792E0FCB54EC00E9E122 /* database-small.png in Resources */, 177E792F0FCB54EC00E9E122 /* dummy-small.png in Resources */, 177E79300FCB54EC00E9E122 /* table-small-square.tiff in Resources */, + 586F432B0FD74CFC00B428D7 /* SSHQuestionDialog.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; |