diff --git a/appveyor.yml b/appveyor.yml index 5690bc5e..430d4db5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -114,9 +114,9 @@ install: - ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/releases/php-' + ${env:PHP_VERSION} + '-src.zip', ${env:APPVEYOR_BUILD_FOLDER} + '\..\php.zip') #- echo Downloading PHP deps [%PHP_DEPSVER%] #- ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/php-sdk/deps-' + ${env:PHP_DEPSVER} + '-vc' + ${env:PHP_VC} + '-' + ${env:BUILD_PLATFORM} + '.7z', ${env:APPVEYOR_BUILD_FOLDER} + '\..\deps.7z') - - echo Downloading MSODBCSQL 13 - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/1/E/7/1E7B1181-3974-4B29-9A47-CC857B271AA2/English/' + ${env:BUILD_PLATFORM} + '/msodbcsql.msi', 'msodbcsql.msi') - - ps: msiexec /i msodbcsql.msi /quiet /qn /norestart + - echo Downloading MSODBCSQL 13.1 + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/D/5/E/D5EEF288-A277-45C8-855B-8E2CB7E25B96/' + ${env:BUILD_PLATFORM} + '/msodbcsql.msi', 'msodbcsql.msi') + - ps: msiexec /i msodbcsql.msi /quiet /qn - cd .. - cd - 7z x -y php-sdk-binary-tools-20110915.zip -o%PHP_SDK% diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 3ec12cb7..1391cfb7 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -41,6 +41,7 @@ const char APP[] = "APP"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char ConnectionPooling[] = "ConnectionPooling"; +const char Authentication[] = "Authentication"; #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -200,6 +201,15 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, + { + PDOConnOptionNames::Authentication, + sizeof( PDOConnOptionNames::Authentication ), + SQLSRV_CONN_OPTION_AUTHENTICATION, + ODBCConnOptions::Authentication, + sizeof( ODBCConnOptions::Authentication ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, { PDOConnOptionNames::ConnectionPooling, sizeof( PDOConnOptionNames::ConnectionPooling ), diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 6a735018..9bc11845 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -139,7 +139,7 @@ void string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) void sql_string_parser::add_key_int_value_pair( unsigned int value TSRMLS_DC ) { zval value_z; ZVAL_LONG( &value_z, value ); - + core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z TSRMLS_CC ); } @@ -169,6 +169,31 @@ void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); } +void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) +{ + // if the keyword is 'Authentication', check whether the user specified option is supported + bool valid = true; + if ( stricmp( this->current_key_name, ODBCConnOptions::Authentication ) == 0 ) { + if (len <= 0) + valid = false; + else { + // extract option from the value by len + sqlsrv_malloc_auto_ptr option; + option = static_cast( sqlsrv_malloc( len + 1 ) ); + memcpy_s( option, len + 1, value, len ); + option[len] = '\0'; + + valid = core_is_authentication_option_valid( option, len ); + } + } + if( !valid ) { + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, this->current_key_name ); + } + + string_parser::add_key_value_pair( value, len ); +} + + inline bool sql_string_parser::is_placeholder_char( char c ) { // placeholder only accepts numbers, upper and lower case alphabets and underscore @@ -411,7 +436,7 @@ void sql_string_parser::parse_sql_string( TSRMLS_D ) { start_pos = this->pos; next(); // keep going until the next space or line break - // while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) { + // while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) { while ( is_placeholder_char( this->orig_str[pos] )) { next(); } diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 227cc83b..2a89538d 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -377,6 +377,10 @@ pdo_error PDO_ERRORS[] = { PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, { IMSSP, (SQLCHAR*) "Statement with emulate prepare on does not support output or input_output parameters.", -72, false } }, + { + PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, + { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false } + }, { UINT_MAX, {} } }; @@ -558,15 +562,15 @@ namespace { // Place get_error_message into the anonymous namespace in pdo_util.cpp sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { - sqlsrv_error_const *error_message = NULL; - int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; - if (zr == FAILURE) { - DIE("get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code); - } + sqlsrv_error_const *error_message = NULL; + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if (zr == FAILURE) { + DIE("get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code); + } - SQLSRV_ASSERT(error_message != NULL, "get_error_message: error_message was null"); + SQLSRV_ASSERT(error_message != NULL, "get_error_message: error_message was null"); - return error_message; + return error_message; } void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 1b5234f5..5519f07c 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -165,6 +165,9 @@ class conn_string_parser : private string_parser int discard_trailing_white_spaces(const char* str, int len); void validate_key(const char *key, int key_len TSRMLS_DC); + protected: + void add_key_value_pair(const char* value, int len TSRMLS_DC); + public: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); void parse_conn_string( TSRMLS_D ); @@ -390,6 +393,7 @@ enum PDO_ERROR_CODES { PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, + PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION }; extern pdo_error PDO_ERRORS[]; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 60040fd8..a459c16d 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -92,7 +92,7 @@ void common_conn_str_append_func( const char* odbc_name, const char* val, size_t sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, const char* server, const char* uid, const char* pwd, HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], - void* driver, const char* driver_func TSRMLS_DC ) + void* driver, const char* driver_func TSRMLS_DC ) { SQLRETURN r; @@ -112,7 +112,7 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ // Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider // the connection string attributes to set (enable/disable) connection pooling. // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. - + #ifndef _WIN32 char pooling_string[ 128 ] = {0}; SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); @@ -128,7 +128,7 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ // it in build_connection_string_and_set_conn_attr. if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - + zval* option_z = NULL; option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); if ( option_z ) { @@ -163,18 +163,18 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ 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 ); + // 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 ); + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), 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). @@ -215,11 +215,11 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ // but fails if the connection is using a pool, i.e. r= SQL_SUCCESS. // Thus, in Linux, we don't call determine_server_version() for a connection that uses pool. #ifndef _WIN32 - if ( r == SQL_SUCCESS_WITH_INFO ) { + if ( r == SQL_SUCCESS_WITH_INFO ) { #endif // !_WIN32 determine_server_version( conn TSRMLS_CC ); #ifndef _WIN32 - } + } #endif // !_WIN32 } catch( std::bad_alloc& ) { @@ -550,6 +550,20 @@ bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) return true; } +// core_is_authentication_option_valid +// if the option for the authentication is valid, returns true. This returns false otherwise. +bool core_is_authentication_option_valid(const char* value, size_t value_len) +{ + if (value_len <= 0) + return false; + + if( ! stricmp( value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD ) || ! stricmp( value, AzureADOptions::AZURE_AUTH_AD_PASSWORD ) ) { + return true; + } + + return false; +} + // *** internal connection functions and classes *** @@ -625,33 +639,33 @@ void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* s if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { zval* trace_value = NULL; - trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - if (trace_value == NULL || !zend_is_true(trace_value)) { + if (trace_value == NULL || !zend_is_true(trace_value)) { zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); } } - zend_string *key = NULL; - zend_ulong index = -1; - zval* data = NULL; + zend_string *key = NULL; + zend_ulong index = -1; + zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); // MARS on if not explicitly turned off if( !mars_mentioned ) { @@ -707,7 +721,7 @@ const char* get_processor_arch( void ) return PROCESSOR_ARCH[2]; } else { DIE( "Unknown processor architecture." ); - } + } return NULL; #else SYSTEM_INFO sys_info; @@ -727,7 +741,7 @@ const char* get_processor_arch( void ) DIE( "Unknown Windows processor architecture." ); return NULL; } - return NULL; + return NULL; #endif // !_WIN32 } @@ -747,7 +761,7 @@ void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) errno = 0; char version_major_str[ 3 ]; SERVER_VERSION version_major; - memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); + memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); version_major_str[ 2 ] = '\0'; version_major = static_cast( atoi( version_major_str )); @@ -817,7 +831,7 @@ size_t core_str_zval_is_true( zval* value_z ) } // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; + Z_STRLEN_P( value_z ) = val_len; const char VALID_TRUE_VALUE_1[] = "true"; const char VALID_TRUE_VALUE_2[] = "1"; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 52f84de1..dd3c3bbc 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -180,6 +180,11 @@ const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; +namespace AzureADOptions { + const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword"; + const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword"; +} + // types for conversions on output parameters (though they can be used for input parameters, they are ignored) enum SQLSRV_PHPTYPE { MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type @@ -1077,6 +1082,7 @@ namespace ODBCConnOptions { const char APP[] = "APP"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; +const char Authentication[] = "Authentication"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; #ifdef _WIN32 @@ -1121,6 +1127,7 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_ATTACHDBFILENAME, SQLSRV_CONN_OPTION_APPLICATION_INTENT, SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + SQLSRV_CONN_OPTION_AUTHENTICATION, #ifdef _WIN32 SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL, @@ -1190,6 +1197,7 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_versi void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); size_t core_str_zval_is_true( zval* str_zval ); +bool core_is_authentication_option_valid( const char* value, size_t value_len ); //********************************************************************************************************************************* // Statement diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index bfbef455..d0a12f26 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -90,7 +90,8 @@ #define SQL_COPT_SS_AEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11) /* Load a keystore provider or read the list of loaded keystore providers */ #define SQL_COPT_SS_AEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12) /* Communicate with a loaded keystore provider */ #define SQL_COPT_SS_AETRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13) /* List of trusted CMK paths */ -#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL +#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14) /* Symmetric Key Cache TTL */ +#define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15) /* The authentication method used for the connection */ /* * SQLColAttributes driver specific defines. diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index e0fe2661..69c9ce10 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -54,23 +54,23 @@ struct conn_char_set_func { const char* encoding = Z_STRVAL_P( value ); size_t encoding_len = Z_STRLEN_P( value ); - zend_ulong index = -1; - zend_string* key = NULL; - void* ss_encoding_temp = NULL; + zend_ulong index = -1; + zend_string* key = NULL; + void* ss_encoding_temp = NULL; - ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { - sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); - ss_encoding_temp = NULL; - if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { + sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); + ss_encoding_temp = NULL; + if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { - if ( ss_encoding->not_for_connection ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } + if ( ss_encoding->not_for_connection ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } - conn->set_encoding( static_cast(ss_encoding->code_page )); - return; - } - } ZEND_HASH_FOREACH_END(); + conn->set_encoding( static_cast(ss_encoding->code_page )); + return; + } + } ZEND_HASH_FOREACH_END(); THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); } @@ -134,8 +134,8 @@ struct bool_conn_attr_func { static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) { try { - core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), - SQL_IS_UINTEGER TSRMLS_CC); + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), + SQL_IS_UINTEGER TSRMLS_CC); } catch( core::CoreException& ) { @@ -186,6 +186,7 @@ const char APP[] = "APP"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char CharacterSet[] = "CharacterSet"; +const char Authentication[] = "Authentication"; const char ConnectionPooling[] = "ConnectionPooling"; #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; @@ -220,25 +221,25 @@ const stmt_option SS_STMT_OPTS[] = { SSStmtOptionNames::QUERY_TIMEOUT, sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - std::unique_ptr( new stmt_option_query_timeout ) + std::unique_ptr( new stmt_option_query_timeout ) }, { SSStmtOptionNames::SEND_STREAMS_AT_EXEC, sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - std::unique_ptr( new stmt_option_send_at_exec ) + std::unique_ptr( new stmt_option_send_at_exec ) }, { SSStmtOptionNames::SCROLLABLE, sizeof( SSStmtOptionNames::SCROLLABLE ), SQLSRV_STMT_OPTION_SCROLLABLE, - std::unique_ptr( new stmt_option_ss_scrollable ) + std::unique_ptr( new stmt_option_ss_scrollable ) }, { SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - std::unique_ptr( new stmt_option_buffered_query_limit ) + std::unique_ptr( new stmt_option_buffered_query_limit ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -282,6 +283,15 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, conn_char_set_func::func }, + { + SSConnOptionNames::Authentication, + sizeof( SSConnOptionNames::Authentication ), + SQLSRV_CONN_OPTION_AUTHENTICATION, + ODBCConnOptions::Authentication, + sizeof( ODBCConnOptions::Authentication ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, { SSConnOptionNames::ConnectionPooling, sizeof( SSConnOptionNames::ConnectionPooling ), @@ -503,11 +513,11 @@ PHP_FUNCTION ( sqlsrv_connect ) // register the connection with the PHP runtime - ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); + ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); conn->stmts = stmts; stmts.transferred(); - RETURN_RES( Z_RES(conn_z) ); + RETURN_RES( Z_RES(conn_z) ); } catch( core::CoreException& ) { @@ -607,7 +617,7 @@ PHP_FUNCTION( sqlsrv_close ) try { - // dummy context to pass to the error handler + // dummy context to pass to the error handler error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); SET_FUNCTION_NAME( *error_ctx ); @@ -618,7 +628,7 @@ PHP_FUNCTION( sqlsrv_close ) CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); } - + // if sqlsrv_close was called on a non-existent connection then we just return success. if( Z_TYPE_P( conn_r ) == IS_NULL ) { RETURN_TRUE; @@ -630,31 +640,31 @@ PHP_FUNCTION( sqlsrv_close ) conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); - // if sqlsrv_close was called on an already closed connection then we just return success. - if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { - RETURN_TRUE; - } - - CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - + // if sqlsrv_close was called on an already closed connection then we just return success. + if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { + RETURN_TRUE; + } + + CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); } SET_FUNCTION_NAME( *conn ); - + // cause any variables still holding a reference to this to be invalid so they cause // an error when passed to a sqlsrv function. There's nothing we can do if the // removal fails, so we just log it and move on. if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); } - - ZVAL_NULL( conn_r ); + + ZVAL_NULL( conn_r ); RETURN_TRUE; } catch( core::CoreException& ) { - + RETURN_FALSE; } catch( ... ) { @@ -937,10 +947,10 @@ PHP_FUNCTION( sqlsrv_prepare ) core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); - if (params_z) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); - } + if (params_z) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } stmt->prepared = true; @@ -1021,7 +1031,7 @@ PHP_FUNCTION( sqlsrv_query ) hash_auto_ptr ss_stmt_options_ht; size_t sql_len = 0; zval* options_z = NULL; - zval* params_z = NULL; + zval* params_z = NULL; zval stmt_z; ZVAL_UNDEF(&stmt_z); @@ -1058,8 +1068,8 @@ PHP_FUNCTION( sqlsrv_query ) ss_error_handler, NULL TSRMLS_CC ) ); if( params_z ) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); } stmt->set_func( "sqlsrv_query" ); @@ -1073,7 +1083,7 @@ PHP_FUNCTION( sqlsrv_query ) ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); // store the resource id with the connection so the connection // can release this statement when it closes. - zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); + zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); stmt->conn_index = next_index; @@ -1103,7 +1113,7 @@ PHP_FUNCTION( sqlsrv_query ) void free_stmt_resource( zval* stmt_z TSRMLS_DC ) { - if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { + if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); } ZVAL_NULL( stmt_z ); @@ -1127,31 +1137,31 @@ void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) // loop through the stmts hash table and destroy each stmt resource so we can close the // ODBC connection - zval* rsrc_ptr = NULL; - ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { - try { - int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { - throw core::CoreException(); - } - } - catch( core::CoreException& ) { - DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); - } - // see if the statement is still valid, and if not skip to the next one - // presumably this should never happen because if it's in the list, it should still be valid - // by virtue that a statement resource should remove itself from its connection when it is - // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource - // and move to the next one. - ss_sqlsrv_stmt* stmt = NULL; - stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); - if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { - LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" - " from the connection so this shouldn't be out of sync." ); - continue; - } - // delete the statement by deleting it from Zend's resource list, which will force its destruction - stmt->conn = NULL; + zval* rsrc_ptr = NULL; + ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { + try { + int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { + throw core::CoreException(); + } + } + catch( core::CoreException& ) { + DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); + } + // see if the statement is still valid, and if not skip to the next one + // presumably this should never happen because if it's in the list, it should still be valid + // by virtue that a statement resource should remove itself from its connection when it is + // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource + // and move to the next one. + ss_sqlsrv_stmt* stmt = NULL; + stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); + if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { + LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" + " from the connection so this shouldn't be out of sync." ); + continue; + } + // delete the statement by deleting it from Zend's resource list, which will force its destruction + stmt->conn = NULL; // this would call the destructor on the statement. // There's nothing we can do if the removal fails, so we just log it and move on. @@ -1205,6 +1215,17 @@ int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, throw ss::SSException(); } + + bool valid = true; + if( stricmp( SS_CONN_OPTS[i].sqlsrv_name, SSConnOptionNames::Authentication ) == 0 ) { + valid = core_is_authentication_option_valid( value, value_len ); + } + + CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[ i ].sqlsrv_name ) { + + throw ss::SSException(); + } + break; } } @@ -1263,23 +1284,23 @@ void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ Has if( stmt_options ) { HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - size_t key_len = 0; + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + size_t key_len = 0; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - if (type != HASH_KEY_IS_STRING) { - CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { - throw core::CoreException(); - } - } - key_len = ZSTR_LEN(key) + 1; - add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); + if (type != HASH_KEY_IS_STRING) { + CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { + throw core::CoreException(); + } + } + key_len = ZSTR_LEN(key) + 1; + add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } } catch( core::CoreException& ) { @@ -1299,37 +1320,37 @@ void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ cha if( user_options_z ) { HashTable* options_ht = Z_ARRVAL_P( user_options_z ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { - throw ss::SSException(); - } + CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { + throw ss::SSException(); + } - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { - throw ss::SSException(); - } + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { + throw ss::SSException(); + } - // Length of the key string does not include the null terminator in PHP7, +1 has to be added - size_t key_len = ZSTR_LEN(key) + 1; - if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { + // Length of the key string does not include the null terminator in PHP7, +1 has to be added + size_t key_len = ZSTR_LEN(key) + 1; + if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { - *uid = Z_STRVAL_P( data ); - } + *uid = Z_STRVAL_P( data ); + } - else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { + else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { - *pwd = Z_STRVAL_P( data ); - } - else { + *pwd = Z_STRVAL_P( data ); + } + else { - ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); - } - } ZEND_HASH_FOREACH_END(); + ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); + } + } ZEND_HASH_FOREACH_END(); } } catch( core::CoreException& ) { diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 828a6356..e7e46c4d 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -352,7 +352,8 @@ enum SS_ERROR_CODES { SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, + SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION }; extern ss_error SS_ERRORS[]; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 2c73da9a..cef2b1d9 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -366,6 +366,10 @@ ss_error SS_ERRORS[] = { "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." , -61, true } }, + { + SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, + { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -62, false } + }, // internal warning definitions { diff --git a/test/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/pdo_sqlsrv/pdo_azure_ad_authentication.phpt new file mode 100644 index 00000000..ad96d50e --- /dev/null +++ b/test/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test the Authentication keyword with options SqlPassword and ActiveDirectoryIntegrated. +--SKIPIF-- + +--FILE-- +getMessage() ); + echo "\n"; +} + +$stmt = $conn->query( "SELECT name FROM master.dbo.sysdatabases" ); +if ( $stmt === false ) +{ + echo "Query failed.\n"; +} +else +{ + $first_db = $stmt->fetch(); + var_dump( $first_db ); +} + +$conn = null; + +//////////////////////////////////////// + +$connectionInfo = "Authentication = ActiveDirectoryIntegrated; TrustServerCertificate = true;"; + +try +{ + $conn = new PDO( "sqlsrv:server = $serverName ; $connectionInfo" ); + echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; + $conn = null; +} +catch( PDOException $e ) +{ + echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; + print_r( $e->getMessage() ); + echo "\n"; +} + +?> +--EXPECT-- +Connected successfully with Authentication=SqlPassword. +array(2) { + ["name"]=> + string(6) "master" + [0]=> + string(6) "master" +} +Could not connect with Authentication=ActiveDirectoryIntegrated. +SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. \ No newline at end of file diff --git a/test/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/sqlsrv/sqlsrv_azure_ad_authentication.phpt new file mode 100644 index 00000000..af22278a --- /dev/null +++ b/test/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test the Authentication keyword with options SqlPassword and ActiveDirectoryIntegrated. +--SKIPIF-- + +--FILE-- +$username, "PWD"=>$password, + "Authentication"=>"SqlPassword", "TrustServerCertificate"=>true ); + + $conn = sqlsrv_connect( $serverName, $connectionInfo ); + + if( $conn === false ) + { + echo "Could not connect with Authentication=SqlPassword.\n"; + print_r( sqlsrv_errors() ); + } + // else + // { + // echo "Connected successfully with Authentication=SqlPassword.\n"; + // } + + $stmt = sqlsrv_query( $conn, "SELECT name FROM master.dbo.sysdatabases" ); + if ( $stmt === false ) + { + echo "Query failed.\n"; + } + // else + // { + // $first_db = sqlsrv_fetch_array( $stmt ); + // var_dump( $first_db ); + // } + + sqlsrv_free_stmt( $stmt ); + sqlsrv_close( $conn ); +} + +//////////////////////////////////////// + +$connectionInfo = array( "Authentication"=>"ActiveDirectoryIntegrated", "TrustServerCertificate"=>true ); + +$conn = sqlsrv_connect( $serverName, $connectionInfo ); +if( $conn === false ) +{ + echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; + print_r( sqlsrv_errors() ); +} +else +{ + echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; + sqlsrv_close( $conn ); +} + +?> +--EXPECT-- +Could not connect with Authentication=ActiveDirectoryIntegrated. +Array +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -62 + [code] => -62 + [2] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. + [message] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. + ) + +)