in library/cdbc/src/driver_manager.cpp [234:594]
ConnectionWrapper DriverManager::getConnection(const db_mgmt_ConnectionRef &connectionProperties,
std::shared_ptr<SSHTunnel> tunnel, Authentication::Ref password,
ConnectionInitSlot connection_init_slot) {
grt::DictRef parameter_values = connectionProperties->parameterValues();
// Load the driver for the connection dynamically. However for the C++ connector we have a static
// link anyway, so use this instead.
// 0. determine correct driver filename
db_mgmt_DriverRef drv = connectionProperties->driver();
std::string library = "";
if (drv.is_valid())
library = drv->driverLibraryName();
else
throw SQLException("Invalid connection settings: undefined connection driver");
Driver *driver;
if (library == "mysqlcppconn")
driver = get_driver_instance();
else {
#ifdef _MSC_VER
library.append(".dll");
#elif defined(__APPLE__)
library.append(".dylib");
#else
library.append(".so");
#endif
if (drv->name() != "MysqlNativeSaslKerberos" && drv->name() != "MysqlNativeKerberos" &&
parameter_values.get_string("userName").empty())
throw SQLException("No user name set for this connection");
// 1. find driver
GModule *gmodule = g_module_open((_driver_path + "/" + library).c_str(), G_MODULE_BIND_LOCAL);
if (NULL == gmodule) {
fprintf(stderr, "Error: %s", g_module_error());
throw SQLException(std::string("Database driver: Failed to open library '")
.append(_driver_path + "/" + library)
.append("'. Check settings.")
.c_str());
}
Driver *(*get_driver_instance)() = NULL;
g_module_symbol(gmodule, "sql_mysql_get_driver_instance", (gpointer *)&get_driver_instance);
if (NULL == get_driver_instance) {
g_module_close(gmodule);
throw SQLException("Database driver: Failed to get library instance. Check settings.");
}
driver = get_driver_instance();
}
if (driver == NULL)
throw SQLException("Database driver: Failed to get driver instance. Check settings.");
else
_drivers[library] = std::bind(&Driver::threadEnd, driver);
getClientLibVersion(driver);
// 2. call driver->connect()
Param_types param_types;
{
grt::ListRef<db_mgmt_DriverParameter> params = connectionProperties->driver()->parameters();
for (size_t n = 0, count = params.count(); n < count; ++n) {
db_mgmt_DriverParameterRef param = params.get(n);
param_types[param->name()] = param->paramType();
}
// set the values that take boolean options, so that they can be converted correctly in case they're specified
// manually by the user
param_types["OPT_REPORT_DATA_TRUNCATION"] = "boolean";
param_types["OPT_ENABLE_CLEARTEXT_PLUGIN"] = "boolean";
param_types["CLIENT_INTERACTIVE"] = "boolean";
param_types["CLIENT_COMPRESS"] = "boolean";
param_types["CLIENT_FOUND_ROWS"] = "boolean";
param_types["CLIENT_IGNORE_SIGPIPE"] = "boolean";
param_types["CLIENT_IGNORE_SPACE"] = "boolean";
param_types["CLIENT_LOCAL_FILES"] = "boolean";
param_types["CLIENT_MULTI_STATEMENTS"] = "boolean";
param_types["CLIENT_NO_SCHEMA"] = "boolean";
}
ConnectOptionsMap properties;
parameter_values.foreach (
std::bind(&conv_to_dbc_value, std::placeholders::_1, std::placeholders::_2, std::ref(properties), param_types));
{
ConnectPropertyVal tmp;
const int conn_timeout = 60;
const int read_timeout = 30;
if (properties.find("OPT_CONNECT_TIMEOUT") == properties.end())
properties["OPT_CONNECT_TIMEOUT"] = conn_timeout;
if (properties.find("OPT_READ_TIMEOUT") == properties.end())
properties["OPT_READ_TIMEOUT"] = read_timeout;
#ifdef _MSC_VER
properties["pluginDir"] = base::dirname(mforms::App::get()->get_executable_path("base.dll"));
#elif defined(__APPLE__)
auto nativePasswordDir = base::dirname(mforms::App::get()->get_executable_path("mysql_native_password.so"));
properties["pluginDir"] = nativePasswordDir + "/../Frameworks";
#else
if (!_testing) {
properties["pluginDir"] = base::dirname(mforms::App::get()->get_executable_path("mysql_native_password.so"));
}
#endif
properties["OPT_AUTHENTICATION_KERBEROS_CLIENT_MODE"] = "";
std::string krb5 = parameter_values.get_string("krb5");
std::string krb5cache = parameter_values.get_string("krb5cache");
std::vector<char> env;
if (!krb5.empty()) {
auto tmp = std::string("KRB5_CONFIG=" + krb5);
env = std::vector<char>(tmp.begin(), tmp.end());
} else {
auto tmp = std::string("KRB5_CONFIG=");
env = std::vector<char>(tmp.begin(), tmp.end());
}
putenv(&env[0]);
env.clear();
if (!krb5cache.empty()) {
auto tmp = std::string("KRB5CCNAME=" + krb5cache);
env = std::vector<char>(tmp.begin(), tmp.end());
} else {
auto tmp = std::string("KRB5CCNAME=");
env = std::vector<char>(tmp.begin(), tmp.end());
}
putenv(&env[0]);
properties["defaultAuth"] = "";
}
properties["OPT_CAN_HANDLE_EXPIRED_PASSWORDS"] = true;
properties["CLIENT_MULTI_STATEMENTS"] = true;
properties["metadataUseInfoSchema"] =
false; // I_S is way too slow for many things as of MySQL 5.6.10, so disable it for now
// set application name
{
std::map<sql::SQLString, sql::SQLString> attribs;
attribs["program_name"] = "MySQLWorkbench";
properties["OPT_CONNECT_ATTR_ADD"] = attribs;
}
// If SSL is enabled but there's no certificate or anything, create the sslKey option to force enabling SSL without
// a key
// (equivalent to starting cmdline client with mysql --ssl-key=)
if (parameter_values.get_string("sslKey", "") == "" && parameter_values.get_int("useSSL", 0) != 0)
properties["sslKey"] = std::string();
// If SSL is not enabled, clear all ssl related props to not confuse the connector
if (parameter_values.get_int("useSSL", 0) == 0) {
properties.erase("sslKey");
properties.erase("sslCert");
properties.erase("sslCA");
properties.erase("sslCAPath");
properties.erase("sslCipher");
}
ssize_t sslModeWb = parameter_values.get_int("useSSL", 0);
sql::ssl_mode sslMode = sql::SSL_MODE_DISABLED;
switch (sslModeWb) {
case 0:
sslMode = sql::SSL_MODE_DISABLED;
properties["OPT_GET_SERVER_PUBLIC_KEY"] = true;
break;
case 1:
sslMode = sql::SSL_MODE_PREFERRED;
break;
case 2:
sslMode = sql::SSL_MODE_REQUIRED;
break;
case 3:
sslMode = sql::SSL_MODE_VERIFY_CA;
break;
case 4:
sslMode = sql::SSL_MODE_VERIFY_IDENTITY;
break;
}
properties["OPT_SSL_MODE"] = sslMode;
// If we are on a pipe connection then set the host name explicitly.
// However, pipe connections can only be established on the local box (Win only).
if (drv->name() == "MysqlNativeSocket") {
#ifdef _MSC_VER
ConnectOptionsMap::iterator it = properties.find("socket");
if (it != properties.end()) {
properties["pipe"] = it->second;
properties.erase(it);
}
properties["hostName"] = std::string(".");
#else
properties["hostName"] = std::string();
#endif
} else if (drv->name() == "MysqlNativeSaslKerberos") {
properties["defaultAuth"] = "authentication_ldap_sasl_client";
std::string plugin_dir_path = parameter_values.get_string("mysqlplugindir");
if (!plugin_dir_path.empty()) {
properties["pluginDir"] = plugin_dir_path;
} else {
std::string libName = "authentication_ldap_sasl_client";
#ifdef _MSC_VER
libName.append(".dll");
#else
libName.append(".so");
#endif
properties["pluginDir"] = base::dirname(mforms::App::get()->get_executable_path(libName));
}
} else if (drv->name() == "MysqlNativeKerberos") {
properties["defaultAuth"] = "authentication_kerberos_client";
std::string plugin_dir_path = parameter_values.get_string("mysqlplugindir");
if (!plugin_dir_path.empty()) {
properties["pluginDir"] = plugin_dir_path;
} else {
std::string libName = "authentication_kerberos_client";
#ifdef _MSC_VER
libName.append(".dll");
#else
libName.append(".so");
#endif
properties["pluginDir"] = base::dirname(mforms::App::get()->get_executable_path(libName));
}
properties["OPT_AUTHENTICATION_KERBEROS_CLIENT_MODE"] = parameter_values.get_int("kerberosMode", 0) == 1 ? "SSPI": "GSSAPI";
} else if (drv->name() == "MysqlNativeLDAP") {
properties["OPT_ENABLE_CLEARTEXT_PLUGIN"] = true;
}
if (tunnel) {
// Make the driver connect to the local tunnel port.
properties["port"] = tunnel->getConfig().localport;
properties["hostName"] = sql::SQLString("127.0.0.1");
} else
properties["hostName"] = "[" + parameter_values.get_string("hostName") +
"]"; // [quote] hostname so that ipv6 addresses aren't parsed as URIs
Authentication::Ref authref;
// Check if there is a stored or cached password, if there isn't try without one (blank)
// If we get an auth error, then we ask for the password
bool force_ask_password = false;
retry:
if (password && drv->name() != "MysqlNativeSaslKerberos" && drv->name() != "MysqlNativeKerberos") {
authref = password;
if (password->is_valid())
properties["password"] = std::string(authref->password());
} else {
// password not in profile (and no keyfile provided)
if (_requestPassword && (force_ask_password || parameter_values.get_string("password") == "")) {
// check if we have cached the password for this connection
if (time(NULL) - _cacheTime > MYSQL_PASSWORD_CACHE_TIMEOUT || force_ask_password) {
_cacheKey.clear();
_cachedPassword.clear();
}
std::string key = connectionProperties->hostIdentifier();
if (parameter_values.get_string("userName") + std::string("@") + key == _cacheKey &&
!key.empty()) // hostIdentifier will be "" if it's a temporary connection
properties["password"] = _cachedPassword;
else {
if (force_ask_password)
_cachedPassword = _requestPassword(connectionProperties, force_ask_password);
else {
bool is_cached_password_found = _findPassword(connectionProperties, _cachedPassword);
if (!is_cached_password_found)
_cachedPassword = ""; // try no password
}
properties["password"] = _cachedPassword;
_cacheKey = parameter_values.get_string("userName") + std::string("@") + key;
}
_cacheTime = time(NULL);
}
}
// passing some empty values confuse the connector
{
std::list<std::string> prop_names;
prop_names.push_back("socket");
prop_names.push_back("schema");
for (const std::string &prop_name : prop_names) {
ConnectOptionsMap::iterator prop_iter = properties.find(prop_name);
if (properties.end() != prop_iter) {
sql::SQLString *val = prop_iter->second.get<sql::SQLString>();
if (val->compare("") == 0)
properties.erase(prop_iter);
}
}
}
try {
std::unique_ptr<Connection> conn(driver->connect(properties));
std::string ssl_cipher;
// make sure the user we got logged in is the user we wanted
{
std::unique_ptr<sql::Statement> statement(conn.get()->createStatement());
std::unique_ptr<sql::ResultSet> rs(statement->executeQuery("SELECT current_user()"));
if (rs->next()) {
std::string current_user = rs->getString(1);
if (current_user == "" && parameter_values.get_string("userName") != "") {
// got logged in as anon-user when we wanted something else
// if we got there by accident, try to login again after asking for pwd
if (!force_ask_password) {
if (authref) {
authref->invalidate();
throw AuthenticationError("Invalid username or password", authref);
} else {
force_ask_password = true;
goto retry;
}
}
}
}
}
std::string sql_mode = parameter_values.get_string("SQL_MODE", "");
if (!sql_mode.empty()) {
std::unique_ptr<sql::Statement> statement(conn.get()->createStatement());
statement->execute("SET SESSION SQL_MODE='" + sql_mode + "'");
}
if (connection_init_slot)
connection_init_slot(conn.get(), connectionProperties);
// We could set this on the parameters, but we wouldn't know the server version
std::unique_ptr<sql::Statement> stmt(conn.get()->createStatement());
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery("show character set where charset = 'utf8mb4'"));
if (res->rowsCount() >= 1) {
stmt->executeUpdate("SET NAMES 'utf8mb4'");
} else {
stmt->executeUpdate("SET NAMES 'utf8'");
}
std::string def_schema = parameter_values.get_string("schema", "");
if (!def_schema.empty())
conn->setSchema(def_schema);
return ConnectionWrapper(std::move(conn), tunnel);
} catch (sql::SQLException &exc) {
// authentication error
if (exc.getErrorCode() == 0 && getClientLibVersionNumeric(driver) >= 80019) {
throw sql::SQLException(exc.what(), exc.getSQLStateCStr(),
2003); // Convert to to error 2003 as the previous connector
} else if (exc.getErrorCode() == 1045
|| exc.getErrorCode() == 1044
|| exc.getErrorCode() == 1968 // ER_ACCESS_DENIED_NO_PASSWORD_ERROR
|| (exc.getErrorCode() == 2000 && drv->name() == "MysqlNativeKerberos")) {
if (!force_ask_password) {
if (authref) {
authref->invalidate();
throw AuthenticationError(exc.what(), authref);
} else {
// ask for password again, this time disablig the password caching
force_ask_password = true;
goto retry;
}
}
}
throw;
} catch (...) {
_cacheKey.clear();
_cachedPassword.clear();
throw;
}
}