in driver/connect.cc [476:1263]
SQLRETURN DBC::connect(DataSource *dsrc)
{
SQLRETURN rc = SQL_SUCCESS;
unsigned long flags;
/* Use 'int' and fill all bits to avoid alignment Bug#25920 */
unsigned int opt_ssl_verify_server_cert = ~0;
const my_bool on = 1;
unsigned int on_int = 1;
unsigned long max_long = ~0L;
bool initstmt_executed = false;
dbc_guard guard(this);
#ifdef WIN32
/*
Detect if we are running with ADO present, and force on the
FLAG_COLUMN_SIZE_S32 option if we are.
*/
if (GetModuleHandle("msado15.dll") != NULL)
dsrc->opt_COLUMN_SIZE_S32 = true;
/* Detect another problem specific to MS Access */
if (GetModuleHandle("msaccess.exe") != NULL)
dsrc->opt_DFLT_BIGINT_BIND_STR = true;
/* MS SQL Likes when the CHAR columns are padded */
if (GetModuleHandle("sqlservr.exe") != NULL)
dsrc->opt_PAD_SPACE = true;
#endif
mysql = new_mysql();
if (!mysql)
return set_error("HY001", "Memory allocation error", MYERR_S1001);
flags = get_client_flags(dsrc);
/* Set other connection options */
if (dsrc->opt_BIG_PACKETS || dsrc->opt_SAFE)
#if MYSQL_VERSION_ID >= 50709
mysql_options(mysql, MYSQL_OPT_MAX_ALLOWED_PACKET, &max_long);
#else
/* max_allowed_packet is a magical mysql macro. */
max_allowed_packet = ~0L;
#endif
if (dsrc->opt_NAMED_PIPE)
mysql_options(mysql, MYSQL_OPT_NAMED_PIPE, NullS);
if (dsrc->opt_USE_MYCNF)
mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "odbc");
if (login_timeout)
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&login_timeout);
if (dsrc->opt_READTIMEOUT) {
int timeout = dsrc->opt_READTIMEOUT;
mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT,
&timeout);
}
if (dsrc->opt_WRITETIMEOUT) {
int timeout = dsrc->opt_WRITETIMEOUT;
mysql_options(mysql, MYSQL_OPT_WRITE_TIMEOUT,
&timeout);
}
/*
Pluggable authentication was introduced in mysql 5.5.7
*/
#if MYSQL_VERSION_ID >= 50507
if (dsrc->opt_PLUGIN_DIR)
{
mysql_options(mysql, MYSQL_PLUGIN_DIR,
(const char*)dsrc->opt_PLUGIN_DIR);
}
else if(!default_plugin_location.empty())
{
/*
Note: The default location is either configured by DEFAULT_PLUGIN_DIR
build option (e.g., when building for RPM/DEB packages) or, in case
of Windows, set relative to the DLL location.
*/
mysql_options(mysql, MYSQL_PLUGIN_DIR, default_plugin_location.c_str());
}
if (dsrc->opt_DEFAULT_AUTH)
{
mysql_options(mysql, MYSQL_DEFAULT_AUTH,
(const char*)dsrc->opt_DEFAULT_AUTH);
}
// For some plugins the error message is specified if plugin
// could not be loaded, for some plugins the message is obtained
// from MYSQL* structure. If MSGPLUG param is nullptr
// the error message will be used from the exception.
#define CATCH_PLUGIN_ERROR(MSGPLUG, MSGOPT) catch(plugin_error &err) \
{ \
const char *msgplug = MSGPLUG; \
switch (err.type) \
{ \
case plugin_error::PLUGIN: \
return set_error("HY000", msgplug ? msgplug : err.message.c_str(), 0); \
break;\
case plugin_error::OPTION: \
return set_error("HY000", MSGOPT, 0); \
break;\
default: \
return set_error("HY000", err.message.c_str(), 0); \
} \
}
auto setter = global_pool.get_plugin_option_setter(mysql);
try
{
fido_callback_func fido_func = fido_callback;
if(!fido_func && global_fido_callback)
fido_func = global_fido_callback;
setter.set_plugin_option(
"authentication_webauthn_client",
"plugin_authentication_webauthn_client_messages_callback",
(const void*)fido_func
);
}
CATCH_PLUGIN_ERROR(
"Failed to set a WebAuthn authentciation "
"callback because the WebAuthn authentication "
"plugin could not be loaded",
"Failed to set a WebAuthn authentication callback function"
)
try
{
unsigned int dev = dsrc->opt_WEBAUTHN_DEVICE_NUMBER;
// Set only non-zero device. Zero will be set by default otherwise.
if (dev)
{
setter.set_plugin_option(
"authentication_webauthn_client",
"device",
&dev
);
}
}
CATCH_PLUGIN_ERROR(
"Failed to set a WebAuthn authentication device "
"because the WebAuthn authentication "
"plugin could not be loaded",
"Failed to set a WebAuthn authentication device"
)
try
{
setter.set_plugin_option(
"authentication_oci_client",
"oci-config-file",
(const char*)dsrc->opt_OCI_CONFIG_FILE
);
}
CATCH_PLUGIN_ERROR(
"Couldn't load plugin authentication_oci_client",
"Failed to set config file for authentication_oci_client plugin"
)
try
{
setter.set_plugin_option(
"authentication_oci_client",
"authentication-oci-client-config-profile",
(const char*)dsrc->opt_OCI_CONFIG_PROFILE
);
}
CATCH_PLUGIN_ERROR(
"Couldn't load plugin authentication_oci_client",
"Failed to set config profile for authentication_oci_client plugin"
)
#ifdef WIN32
try
{
setter.set_plugin_option(
"authentication_kerberos_client",
"plugin_authentication_kerberos_client_mode",
(const char*)dsrc->opt_AUTHENTICATION_KERBEROS_MODE
);
}
// For kerberos the loading plugin error should be obtained from
// mysql_error(mysql) call. Therefore the first parameter is nullptr.
CATCH_PLUGIN_ERROR(
nullptr,
"Failed to set mode for authentication_kerberos_client plugin"
)
#else
if ((bool)dsrc->opt_AUTHENTICATION_KERBEROS_MODE &&
myodbc_strcasecmp("GSSAPI",
(const char *)dsrc->opt_AUTHENTICATION_KERBEROS_MODE))
{
return set_error("HY000",
"Invalid value for authentication-kerberos-mode. "
"Only GSSAPI is supported.", 0);
}
#endif
try
{
setter.set_plugin_option(
"authentication_openid_connect_client",
"id-token-file",
(const char*)dsrc->opt_OPENID_TOKEN_FILE
);
}
CATCH_PLUGIN_ERROR(
"Couldn't load plugin authentication_openid_connect_client",
"Failed to set config file for authentication_openid_connect_client plugin"
)
#endif
#define SSL_SET(X, Y) \
if (dsrc->opt_##X && mysql_options(mysql, MYSQL_OPT_##X, \
(const char *)dsrc->opt_##X)) \
return set_error("HY000", "Failed to set " Y, 0);
#define SSL_OPTIONS_LIST(X) \
X(SSL_KEY, "the path name of the client private key file") \
X(SSL_CERT, "the path name of the client public key certificate file") \
X(SSL_CA, "the path name of the Certificate Authority (CA) certificate file") \
X(SSL_CAPATH, "the path name of the directory that contains trusted SSL CA certificate files") \
X(SSL_CIPHER, "the list of permissible ciphers for SSL encryption") \
X(SSL_CRL, "Failed to set the certificate revocation list file") \
X(SSL_CRLPATH, "Failed to set the certificate revocation list path")
SSL_OPTIONS_LIST(SSL_SET);
#if MYSQL_VERSION_ID < 80003
if (dsrc->SSLVERIFY)
mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
(const char *)&opt_ssl_verify_server_cert);
#endif
#if MYSQL_VERSION_ID >= 50660
if (dsrc->opt_RSAKEY)
{
/* Read the public key on the client side */
mysql_options(mysql, MYSQL_SERVER_PUBLIC_KEY,
(const char*)dsrc->opt_RSAKEY);
}
#endif
#if MYSQL_VERSION_ID >= 50710
{
std::string tls_options;
if (dsrc->opt_TLS_VERSIONS)
{
// If tls-versions is used the NO_TLS_X options are deactivated
tls_options = (const char*)dsrc->opt_TLS_VERSIONS;
}
else
{
std::map<std::string, bool> opts = {
{ "TLSv1.2", !dsrc->opt_NO_TLS_1_2 },
{ "TLSv1.3", !dsrc->opt_NO_TLS_1_3 },
};
for (auto &opt : opts)
{
if (!opt.second)
continue;
if (!tls_options.empty())
tls_options.append(",");
tls_options.append(opt.first);
}
}
if (!tls_options.length() ||
mysql_options(mysql, MYSQL_OPT_TLS_VERSION, tls_options.c_str()))
{
return set_error("HY000",
"SSL connection error: No valid TLS version available", 0);
}
}
#endif
#if MYSQL_VERSION_ID >= 80004
if (dsrc->opt_GET_SERVER_PUBLIC_KEY)
{
/* Get the server public key */
mysql_options(mysql, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, (const void*)&on);
}
#endif
#if MYSQL_VERSION_ID >= 50610
if (dsrc->opt_CAN_HANDLE_EXP_PWD)
{
mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, (char *)&on);
}
#endif
#if (MYSQL_VERSION_ID >= 50527 && MYSQL_VERSION_ID < 50600) || MYSQL_VERSION_ID >= 50607
if (dsrc->opt_ENABLE_CLEARTEXT_PLUGIN)
{
mysql_options(mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN, (char *)&on);
}
#endif
if (dsrc->opt_ENABLE_LOCAL_INFILE)
{
mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, &on_int);
}
if (dsrc->opt_LOAD_DATA_LOCAL_DIR)
{
mysql_options(mysql, MYSQL_OPT_LOAD_DATA_LOCAL_DIR,
(const char*)dsrc->opt_LOAD_DATA_LOCAL_DIR);
}
// Set the connector identification attributes.
std::string attr_list[][2] = {
{"_connector_license", MYODBC_LICENSE},
{"_connector_name", "mysql-connector-odbc"},
{"_connector_type", MYODBC_STRDRIVERTYPE},
{"_connector_version", MYODBC_CONN_ATTR_VER}
};
for (auto &val : attr_list)
{
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD,
val[0].c_str(), val[1].c_str());
}
#if MFA_ENABLED
if(dsrc->pwd1 && dsrc->pwd1[0])
{
ds_get_utf8attr(dsrc->pwd1, &dsrc->pwd18);
int fator = 1;
mysql_options4(mysql, MYSQL_OPT_USER_PASSWORD,
&fator,
dsrc->pwd18);
}
if(dsrc->pwd2 && dsrc->pwd2[0])
{
ds_get_utf8attr(dsrc->pwd2, &dsrc->pwd28);
int fator = 2;
mysql_options4(mysql, MYSQL_OPT_USER_PASSWORD,
&fator,
dsrc->pwd28);
}
if(dsrc->pwd3 && dsrc->pwd3[0])
{
ds_get_utf8attr(dsrc->pwd3, &dsrc->pwd38);
int fator = 3;
mysql_options4(mysql, MYSQL_OPT_USER_PASSWORD,
&fator,
dsrc->pwd38);
}
#endif
#if MYSQL_VERSION_ID >= 50711
if (dsrc->opt_SSL_MODE)
{
unsigned int mode = 0;
if (!myodbc_strcasecmp(ODBC_SSL_MODE_DISABLED, dsrc->opt_SSL_MODE))
mode = SSL_MODE_DISABLED;
if (!myodbc_strcasecmp(ODBC_SSL_MODE_PREFERRED, dsrc->opt_SSL_MODE))
mode = SSL_MODE_PREFERRED;
if (!myodbc_strcasecmp(ODBC_SSL_MODE_REQUIRED, dsrc->opt_SSL_MODE))
mode = SSL_MODE_REQUIRED;
if (!myodbc_strcasecmp(ODBC_SSL_MODE_VERIFY_CA, dsrc->opt_SSL_MODE))
mode = SSL_MODE_VERIFY_CA;
if (!myodbc_strcasecmp(ODBC_SSL_MODE_VERIFY_IDENTITY, dsrc->opt_SSL_MODE))
mode = SSL_MODE_VERIFY_IDENTITY;
// Don't do anything if there is no match with any of the available modes
if (mode)
mysql_options(mysql, MYSQL_OPT_SSL_MODE, &mode);
}
#endif
uint16_t total_weight = 0;
std::vector<Srv_host_detail> hosts;
try {
hosts = parse_host_list(dsrc->opt_SERVER, dsrc->opt_PORT);
} catch (std::string &e)
{
return set_error("HY000", e.c_str(), 0);
}
if(!dsrc->opt_MULTI_HOST && hosts.size() > 1)
{
return set_error("HY000", "Missing option MULTI_HOST=1", 0);
}
if(dsrc->opt_ENABLE_DNS_SRV && hosts.size() > 1)
{
return set_error("HY000", "Specifying multiple hostnames with DNS SRV look up is not allowed.", 0);
}
if(dsrc->opt_ENABLE_DNS_SRV && dsrc->opt_PORT)
{
return set_error("HY000", "Specifying a port number with DNS SRV lookup is not allowed.", 0);
}
if(dsrc->opt_ENABLE_DNS_SRV)
{
if(dsrc->opt_SOCKET)
{
return set_error("HY000",
#ifdef _WIN32
"Using Named Pipes with DNS SRV lookup is not allowed.",
#else
"Using Unix domain sockets with DNS SRV lookup is not allowed",
#endif
0);
}
if(hosts.empty())
{
std::stringstream err;
err << "Unable to locate any hosts for " << (const char*)dsrc->opt_SERVER;
return set_error("HY000", err.str().c_str(), 0);
}
}
// Handle OPENTELEMETRY option.
// Note: Using while() instead of if() to be able to get out of it with
// `break` statement.
while (dsrc->opt_OPENTELEMETRY)
{
#ifndef TELEMETRY
return set_error("HY000",
"OPENTELEMETRY option is not supported on this platform."
,0);
#else
#define SET_OTEL_MODE(X,N) \
if (!myodbc_strcasecmp(#X, dsrc->opt_OPENTELEMETRY)) \
{ telemetry.set_mode(OTEL_ ## X); break; }
ODBC_OTEL_MODE(SET_OTEL_MODE)
// If we are here then option was not recognized above.
return set_error("HY000",
"OPENTELEMETRY option can be set only to DISABLED or PREFERRED"
, 0);
#endif
}
telemetry.span_start(this);
auto do_connect = [this,&dsrc,&flags](
const char *host,
unsigned int port
) -> short
{
int protocol;
if(dsrc->opt_SOCKET)
{
#ifdef _WIN32
protocol = MYSQL_PROTOCOL_PIPE;
#else
protocol = MYSQL_PROTOCOL_SOCKET;
#endif
} else
{
protocol = MYSQL_PROTOCOL_TCP;
}
mysql_options(mysql, MYSQL_OPT_PROTOCOL, &protocol);
//Setting server and port
dsrc->opt_SERVER = host;
dsrc->opt_PORT = port;
MYSQL *connect_result = dsrc->opt_ENABLE_DNS_SRV ?
mysql_real_connect_dns_srv(mysql,
host,
dsrc->opt_UID,
dsrc->opt_PWD,
dsrc->opt_DATABASE,
flags)
:
mysql_real_connect(mysql,
host,
dsrc->opt_UID,
dsrc->opt_PWD,
dsrc->opt_DATABASE,
port,
dsrc->opt_SOCKET,
flags);
if (!connect_result)
{
unsigned int native_error= mysql_errno(mysql);
/* Before 5.6.11 error returned by server was ER_MUST_CHANGE_PASSWORD(1820).
In 5.6.11 it changed to ER_MUST_CHANGE_PASSWORD_LOGIN(1862)
We must to change error for old servers in order to set correct sqlstate */
if (native_error == 1820 && ER_MUST_CHANGE_PASSWORD_LOGIN != 1820)
{
native_error= ER_MUST_CHANGE_PASSWORD_LOGIN;
}
#if MYSQL_VERSION_ID < 50610
/* In that special case when the driver was linked against old version of libmysql*/
if (native_error == ER_MUST_CHANGE_PASSWORD_LOGIN
&& dsrc->CAN_HANDLE_EXP_PWD)
{
/* The password has expired, application said it knows how to deal with
that, but the driver was linked that
does not support this option. Thus we change native error. */
/* TODO: enum/defines for driver specific errors */
return set_conn_error(dbc, MYERR_08004,
"Your password has expired, but underlying library doesn't support "
"this functionlaity", 0);
}
#endif
set_error("HY000", mysql_error(mysql), native_error);
translate_error((char*)error.sqlstate.c_str(), MYERR_S1000, native_error);
return SQL_ERROR;
}
return SQL_SUCCESS;
};
//Connect loop
{
bool connected = false;
std::random_device rd;
std::mt19937 generator(rd()); // seed the generator
while(!hosts.empty() && !connected)
{
std::uniform_int_distribution<int> distribution(
0, (int)hosts.size() - 1); // define the range of random numbers
int pos = distribution(generator);
auto el = hosts.begin();
std::advance(el, pos);
if(do_connect(el->name.c_str(), el->port) == SQL_SUCCESS)
{
connected = true;
telemetry.set_attribs(this, dsrc);
break;
}
else
{
switch (mysql_errno(mysql))
{
case ER_CON_COUNT_ERROR:
case CR_SOCKET_CREATE_ERROR:
case CR_CONNECTION_ERROR:
case CR_CONN_HOST_ERROR:
case CR_IPSOCK_ERROR:
case CR_UNKNOWN_HOST:
//On Network errors, continue
break;
default:
//If SQLSTATE not 08xxx, which is used for network errors
if(strncmp(mysql_sqlstate(mysql), "08", 2) != 0)
{
//Return error and do not try another host
return SQL_ERROR;
}
}
}
hosts.erase(el);
}
if(!connected)
{
if(dsrc->opt_ENABLE_DNS_SRV)
{
std::string err =
std::string("Unable to connect to any of the hosts of ") +
(const char*)dsrc->opt_SERVER + " SRV";
set_error("HY000", err.c_str(), 0);
}
else if (dsrc->opt_MULTI_HOST && hosts.size() > 1) {
set_error("HY000", "Unable to connect to any of the hosts", 0);
}
//The others will retrieve the error from connect
return SQL_ERROR;
}
}
has_query_attrs = mysql->server_capabilities & CLIENT_QUERY_ATTRIBUTES;
if (!is_minimum_version(mysql->server_version, "4.1.1"))
{
close();
return set_error("08001", "Driver does not support server versions under 4.1.1", 0);
}
rc = set_charset_options(dsrc->opt_CHARSET);
// It could be an error with expired password in which case we
// still try to execute init statements and retry below.
if (rc == SQL_ERROR && error.native_error != ER_MUST_CHANGE_PASSWORD)
return SQL_ERROR;
// Try running INITSTMT.
if (!SQL_SUCCEEDED(run_initstmt(this, dsrc)))
return error.retcode;
// If we had expired password error at the beginning
// try setting charset options again.
// NOTE: charset name is converted to a single-byte
// charset by previous call to ds_get_utf8attr().
if (rc == SQL_ERROR)
rc = set_charset_options((const char*)dsrc->opt_CHARSET);
if (!SQL_SUCCEEDED(rc))
{
return SQL_ERROR;
}
/*
The MySQL server has a workaround for old versions of Microsoft Access
(and possibly other products) that is no longer necessary, but is
unfortunately enabled by default. We have to turn it off, or it causes
other problems.
*/
if (!dsrc->opt_AUTO_IS_NULL &&
execute_query("SET SQL_AUTO_IS_NULL = 0", SQL_NTS, true) != SQL_SUCCESS)
{
return SQL_ERROR;
}
/*
The padding of CHAR(N) data should be done on the server.
To enable this behavior the SQL_MODE should be set to
PAD_CHAR_TO_FULL_LENGTH.
NOTE: If @@sql_mode is empty the concatenation with preceding
comma is acceptable.
*/
if (dsrc->opt_PAD_SPACE &&
execute_query("SET sql_mode=concat(@@sql_mode,',PAD_CHAR_TO_FULL_LENGTH');",
SQL_NTS, true) != SQL_SUCCESS)
{
return SQL_ERROR;
}
ds = *dsrc;
/* init all needed UTF-8 strings */
const char *opt_db = ds.opt_DATABASE;
database = opt_db ? opt_db : "";
if (ds.opt_LOG_QUERY && !query_log)
query_log = init_query_log();
/* Set the statement error prefix based on the server version. */
myodbc::strxmov(st_error_prefix, MYODBC_ERROR_PREFIX, "[mysqld-",
mysql->server_version, "]", NullS);
/*
This variable will be needed later to process possible
errors when setting MYSQL_OPT_RECONNECT when client lib
used at runtime does not support it.
*/
int set_reconnect_result = 0;
#if MYSQL_VERSION_ID < 80300
/* This needs to be set after connection, or it doesn't stick. */
if (ds.opt_AUTO_RECONNECT)
{
set_reconnect_result = mysql_options(mysql,
MYSQL_OPT_RECONNECT, (char *)&on);
}
#else
/* MYSQL_OPT_RECONNECT doesn't exist at build time, report warning later. */
set_reconnect_result = 1;
#endif
/* Make sure autocommit is set as configured. */
if (commit_flag == CHECK_AUTOCOMMIT_OFF)
{
if (!transactions_supported() || ds.opt_NO_TRANSACTIONS)
{
commit_flag = CHECK_AUTOCOMMIT_ON;
rc = set_error(MYERR_01S02,
"Transactions are not enabled, option value "
"SQL_AUTOCOMMIT_OFF changed to SQL_AUTOCOMMIT_ON",
SQL_SUCCESS_WITH_INFO);
}
else if (autocommit_is_on() && mysql_autocommit(mysql, FALSE))
{
/** @todo set error */
return SQL_ERROR;
}
}
else if ((commit_flag == CHECK_AUTOCOMMIT_ON) &&
transactions_supported() && !autocommit_is_on())
{
if (mysql_autocommit(mysql, TRUE))
{
/** @todo set error */
return SQL_ERROR;
}
}
/* Set transaction isolation as configured. */
if (txn_isolation != DEFAULT_TXN_ISOLATION)
{
char buff[80];
const char *level;
if (txn_isolation & SQL_TXN_SERIALIZABLE)
level= "SERIALIZABLE";
else if (txn_isolation & SQL_TXN_REPEATABLE_READ)
level= "REPEATABLE READ";
else if (txn_isolation & SQL_TXN_READ_COMMITTED)
level= "READ COMMITTED";
else
level= "READ UNCOMMITTED";
if (transactions_supported())
{
sprintf(buff, "SET SESSION TRANSACTION ISOLATION LEVEL %s", level);
if (execute_query(buff, SQL_NTS, true) != SQL_SUCCESS)
{
return SQL_ERROR;
}
}
else
{
txn_isolation = SQL_TXN_READ_UNCOMMITTED;
rc = set_error(MYERR_01S02,
"Transactions are not enabled, so transaction isolation "
"was ignored.", SQL_SUCCESS_WITH_INFO);
}
}
/*
AUTO_RECONNECT option needs to be handled with the following
considerations:
A - version of ODBC driver code
B - version of client lib used for building ODBC driver (*)
C - version of client lib used at runtime
Note (*): As given by MYSQL_VERSION_ID macro.
The behavior should be like this:
A B C
==== ===== ===== =======================================
old * old reconnect option works
old * new reconnect option silently ignored
new old old reconnect option works
new old new reconnect option ignored with warning
new new * reconnect option ignored with warning
*/
if (ds.opt_AUTO_RECONNECT && set_reconnect_result)
{
set_error("HY000",
"The option AUTO_RECONNECT is not supported "
"by MySQL version 8.3.0 or later. "
"Please remove it from the connection string "
"or the Data Source", 0);
rc = SQL_SUCCESS_WITH_INFO;
}
mysql_get_option(mysql, MYSQL_OPT_NET_BUFFER_LENGTH, &net_buffer_len);
guard.set_success(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO);
return rc;
}