From 256e712072897acb08dbdc68b6def3544894488b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 26 Sep 2017 16:38:34 -0700 Subject: [PATCH] also checked if ODBC driver is set when AE is enabled --- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/shared/core_conn.cpp | 174 +++++++++++++++++++++------------ source/shared/core_sqlsrv.h | 14 +-- source/sqlsrv/util.cpp | 2 +- 4 files changed, 120 insertions(+), 72 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index a9bf253a..2c0d43e4 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -398,7 +398,7 @@ pdo_error PDO_ERRORS[] = { { IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -77, false} }, { - SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_AE_DRIVER_REQUIRED, { IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -78, false } }, { diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index d14b1787..0875a102 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -31,8 +31,8 @@ #ifndef _WIN32 #include -#endif #include +#endif // *** internal variables and constants *** @@ -150,67 +150,89 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC ); - bool is_missing_driver = true; - - if ( conn->is_driver_set ) { - r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled ); - } - else if ( conn->ce_option.enabled ) { -#ifndef _WIN32 - // In non-Windows environment, because different unixODBC versions return different error states, - // if it fails to connect it is unreliable to check for a certain sql state error. Thus, - // we will ignore the returned value of 'is_missing_driver' and simply throw an exception. - if( core_search_odbc_driver( DRIVER_VERSION::ODBC_DRIVER_17 ) ) { - conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ]; - r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled ); - } - else { - CHECK_CUSTOM_ERROR( true, conn, SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, get_processor_arch()) { + if( conn->ce_option.enabled ) { + // when AE is enabled, must use ODBC driver 17 + if( conn->driver_version != ODBC_DRIVER_UNKNOWN ) { + CHECK_CUSTOM_ERROR( conn->driver_version != ODBC_DRIVER_17, conn, SQLSRV_ERROR_AE_DRIVER_REQUIRED, get_processor_arch()) { throw core::CoreException(); } - } -#else - conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ]; - r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled ); - CHECK_CUSTOM_ERROR( is_missing_driver, conn, SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); + r = core_odbc_connect( conn, conn_str, is_pooled ); } -#endif // !_WIN32 - } - else { + else { + // driver not specified, so connect using ODBC 17 #ifndef _WIN32 - is_missing_driver = true; - for ( std::size_t i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) { - if (i == DRIVER_VERSION::ODBC_DRIVER_11) // skip ODBC 11 in non-Windows environment - continue; + // In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states + // If it fails to connect using ODBC 17, it is unreliable to check for a certain sql state error. + // Thus, we will simply throw an exception. + if( core_search_odbc_driver_unix( DRIVER_VERSION::ODBC_DRIVER_17 ) ) { + conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ]; + r = core_odbc_connect( conn, conn_str, is_pooled ); + } + else { + CHECK_CUSTOM_ERROR( true, conn, SQLSRV_ERROR_AE_DRIVER_REQUIRED, get_processor_arch()) { + throw core::CoreException(); + } + } +#else + conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ]; + r = core_odbc_connect( conn, conn_str, is_pooled ); - if( core_search_odbc_driver( i ) ) { - std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION( i ) ]; - r = core_odbc_connect( conn, conn_str_driver, is_missing_driver, is_pooled ); - // simply check the connection succeeded or not and ignore the returned value of 'is_missing_driver' - if ( SQL_SUCCEEDED( r ) ) { - is_missing_driver = false; + if(! SQL_SUCCEEDED( r ) ) { + // sql state IM002 means that the specified ODBC driver is not installed + CHECK_CUSTOM_ERROR( core_compare_error_state( conn, r, "IM002" ) , conn, SQLSRV_ERROR_AE_DRIVER_REQUIRED, get_processor_arch()) { + throw core::CoreException(); + } + } +#endif // !_WIN32 + } + } + else { + if( conn->driver_version != ODBC_DRIVER_UNKNOWN ) { + r = core_odbc_connect( conn, conn_str, is_pooled ); + } + else { +#ifndef _WIN32 + DRIVER_VERSION odbc_version = ODBC_DRIVER_UNKNOWN; + for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) { + // skip ODBC 11 in a non-Windows environment -- only available in Red Hat / SUSE (preview) + // https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server#microsoft-odbc-driver-11-for-sql-server-on-linux + if (i == DRIVER_VERSION::ODBC_DRIVER_11) + continue; + + if( core_search_odbc_driver_unix( DRIVER_VERSION( i ) ) ) { + odbc_version = DRIVER_VERSION( i ); break; } } - } - CHECK_CUSTOM_ERROR( is_missing_driver, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); - } -#else - for ( std::size_t i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) { - is_missing_driver = false; - std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION(i) ]; - r = core_odbc_connect( conn, conn_str_driver, is_missing_driver, is_pooled ); - CHECK_CUSTOM_ERROR( is_missing_driver && ( i == DRIVER_VERSION::LAST ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + CHECK_CUSTOM_ERROR( odbc_version == ODBC_DRIVER_UNKNOWN, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { throw core::CoreException(); } - if ( !is_missing_driver) { - break; - } - } // for + std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION( odbc_version ) ]; + r = core_odbc_connect( conn, conn_str_driver, is_pooled ); +#else + for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) { + std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION(i) ]; + r = core_odbc_connect( conn, conn_str_driver, is_pooled ); + + if( SQL_SUCCEEDED( r ) ) { + // successfully connected! + break; + } + else if(! core_compare_error_state( conn, r, "IM002" ) ) { + // sql state IM002 means that the specified ODBC driver is not installed + // something went wrong other than missing the ODBC driver + break; + } + else if( i == DRIVER_VERSION::LAST ) { + // failed to connect even using the last valid ODBC driver + CHECK_CUSTOM_ERROR( true, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + throw core::CoreException(); + } + } + } // for #endif // !_WIN32 + } } // else ce_option enabled CHECK_SQL_ERROR( r, conn ) { @@ -268,14 +290,37 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont return return_conn; } -// core_search_odbc_driver +// core_compare_error_state +// This method compares the error state to the one specified +// Parameters: +// conn - the connection structure on which we establish the connection +// rc - ODBC return code +// Return - a boolean flag that indicates if the error states are the same + +bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ const char* error_state ) +{ + if( SQL_SUCCEEDED( rc ) ) + return false; + + 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 same = false; + if( SQL_SUCCEEDED(sr) && ! strcmp(error_state, reinterpret_cast( state ) ) ) + same = true; + + return same; +} + +// core_search_odbc_driver_unix // This method is meant to be used in a non-Windows environment, // searching for a particular ODBC driver name in the odbcinst.ini file // Parameters: // driver_version - a valid value in enum DRIVER_VERSION // Return - a boolean flag that indicates if the specified driver version is found or not -bool core_search_odbc_driver( _In_ std::size_t driver_version ) +bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ) { char szBuf[DEFAULT_CONN_STR_LEN+1]; // use an arbitrary large enough buffer size WORD cbBufMax = DEFAULT_CONN_STR_LEN; @@ -319,11 +364,10 @@ bool core_search_odbc_driver( _In_ std::size_t driver_version ) // Parameters: // conn - The connection structure on which we establish the connection // conn_str - Connection string -// is_missing_driver - indicates whether odbc driver is installed on client machine // is_pooled - indicate whether it is a pooled connection // Return - SQLRETURN status returned by SQLDriverConnect -SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled ) +SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled ) { SQLRETURN r = SQL_SUCCESS; sqlsrv_malloc_auto_ptr wconn_string; @@ -358,14 +402,6 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con 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 ); - // sql state IM002, means that the specified ODBC driver is not installed - // might return IM003 in ODBC 17 (an issue that will be fixed) - is_missing_driver = ( SQL_SUCCEEDED(sr) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && (state[4] == '2' || state[4] == '3')); - } return r; } @@ -989,10 +1025,20 @@ void driver_set_func::func( _In_ connection_option const* option, _In_ zval* val std::string driver_option( "" ); common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC ); - CHECK_CUSTOM_ERROR( std::find( CONNECTION_STRING_DRIVER_NAME.begin(), CONNECTION_STRING_DRIVER_NAME.end(), driver_option) == CONNECTION_STRING_DRIVER_NAME.end(), conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str){ + conn->driver_version = ODBC_DRIVER_UNKNOWN; + for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) { + std::string driver_name = CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION( i ) ]; + + if (! driver_name.compare( driver_option ) ) { + conn->driver_version = DRIVER_VERSION( i ); + break; + } + } + + CHECK_CUSTOM_ERROR( conn->driver_version == ODBC_DRIVER_UNKNOWN, conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str){ throw core::CoreException(); } - conn->is_driver_set = true; + conn_str += driver_option; } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 152358c8..1b6dda41 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1039,7 +1039,8 @@ enum SERVER_VERSION { // supported driver versions. // the latest RTWed ODBC is the first one -enum DRIVER_VERSION : std::size_t{ +enum DRIVER_VERSION { + ODBC_DRIVER_UNKNOWN = -1, FIRST = 0, ODBC_DRIVER_13 = FIRST, ODBC_DRIVER_11 = 1, @@ -1073,14 +1074,14 @@ struct sqlsrv_conn : public sqlsrv_context { SERVER_VERSION server_version; // version of the server that we're connected to col_encryption_option ce_option; // holds the details of what are required to enable column encryption - bool is_driver_set; + DRIVER_VERSION driver_version; // version of ODBC driver // initialize with default values sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) { server_version = SERVER_VERSION_UNKNOWN; - is_driver_set = false; + driver_version = ODBC_DRIVER_UNKNOWN; } // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be @@ -1235,7 +1236,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, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled ); +SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled ); 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 ); @@ -1247,7 +1248,8 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len ); size_t core_str_zval_is_true( _Inout_ zval* str_zval ); bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len ); -bool core_search_odbc_driver( _In_ std::size_t driver_version ); +bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ); +bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ const char* error_state ); //********************************************************************************************************************************* // Statement @@ -1640,7 +1642,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_ODBC, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_AE_DRIVER_REQUIRED, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, SQLSRV_ERROR_ZEND_HASH, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 47e45b93..0e527d0c 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -397,7 +397,7 @@ ss_error SS_ERRORS[] = { { IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -104, false} }, { - SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_AE_DRIVER_REQUIRED, { IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -105, false } }, {