@@ -114,6 +114,9 @@ - (void)_startEditingConnection;
114
114
115
115
- (void)_documentWillClose:(NSNotification *)notification;
116
116
117
+ - (void)_beginRequestPasswordForInsecurePlugin:(NSString *)pluginName;
118
+ - (void)_insecurePasswordAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
119
+
117
120
static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key);
118
121
#endif
119
122
@@ -163,6 +166,8 @@ @implementation SPConnectionController
163
166
@synthesize sshKeyLocation;
164
167
@synthesize sshPort;
165
168
@synthesize useCompression;
169
+ @synthesize agreedInsecurePlugin = agreedInsecurePlugin;
170
+ @synthesize insecureOverridePassword = insecureOverridePassword;
166
171
167
172
#ifdef SP_CODA
168
173
@synthesize dbDocument;
@@ -199,6 +204,133 @@ - (NSString *)keychainPasswordForSSH
199
204
return kcSSHPassword;
200
205
}
201
206
207
+ /**
208
+ * This method is responsible for handing the user password to SPMySQL when it needs it.
209
+ * It will receive the name of the auth plugin mysql plans to use and with
210
+ * that it has to decide whether to fetch the password from keychain, or present
211
+ * additional prompts to the user (e.g. in case the auth plugin is insecure).
212
+ *
213
+ * Note: The 5.5 libmysqlclient on the first attempt ignores the requested plugin
214
+ * and tries to guess one instead. Only if that fails it will try to use
215
+ * the server's suggested one. Thus this method may be invoked multiple times
216
+ * during a single connection attempt.
217
+ *
218
+ * @return The actual user password to give to the mysql auth plugin.
219
+ * This may be empty (no password).
220
+ * This may be nil, which will be interpreted as "cancelled by user"
221
+ *
222
+ * This MUST be called on the UI thread!
223
+ */
224
+ - (NSString *)actualPasswordForAuthPlugin:(NSString *)pluginName
225
+ {
226
+ NSArray *securePluginNames = @[
227
+ @"mysql_native_password", //default on 4.1+
228
+ //@"sha256_password", // not supported by the 5.5 client, only secure when used with TLS server cert checks!
229
+ // over SSL: password is transmitted in plaintext
230
+ // over plaintext: the mysql client uses assymetric crypto to encrypt the password and
231
+ // send the encrypted plaintext to the server (this requires the OpenSSL
232
+ // libs, so doesn't work with our client builds)
233
+ ];
234
+ //INSECURE:
235
+ // mysql_clear_password; Plaintext password
236
+ // mysql_old_password; Deprecated, used with pre-4.1 servers. No longer supported in 5.7.5+ clients
237
+ //UNSUPPORTED:
238
+ // authentication_windows_client; Kerberos/NTLM auth. Not supported on UNIX by libmysqlclient
239
+ // authentication_ldap_sasl_client; MySQL Enterprise
240
+ // auth_test_plugin; Developer example only
241
+
242
+ // TODO incorporate SSL/TLS state in decision (cleartext over trusted SSL should be fine)
243
+ // This is not possible right now, because someone with access to the Mac could just swap the SSL CA cert
244
+ // and server ip to something he controls. So if Sequel Pro would just check that SSL was in use, it could
245
+ // still be attacked to easily give the password away.
246
+ // Instead we have to link all critical connection parameters (host+port+ssh-tunnel+ssl server or ca cert hash)
247
+ // to the password in keychain and make sure none of them changed before giving the password to libmysqlclient.
248
+
249
+ if(![securePluginNames containsObject:pluginName]) {
250
+ if(![pluginName isEqualToString:agreedInsecurePlugin]) {
251
+ // since the user will probably take longer to answer the dialog than the connection timeout is
252
+ // (and because the UI sheet is async anyway),
253
+ // we will do a little trick and disconnect mysql right now and retry, once we actually have the password
254
+ [self performSelector:@selector(_beginRequestPasswordForInsecurePlugin:) withObject:pluginName afterDelay:0.0];
255
+ cancellingConnection = YES;
256
+ return nil;
257
+ }
258
+ }
259
+
260
+ NSString *pass;
261
+ // override password always wins
262
+ if ((pass = [self insecureOverridePassword])) {
263
+ ;
264
+ }
265
+ // Only set the password if there is no Keychain item set and the connection is not being tested.
266
+ // The connection will otherwise ask the delegate for passwords in the Keychain.
267
+ else if ((!connectionKeychainItemName || isTestingConnection) && (pass = [self password])) {
268
+ ;
269
+ }
270
+ else {
271
+ pass = [self keychainPassword];
272
+ }
273
+
274
+ return (pass ? pass : @""); //returning nil would mean cancelled
275
+ }
276
+
277
+ - (void)_beginRequestPasswordForInsecurePlugin:(NSString *)pluginName
278
+ {
279
+ // if the user presses "Disconnect" in the dialog OR
280
+ // if this was only a test connection OR
281
+ // if the connection failed anyway
282
+ // -> agreedInsecurePlugin will be cleared again via -_restoreConnectionInterface
283
+ [self setAgreedInsecurePlugin:pluginName];
284
+
285
+ //show modal warning dialog
286
+ NSAlert *alert = [[NSAlert alloc] init]; //released in alert callback
287
+ [alert setAlertStyle:NSAlertStyleCritical];
288
+ [alert setMessageText:NSLocalizedString(@"Transmit password insecurely?",@"Connection dialog : password security error alert : title")];
289
+ [alert setInformativeText:NSLocalizedString(@"The MySQL server has requested the password to be transmitted in an insecure manner. This could be indicative of an attack on the server or your network connection!\n\nIf you still want to continue anyway, manually reenter your connection password.",@"Connection dialog : password security error alert : detail message")];
290
+ [alert setAccessoryView:requestPasswordAccessoryView];
291
+
292
+ [requestPasswordPluginNameField setStringValue:pluginName];
293
+
294
+ NSButton *connectAnywayButton = [alert addButtonWithTitle:NSLocalizedString(@"Connect Anyway",@"Connection dialog : password security error alert : confirm button")];
295
+ [connectAnywayButton setTag:NSAlertDefaultReturn];
296
+
297
+ NSButton *disconnectButton = [alert addButtonWithTitle:NSLocalizedString(@"Disconnect",@"Connection dialog : password security error alert : cancel button")];
298
+ [disconnectButton setTag:NSAlertAlternateReturn];
299
+
300
+ [alert beginSheetModalForWindow:[dbDocument parentWindow]
301
+ modalDelegate:self
302
+ didEndSelector:@selector(_insecurePasswordAlertDidEnd:returnCode:contextInfo:)
303
+ contextInfo:NULL];
304
+ }
305
+
306
+ - (void)_insecurePasswordAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
307
+ {
308
+ NSString *pass = nil;
309
+ if(returnCode == NSAlertDefaultReturn) {
310
+ pass = [NSString stringWithString:[requestPasswordPasswordField stringValue]];
311
+ }
312
+ [requestPasswordPasswordField setStringValue:@""];
313
+
314
+ [alert autorelease];
315
+
316
+ cancellingConnection = NO;
317
+
318
+ //cancelled by user?
319
+ if(!pass) {
320
+ [self _restoreConnectionInterface];
321
+ return;
322
+ }
323
+
324
+ // if the manually given password matches the keychain password there is no need to keep it in memory,
325
+ // otherwise we use the override password ivar in order to not affect the normal logic
326
+ if (!connectionKeychainItemName || ![pass isEqualToString:[self keychainPassword]]) {
327
+ [self setInsecureOverridePassword:pass];
328
+ }
329
+
330
+ [[pass retain] release]; //free it asap
331
+ [self initiateMySQLConnection]; //reconnect
332
+ }
333
+
202
334
#pragma mark -
203
335
#pragma mark Connection processes
204
336
@@ -765,6 +897,8 @@ - (void)updateFavoriteSelection:(id)sender
765
897
if (connectionKeychainItemAccount) SPClear(connectionKeychainItemAccount);
766
898
if (connectionSSHKeychainItemName) SPClear(connectionSSHKeychainItemName);
767
899
if (connectionSSHKeychainItemAccount) SPClear(connectionSSHKeychainItemAccount);
900
+ [self setAgreedInsecurePlugin:nil];
901
+ [self setInsecureOverridePassword:nil];
768
902
769
903
SPTreeNode *node = [self selectedFavoriteNode];
770
904
if ([node isGroup]) node = nil;
@@ -1764,7 +1898,11 @@ - (void)_restoreConnectionInterface
1764
1898
// Reset the window title
1765
1899
[dbDocument updateWindowTitle:self];
1766
1900
[[dbDocument parentTabViewItem] setLabel:[dbDocument displayName]];
1767
-
1901
+
1902
+ //only store that if the connection attempt was successful
1903
+ [self setAgreedInsecurePlugin:nil];
1904
+ [self setInsecureOverridePassword:nil];
1905
+
1768
1906
// Stop the current tab's progress indicator
1769
1907
[dbDocument setIsProcessing:NO];
1770
1908
@@ -2083,12 +2221,6 @@ - (void)initiateMySQLConnectionInBackground
2083
2221
}
2084
2222
}
2085
2223
2086
- // Only set the password if there is no Keychain item set and the connection is not being tested.
2087
- // The connection will otherwise ask the delegate for passwords in the Keychain.
2088
- if ((!connectionKeychainItemName || isTestingConnection) && [self password]) {
2089
- [mySQLConnection setPassword:[self password]];
2090
- }
2091
-
2092
2224
// Enable SSL if set
2093
2225
if ([self useSSL]) {
2094
2226
[mySQLConnection setUseSSL:YES];
@@ -2130,6 +2262,11 @@ - (void)initiateMySQLConnectionInBackground
2130
2262
[mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]];
2131
2263
[mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]];
2132
2264
2265
+ // We can always enable the cleartext plugin (this does not yet mean it will actually be used),
2266
+ // since mysql will ask us for the password in -actualPasswordForAuthPlugin: at which point
2267
+ // we get to decide what to do with the actual plugin.
2268
+ [mySQLConnection setAllowCleartextPlugin:YES];
2269
+
2133
2270
// Connect
2134
2271
[mySQLConnection connect];
2135
2272
@@ -3646,6 +3783,8 @@ - (void)dealloc
3646
3783
if (connectionKeychainItemAccount) SPClear(connectionKeychainItemAccount);
3647
3784
if (connectionSSHKeychainItemName) SPClear(connectionSSHKeychainItemName);
3648
3785
if (connectionSSHKeychainItemAccount) SPClear(connectionSSHKeychainItemAccount);
3786
+ [self setAgreedInsecurePlugin:nil];
3787
+ [self setInsecureOverridePassword:nil];
3649
3788
3650
3789
#ifndef SP_CODA
3651
3790
if (currentFavorite) SPClear(currentFavorite);
0 commit comments