Skip to content

Commit

Permalink
Next step in the #2979 experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
dmoagx committed Feb 18, 2018
1 parent fa485aa commit 6b00f5f
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 27 deletions.
50 changes: 34 additions & 16 deletions Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
Expand Up @@ -730,22 +730,27 @@ - (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMaster

- (void)_mysqlConnection:(MYSQL *)connection wantsPassword:(void (^)(const char *passwd))inBlock withPlugin:(const char *)pluginName
{
// If a password was supplied, use it; otherwise ask the delegate if appropriate.
//
// Note that password has no charset in mysql: If a user password is set to 'ü' on a latin1 connection
// 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
// MYSQL_SET_CHARSET_NAME has influence over that, but it does not and could not, since the password is hashed by the client
// before transmitting it to the server and the (5.5) client has no charset support, effectively treating password as
// a NUL-terminated byte array.
// There is one exception, though: The "mysql_clear_password" auth plugin sends the password in plaintext and the server side
// MAY choose to do a charset conversion as appropriate before handing it to whatever backend is used.
// Since we don't know which auth plugin server and client will agree upon, we'll do as the manual says...
NSString *passwd = nil;


// If a password was supplied, use it; otherwise ask the delegate if appropriate.
if (password) {
passwd = password;
} else if ([delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) {
passwd = [delegate keychainPasswordForConnection:self]; //TODO pass pluginName to client
}
else if ([delegate respondsToSelector:@selector(keychainPasswordForConnection:authPlugin:)]) {
// It's not clear what charset the plugin name is in:
// In the 5.5 libmysqlclient:
// * For the compiled-in plugins this will simply be the byte sequence as it was in the source code
// * The server requests a plugin in the first packet it sends and gives its own charset (mysql->server_language)
// * However client for the most part ignores the plugin name
// * and it completely ignores the server_language
// * When the client sends its reply (in send_client_reply_packet()) it will send the plugin name together with
// the desired charset+collation (but doesn't apply any charset conversion logic to the values)
// In the JDBC client it works like this:
// * The plugin name in the first packet from the server will always be interpreted as "ASCII"
// * The plugin name in the client response will be encoded in the client's initial charset
// TODO We will just use latin1 for now, as it is the safest fallback
NSString *plugin = [NSString stringWithCString:pluginName encoding:NSISOLatin1StringEncoding];
passwd = [delegate keychainPasswordForConnection:self authPlugin:plugin];
}

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

NSStringEncoding connectEncodingNS = [SPMySQLConnection stringEncodingForMySQLCharset:connection->options.charset_name];
// Note (libmysqlclient 5.5):
// mysql_character_set_name() is only initialized after mysql has read the first packet from the server.
// Before that it will always be latin1, regardless of what was set with mysql_options().
// That does not mean, that client and server have agreed on a charset already, though!
NSStringEncoding connectEncodingNS = [SPMySQLConnection stringEncodingForMySQLCharset:mysql_character_set_name(connection)];

// Note that password has no charset in mysql: If a user password is set to 'ü' on a latin1 connection
// 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
// MYSQL_SET_CHARSET_NAME has influence over that, but it does not and could not, since the password is hashed by the client
// before transmitting it to the server and the (5.5) client has very limited charset support,
// effectively treating password as a NUL-terminated byte array.
// There is one exception, though: The "mysql_clear_password" auth plugin sends the password in plaintext and the server side
// MAY choose to do a charset conversion as appropriate before handing it to whatever backend is used.
// Since we don't know which auth plugin server and client will agree upon, we'll do as the manual says...
NSInteger cLength = [passwd lengthOfBytesUsingEncoding:connectEncodingNS];

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

@end

void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(const char *passwd))
void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(const char *))
{
assert(mysql && mysql->sp_context);
[(SPMySQLConnection *)mysql->sp_context _mysqlConnection:mysql wantsPassword:with_password withPlugin:plugin];
Expand All @@ -1234,7 +1252,7 @@ void PasswordCallback(MYSQL *mysql, const char *plugin, void (^with_password)(co
errno_t LegacyMemsetS(void *s, rsize_t smax, int c, rsize_t n)
{
volatile unsigned char * addr = (volatile unsigned char *)s;
while(n--) *addr++ = c;
while(n--) *addr++ = (unsigned char)c;

return 0;
}
Expand Up @@ -69,8 +69,9 @@
* can be used to look it up and supplied on demand.
*
* @param connection The connection instance to supply the password for
* @param pluginName The auth plugin libmysqlclients wants to use the password with
*/
- (NSString *)keychainPasswordForConnection:(id)connection;
- (NSString *)keychainPasswordForConnection:(id)connection authPlugin:(NSString *)pluginName;

/**
* Notifies the delegate that no underlying connection is available,
Expand Down
22 changes: 14 additions & 8 deletions Source/SPDatabaseDocument.m
Expand Up @@ -119,7 +119,7 @@ - (void)_loadTableTask;

- (void) closeAndDisconnect;

- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection;
- (NSString *)keychainPassword;
- (NSString *)keychainPasswordForSSHConnection:(SPMySQLConnection *)connection;

@end
Expand Down Expand Up @@ -4686,7 +4686,7 @@ - (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn
[connection setObject:[self database] forKey:@"database"];

if (includePasswords) {
NSString *pw = [self keychainPasswordForConnection:nil];
NSString *pw = [self keychainPassword];
if (!pw) pw = [connectionController password];
if (pw) [connection setObject:pw forKey:@"password"];

Expand Down Expand Up @@ -4885,9 +4885,8 @@ - (BOOL)setState:(NSDictionary *)stateDetails fromFile:(BOOL)spfBased
if ([connection objectForKey:@"password"])
[connectionController setPassword:[connection objectForKey:@"password"]];
else {
NSString *pw = [self keychainPasswordForConnection:nil];
if (pw)
[connectionController setPassword:pw];
NSString *pw = [self keychainPassword];
if (pw) [connectionController setPassword:pw];
}

// Set the socket details, whether or not the type is a socket
Expand Down Expand Up @@ -7150,15 +7149,22 @@ - (void)queryGaveError:(NSString *)error connection:(id)connection
/**
* Invoked when the current connection needs a password from the Keychain.
*/
- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection
- (NSString *)keychainPasswordForConnection:(SPMySQLConnection *)connection authPlugin:(NSString *)pluginName
{
//TODO check plugin name to see whether we want to fetch it from keychain
return [self keychainPassword];
}

- (NSString *)keychainPassword
{
NSString *kcItemName = [connectionController connectionKeychainItemName];
// If no keychain item is available, return an empty password
if (![connectionController connectionKeychainItemName]) return nil;
if (!kcItemName) return nil;

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

NSString *password = [keychain getPasswordForName:[connectionController connectionKeychainItemName] account:[connectionController connectionKeychainItemAccount]];
NSString *password = [keychain getPasswordForName:kcItemName account:[connectionController connectionKeychainItemAccount]];

[keychain release];

Expand Down
6 changes: 4 additions & 2 deletions Source/SPDatabaseStructure.m
Expand Up @@ -258,6 +258,7 @@ - (void)queryDbStructureWithUserInfo:(NSDictionary *)userInfo
goto cleanup_thread_and_pool;
}

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

// Delete all stored data for the database to be updated, leaving the structure key
[queriedStructure removeObjectForKey:db_id];
Expand Down Expand Up @@ -444,9 +446,9 @@ - (NSArray *)allStructureKeys
/**
* Forward keychain password requests to the database object.
*/
- (NSString *)keychainPasswordForConnection:(id)connection
- (NSString *)keychainPasswordForConnection:(id)connection authPlugin:(NSString *)pluginName
{
return [delegate keychainPasswordForConnection:connection];
return [delegate keychainPasswordForConnection:connection authPlugin:pluginName];
}

#pragma mark -
Expand Down

0 comments on commit 6b00f5f

Please sign in to comment.