diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 3e05ff1c..e79b16b8 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -380,7 +380,12 @@ pdo_error PDO_ERRORS[] = { { PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false } - }, + }, + { + SQLSRV_AE_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires Microsoft ODBC Driver 17 or higher to " + "communicate with SQL Server with ColumnEncryption attribute enabled.", -74, false } + }, { UINT_MAX, {} } }; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 8190ebbf..201f78d8 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -50,7 +50,7 @@ const int INFO_BUFFER_LEN = 256; const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; // ODBC driver name. -const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; +const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 17 for SQL Server};","Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; // default options if only the server is specified const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; @@ -144,60 +144,29 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); conn->set_func( driver_func ); - build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_options TSRMLS_CC); - for( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { - std::string conn_str = conn_options + CONNECTION_STRING_DRIVER_NAME[i]; - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( SQLWCHAR ); + build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_options TSRMLS_CC); + bool missing_driver_error = false; + if (conn->ce_option.enabled) { + r = core_odbc_connect(conn, conn_options, DRIVER_VERSION::ODBC_DRIVER_13, wconn_string, wconn_len, missing_driver_error); - wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len ); - - CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) - { + CHECK_CUSTOM_ERROR(missing_driver_error, conn, SQLSRV_AE_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { throw core::CoreException(); } - - SQLSMALLINT output_conn_size; -#ifndef _WIN32 - // unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled. - // connection handle has been allocated using henv_cp, means pooling enabled in a PHP script - if ( henv == &henv_cp ) - { - r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); } - else - { - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - } -#else - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); -#endif // !_WIN32 + else { - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast(conn_str.c_str()), 0, conn_str.size()); - memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - if ( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - bool missing_driver_error = ( SQL_SUCCEEDED( sr ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2' ); + for (std::size_t i = DRIVER_VERSION::ODBC_DRIVER_11; i <= DRIVER_VERSION::LAST; ++i) { + r = core_odbc_connect(conn, conn_options, static_cast(i), wconn_string, wconn_len, missing_driver_error); // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR(missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + CHECK_CUSTOM_ERROR(missing_driver_error && (i == DRIVER_VERSION::LAST), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { throw core::CoreException(); } if ( !missing_driver_error ) { break; } - } - else { - conn->driver_version = static_cast( i ); - break; - } - } // for + } // for + } // else ce_option enabled CHECK_SQL_ERROR( r, conn ) { throw core::CoreException(); @@ -256,6 +225,54 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont return return_conn; } +SQLRETURN core_odbc_connect(_Inout_ sqlsrv_conn* conn, const std::string& conn_options, const DRIVER_VERSION odbc_version, SQLWCHAR* wconn_string, unsigned int& wconn_len,bool missing_driver_error) +{ + SQLRETURN r = SQL_SUCCESS; + std::string conn_str = conn_options + CONNECTION_STRING_DRIVER_NAME[odbc_version]; + + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = static_cast(conn_str.length() + 1) * sizeof(SQLWCHAR); + + wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); + + CHECK_CUSTOM_ERROR(wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; +#ifndef _WIN32 + // unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled. + // connection handle has been allocated using henv_cp, means pooling enabled in a PHP script + if (henv == &henv_cp) + { + r = SQLDriverConnect(conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT); + } + else + { + r = SQLDriverConnectW(conn->handle(), NULL, reinterpret_cast(wconn_string.get()), static_cast(wconn_len), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT); + } +#else + r = SQLDriverConnectW(conn->handle(), NULL, reinterpret_cast(wconn_string), static_cast(wconn_len), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT); +#endif // !_WIN32 + + // clear the connection string from memory to remove sensitive data (such as a password). + memset(const_cast(conn_str.c_str()), 0, conn_str.size()); + memset(wconn_string, 0, wconn_len * sizeof(SQLWCHAR)); // wconn_len is the number of characters, not bytes + conn_str.clear(); + + if (SQL_SUCCEEDED(r)) { + conn->driver_version = static_cast(odbc_version); + } + else { + SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; + SQLSMALLINT len; + SQLRETURN sr = SQLGetDiagField(SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len); + missing_driver_error = (SQL_SUCCEEDED(sr) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2'); + } + return r; +} // core_sqlsrv_begin_transaction diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 52b52093..e3e4b0e9 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1034,10 +1034,11 @@ enum SERVER_VERSION { // supported driver versions. enum DRIVER_VERSION : size_t { - MIN = 0, - ODBC_DRIVER_13 = MIN, - ODBC_DRIVER_11 = 1, - MAX = ODBC_DRIVER_11, + FIRST = 0, + ODBC_DRIVER_17 = FIRST, + ODBC_DRIVER_13 = 1, + ODBC_DRIVER_11 = 2, + LAST = ODBC_DRIVER_11 }; // forward decl @@ -1205,6 +1206,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], _In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC ); +SQLRETURN core_odbc_connect(_Inout_ sqlsrv_conn* conn, const std::string& conn_options, const DRIVER_VERSION odbc_version, SQLWCHAR* wconn_string, unsigned int& wconn_len, bool missing_driver_error); void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC ); void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); @@ -1608,6 +1610,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_ODBC, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + SQLSRV_AE_ERROR_DRIVER_NOT_INSTALLED, SQLSRV_ERROR_ZEND_HASH, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 47ff0a01..b72b0595 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -376,6 +376,11 @@ ss_error SS_ERRORS[] = { SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } }, + { + SQLSRV_AE_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires Microsoft ODBC Driver 17 or higher to " + "communicate with SQL Server with ColumnEncryption attribute enabled.", -105, false } + }, // terminate the list of errors/warnings { UINT_MAX, {} }