This patch changes the way libmysqlclient receives the connection password. Usually it will get the password by trying in order: 1) The passwd that is passed as a parameter to mysql_real_connect() 2) The password that was set on MYSQL->options.password 3) The contents of the environment variable MYSQL_PWD (compile time setting) 4) An empty string If a connection could be made (not yet authenticathed) the password will be stored in MYSQL->passwd for the whole lifetime of the struct. We don't want that for two reasons: 1) That way the password stays in plaintext memory for possibly a long time (and may even get swapped to disk) 2) MySQL uses plugins for auth (negotiated with the server) and some of them may transmit the password in plaintext over an unsecure connection. Since we have no control over that we would have to decide beforehand if that COULD happen and flat out always deny or allow Keychain access (since e.g. the AVAILABILITY of the cleartext plugin can be controlled by an envvar). So with this patch we change the flow of information: Now mysql doesn't receive the password up front, but instead it has to ask the user (ie. SPMySQL) to get the password precisely then when it needs it and mysql will also tell us which auth plugin it negotiated with the server, so we can decide on a per situation basis whether to request manual input or fetch it from Keychain. To apply: cd mysql-source-root patch -p1 < this-file (patch created with `diff -Naur`) diff -Naur mysql-5.5.59-p002/include/mysql.h mysql-5.5.59-p003/include/mysql.h --- mysql-5.5.59-p002/include/mysql.h 2017-11-27 13:03:17.000000000 +0100 +++ mysql-5.5.59-p003/include/mysql.h 2018-02-16 00:23:19.000000000 +0100 @@ -288,6 +288,18 @@ /* needed for embedded server - no net buffer to store the 'info' */ char *info_buffer; void *extension; + + /* SPMySQL patch: + * Set this to a callback function that will be invoked when mysql wants to do authentication. + * @param mysql The MYSQL struct + * @param plugin The name of the auth plugin that will be used (usually either + * "mysql_native_password", "mysql_old_password" or "mysql_clear_password") + * @param with_password A block function you must invoke, during which mysql can use the password you provide via the passwd parameter. + * After the block you should immediately clear the password from memory again. + */ + void (*passwd_callback)(struct st_mysql *mysql, const char *plugin, void (^with_password)(const char *passwd)); + /* SPMySQL patch: This is used with passwd_callback to bridge back to OOP land */ + void *sp_context; } MYSQL; diff -Naur mysql-5.5.59-p002/sql-common/client.c mysql-5.5.59-p003/sql-common/client.c --- mysql-5.5.59-p002/sql-common/client.c 2017-11-27 13:03:17.000000000 +0100 +++ mysql-5.5.59-p003/sql-common/client.c 2018-02-16 00:27:37.000000000 +0100 @@ -2952,7 +2952,7 @@ auth_plugin_t *auth_plugin; MCPVIO_EXT mpvio; ulong pkt_length; - int res; + __block int res; DBUG_ENTER ("run_plugin_auth"); /* determine the default/initial plugin to use */ @@ -2996,7 +2996,29 @@ mpvio.db= db; mpvio.plugin= auth_plugin; - res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + /* + * SPMySQL Patch to inverse the password flow + */ + if(mysql->passwd_callback) + { + res = CR_ERROR; //fallback, if block is never invoked + mysql->passwd_callback(mysql, auth_plugin_name, ^(const char *passwd) { + char *saved_passwd = mysql->passwd; + mysql->passwd = (char *)(passwd ? passwd : ""); // see mysql_change_user + res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + mysql->passwd = saved_passwd; + }); + } + else + { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, + unknown_sqlstate, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), + auth_plugin_name, + "passwd_callback not set!"); + DBUG_RETURN (1); + } + DBUG_PRINT ("info", ("authenticate_user returned %s", res == CR_OK ? "CR_OK" : res == CR_ERROR ? "CR_ERROR" : @@ -3069,7 +3091,13 @@ DBUG_RETURN(1); mpvio.plugin= auth_plugin; - res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + res = CR_ERROR; //fallback, if block is never invoked + mysql->passwd_callback(mysql, auth_plugin_name, ^(const char *passwd) { + char *saved_passwd = mysql->passwd; + mysql->passwd = (char *)(passwd ? passwd : ""); // see mysql_change_user + res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + mysql->passwd = saved_passwd; + }); DBUG_PRINT ("info", ("second authenticate_user returned %s", res == CR_OK ? "CR_OK" :