Skip to content

Commit 6b00f5f

Browse files
committed
Next step in the #2979 experiment
1 parent fa485aa commit 6b00f5f

File tree

4 files changed

+54
-27
lines changed

4 files changed

+54
-27
lines changed

Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -730,22 +730,27 @@ - (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMaster
730730

731731
- (void)_mysqlConnection:(MYSQL *)connection wantsPassword:(void (^)(const char *passwd))inBlock withPlugin:(const char *)pluginName
732732
{
733-
// If a password was supplied, use it; otherwise ask the delegate if appropriate.
734-
//
735-
// Note that password has no charset in mysql: If a user password is set to 'ü' on a latin1 connection
736-
// and you later try to connect on an UTF-8 terminal (or vice versa) it will fail. The MySQL (5.5) manual wrongly states that
737-
// MYSQL_SET_CHARSET_NAME has influence over that, but it does not and could not, since the password is hashed by the client
738-
// before transmitting it to the server and the (5.5) client has no charset support, effectively treating password as
739-
// a NUL-terminated byte array.
740-
// There is one exception, though: The "mysql_clear_password" auth plugin sends the password in plaintext and the server side
741-
// MAY choose to do a charset conversion as appropriate before handing it to whatever backend is used.
742-
// Since we don't know which auth plugin server and client will agree upon, we'll do as the manual says...
743733
NSString *passwd = nil;
744-
734+
735+
// If a password was supplied, use it; otherwise ask the delegate if appropriate.
745736
if (password) {
746737
passwd = password;
747-
} else if ([delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) {
748-
passwd = [delegate keychainPasswordForConnection:self]; //TODO pass pluginName to client
738+
}
739+
else if ([delegate respondsToSelector:@selector(keychainPasswordForConnection:authPlugin:)]) {
740+
// It's not clear what charset the plugin name is in:
741+
// In the 5.5 libmysqlclient:
742+
// * For the compiled-in plugins this will simply be the byte sequence as it was in the source code
743+
// * The server requests a plugin in the first packet it sends and gives its own charset (mysql->server_language)
744+
// * However client for the most part ignores the plugin name
745+
// * and it completely ignores the server_language
746+
// * When the client sends its reply (in send_client_reply_packet()) it will send the plugin name together with
747+
// the desired charset+collation (but doesn't apply any charset conversion logic to the values)
748+
// In the JDBC client it works like this:
749+
// * The plugin name in the first packet from the server will always be interpreted as "ASCII"
750+
// * The plugin name in the client response will be encoded in the client's initial charset
751+
// TODO We will just use latin1 for now, as it is the safest fallback
752+
NSString *plugin = [NSString stringWithCString:pluginName encoding:NSISOLatin1StringEncoding];
753+
passwd = [delegate keychainPasswordForConnection:self authPlugin:plugin];
749754
}
750755

751756
// shortcut for empty/nil password
@@ -754,7 +759,20 @@ - (void)_mysqlConnection:(MYSQL *)connection wantsPassword:(void (^)(const char
754759
return;
755760
}
756761

757-
NSStringEncoding connectEncodingNS = [SPMySQLConnection stringEncodingForMySQLCharset:connection->options.charset_name];
762+
// Note (libmysqlclient 5.5):
763+
// mysql_character_set_name() is only initialized after mysql has read the first packet from the server.
764+
// Before that it will always be latin1, regardless of what was set with mysql_options().
765+
// That does not mean, that client and server have agreed on a charset already, though!
766+
NSStringEncoding connectEncodingNS = [SPMySQLConnection stringEncodingForMySQLCharset:mysql_character_set_name(connection)];
767+
768+
// Note that password has no charset in mysql: If a user password is set to 'ü' on a latin1 connection
769+
// and you later try to connect on an UTF-8 terminal (or vice versa) it will fail. The MySQL (5.5) manual wrongly states that
770+
// MYSQL_SET_CHARSET_NAME has influence over that, but it does not and could not, since the password is hashed by the client
771+
// before transmitting it to the server and the (5.5) client has very limited charset support,
772+
// effectively treating password as a NUL-terminated byte array.
773+
// There is one exception, though: The "mysql_clear_password" auth plugin sends the password in plaintext and the server side
774+
// MAY choose to do a charset conversion as appropriate before handing it to whatever backend is used.
775+
// Since we don't know which auth plugin server and client will agree upon, we'll do as the manual says...
758776
NSInteger cLength = [passwd lengthOfBytesUsingEncoding:connectEncodingNS];
759777

760778
if(!cLength || cLength == NSIntegerMax) {
@@ -1218,7 +1236,7 @@ + (void)_removeThreadVariables:(NSNotification *)aNotification
12181236

12191237
@end
12201238

1221-
void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(const char *passwd))
1239+
void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(const char *))
12221240
{
12231241
assert(mysql && mysql->sp_context);
12241242
[(SPMySQLConnection *)mysql->sp_context _mysqlConnection:mysql wantsPassword:with_password withPlugin:plugin];
@@ -1234,7 +1252,7 @@ void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(co
12341252
errno_t LegacyMemsetS(void *s, rsize_t smax, int c, rsize_t n)
12351253
{
12361254
volatile unsigned char * addr = (volatile unsigned char *)s;
1237-
while(n--) *addr++ = c;
1255+
while(n--) *addr++ = (unsigned char)c;
12381256

12391257
return 0;
12401258
}

Frameworks/SPMySQLFramework/Source/SPMySQLConnectionDelegate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@
6969
* can be used to look it up and supplied on demand.
7070
*
7171
* @param connection The connection instance to supply the password for
72+
* @param pluginName The auth plugin libmysqlclients wants to use the password with
7273
*/
73-
- (NSString *)keychainPasswordForConnection:(id)connection;
74+
- (NSString *)keychainPasswordForConnection:(id)connection authPlugin:(NSString *)pluginName;
7475

7576
/**
7677
* Notifies the delegate that no underlying connection is available,

Source/SPDatabaseDocument.m

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ - (void)_loadTableTask;
119119

120120
- (void) closeAndDisconnect;
121121

122-
- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection;
122+
- (NSString *)keychainPassword;
123123
- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection;
124124

125125
@end
@@ -4686,7 +4686,7 @@ - (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn
46864686
[connection setObject:[self database] forKey:@"database"];
46874687

46884688
if (includePasswords) {
4689-
NSString *pw = [self keychainPasswordForConnection:nil];
4689+
NSString *pw = [self keychainPassword];
46904690
if (!pw) pw = [connectionController password];
46914691
if (pw) [connection setObject:pw forKey:@"password"];
46924692

@@ -4885,9 +4885,8 @@ - (BOOL)setState:(NSDictionary *)stateDetails fromFile:(BOOL)spfBased
48854885
if ([connection objectForKey:@"password"])
48864886
[connectionController setPassword:[connection objectForKey:@"password"]];
48874887
else {
4888-
NSString *pw = [self keychainPasswordForConnection:nil];
4889-
if (pw)
4890-
[connectionController setPassword:pw];
4888+
NSString *pw = [self keychainPassword];
4889+
if (pw) [connectionController setPassword:pw];
48914890
}
48924891

48934892
// Set the socket details, whether or not the type is a socket
@@ -7150,15 +7149,22 @@ - (void)queryGaveError:(NSString *)error connection:(id)connection
71507149
/**
71517150
* Invoked when the current connection needs a password from the Keychain.
71527151
*/
7153-
- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection
7152+
- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection authPlugin:(NSString *)pluginName
71547153
{
7154+
//TODO check plugin name to see whether we want to fetch it from keychain
7155+
return [self keychainPassword];
7156+
}
7157+
7158+
- (NSString *)keychainPassword
7159+
{
7160+
NSString *kcItemName = [connectionController connectionKeychainItemName];
71557161
// If no keychain item is available, return an empty password
7156-
if (![connectionController connectionKeychainItemName]) return nil;
7162+
if (!kcItemName) return nil;
71577163

71587164
// Otherwise, pull the password from the keychain using the details from this connection
71597165
SPKeychain *keychain = [[SPKeychain alloc] init];
71607166

7161-
NSString *password = [keychain getPasswordForName:[connectionController connectionKeychainItemName] account:[connectionController connectionKeychainItemAccount]];
7167+
NSString *password = [keychain getPasswordForName:kcItemName account:[connectionController connectionKeychainItemAccount]];
71627168

71637169
[keychain release];
71647170

Source/SPDatabaseStructure.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ - (void)queryDbStructureWithUserInfo:(NSDictionary *)userInfo
258258
goto cleanup_thread_and_pool;
259259
}
260260

261+
#if 0
261262
// For future usage - currently unused
262263
// If the affected item name and type - for example, table type and table name - were supplied, extract it.
263264
NSString *affectedItem = nil;
@@ -269,6 +270,7 @@ - (void)queryDbStructureWithUserInfo:(NSDictionary *)userInfo
269270
else
270271
affectedItem = nil;
271272
}
273+
#endif
272274

273275
// Delete all stored data for the database to be updated, leaving the structure key
274276
[queriedStructure removeObjectForKey:db_id];
@@ -444,9 +446,9 @@ - (NSArray *)allStructureKeys
444446
/**
445447
* Forward keychain password requests to the database object.
446448
*/
447-
- (NSString *)keychainPasswordForConnection:(id)connection
449+
- (NSString *)keychainPasswordForConnection:(id)connection authPlugin:(NSString *)pluginName
448450
{
449-
return [delegate keychainPasswordForConnection:connection];
451+
return [delegate keychainPasswordForConnection:connection authPlugin:pluginName];
450452
}
451453

452454
#pragma mark -

0 commit comments

Comments
 (0)