2016-04-12 23:43:46 +02:00
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_conn.cpp
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
2019-01-16 19:19:01 +01:00
// Microsoft Drivers 5.6 for PHP for SQL Server
2016-04-12 23:43:46 +02:00
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
2018-05-18 23:05:18 +02:00
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
2016-04-12 23:43:46 +02:00
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2018-05-18 23:05:18 +02:00
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
2016-04-12 23:43:46 +02:00
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
# include "core_sqlsrv.h"
# include <php.h>
2017-01-19 22:00:41 +01:00
# ifdef _WIN32
2016-04-12 23:43:46 +02:00
# include <psapi.h>
# include <windows.h>
# include <winver.h>
2017-01-27 02:06:17 +01:00
# endif // _WIN32
2016-04-12 23:43:46 +02:00
# include <sstream>
2017-09-09 01:47:43 +02:00
# include <vector>
2016-04-12 23:43:46 +02:00
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2017-01-19 22:00:41 +01:00
# include <sys/utsname.h>
2017-09-23 01:58:32 +02:00
# include <odbcinst.h>
2017-09-27 01:38:34 +02:00
# endif
2017-01-19 22:00:41 +01:00
2016-04-12 23:43:46 +02:00
// *** internal variables and constants ***
namespace {
// *** internal constants ***
// an arbitrary figure that should be large enough for most connection strings.
const int DEFAULT_CONN_STR_LEN = 2048 ;
// length of buffer used to retrieve information for client and server info buffers
const int INFO_BUFFER_LEN = 256 ;
2018-05-18 01:24:40 +02:00
// length for name of keystore used in CEKeyStoreData
const int MAX_CE_NAME_LEN = 260 ;
2016-04-12 23:43:46 +02:00
// processor architectures
const char * PROCESSOR_ARCH [ ] = { " x86 " , " x64 " , " ia64 " } ;
2017-09-14 18:34:48 +02:00
// ODBC driver names.
// the order of this list should match the order of DRIVER_VERSION enum
2018-01-03 00:26:33 +01:00
std : : vector < std : : string > 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}; " } ;
2016-04-12 23:43:46 +02:00
// default options if only the server is specified
2017-09-13 04:21:55 +02:00
const char CONNECTION_STRING_DEFAULT_OPTIONS [ ] = " Mars_Connection={Yes}; " ;
2016-04-12 23:43:46 +02:00
// connection option appended when no user name or password is given
const char CONNECTION_OPTION_NO_CREDENTIALS [ ] = " Trusted_Connection={Yes}; " ;
// connection option appended for MARS when MARS isn't explicitly mentioned
const char CONNECTION_OPTION_MARS_ON [ ] = " MARS_Connection={Yes}; " ;
// *** internal function prototypes ***
2018-05-18 23:05:18 +02:00
void build_connection_string_and_set_conn_attr ( _Inout_ sqlsrv_conn * conn , _Inout_z_ const char * server , _Inout_opt_z_ const char * uid , _Inout_opt_z_ const char * pwd ,
_Inout_opt_ HashTable * options_ht , _In_ const connection_option valid_conn_opts [ ] ,
2017-09-11 20:43:52 +02:00
void * driver , _Inout_ std : : string & connection_string TSRMLS_DC ) ;
2017-06-22 23:04:34 +02:00
void determine_server_version ( _Inout_ sqlsrv_conn * conn TSRMLS_DC ) ;
2016-04-12 23:43:46 +02:00
const char * get_processor_arch ( void ) ;
2017-06-22 23:04:34 +02:00
void get_server_version ( _Inout_ sqlsrv_conn * conn , _Outptr_result_buffer_ ( len ) char * * server_version , _Out_ SQLSMALLINT & len TSRMLS_DC ) ;
connection_option const * get_connection_option ( sqlsrv_conn * conn , _In_ const char * key , _In_ SQLULEN key_len TSRMLS_DC ) ;
void common_conn_str_append_func ( _In_z_ const char * odbc_name , _In_reads_ ( val_len ) const char * val , _Inout_ size_t val_len , _Inout_ std : : string & conn_str TSRMLS_DC ) ;
2018-05-06 02:08:01 +02:00
void load_azure_key_vault ( _Inout_ sqlsrv_conn * conn TSRMLS_DC ) ;
void configure_azure_key_vault ( sqlsrv_conn * conn , BYTE config_attr , const DWORD config_value , size_t key_size ) ;
void configure_azure_key_vault ( sqlsrv_conn * conn , BYTE config_attr , const char * config_value , size_t key_size ) ;
2016-04-12 23:43:46 +02:00
}
// core_sqlsrv_connect
// opens a connection and returns a sqlsrv_conn structure.
// Parameters:
// henv_cp - connection pooled env context
// henv_ncp - non connection pooled env context
// server - name of the server we're connecting to
2018-05-18 23:05:18 +02:00
// uid - username
2016-04-12 23:43:46 +02:00
// pwd - password
// options_ht - zend_hash list of options
// err - error callback to put into the connection's context
// valid_conn_opts[] - array of valid driver supported connection options.
// driver - reference to caller
// Return
// A sqlsrv_conn structure. An exception is thrown if an error occurs
2017-06-22 23:04:34 +02:00
sqlsrv_conn * core_sqlsrv_connect ( _In_ sqlsrv_context & henv_cp , _In_ sqlsrv_context & henv_ncp , _In_ driver_conn_factory conn_factory ,
2018-05-18 23:05:18 +02:00
_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 [ ] ,
2017-06-22 23:04:34 +02:00
_In_ void * driver , _In_z_ const char * driver_func TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
SQLRETURN r ;
2017-09-09 02:15:31 +02:00
std : : string conn_str ;
conn_str . reserve ( DEFAULT_CONN_STR_LEN ) ;
2016-04-12 23:43:46 +02:00
sqlsrv_malloc_auto_ptr < sqlsrv_conn > conn ;
2017-09-11 20:43:52 +02:00
bool is_pooled = false ;
2016-04-12 23:43:46 +02:00
2017-02-02 02:36:41 +01:00
# ifdef _WIN32
2017-02-01 03:10:37 +01:00
sqlsrv_context * henv = & henv_cp ; // by default use the connection pooling henv
2017-09-11 20:43:52 +02:00
is_pooled = true ;
2017-02-01 03:10:37 +01:00
# else
sqlsrv_context * henv = & henv_ncp ; // by default do not use the connection pooling henv
2017-09-11 20:43:52 +02:00
is_pooled = false ;
2018-05-18 23:05:18 +02:00
# endif // _WIN32
2016-04-12 23:43:46 +02:00
2017-02-01 03:10:37 +01:00
try {
2018-05-18 23:05:18 +02:00
// 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.
2017-09-30 00:54:34 +02:00
// Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section.
2018-05-18 23:05:18 +02:00
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2018-08-01 02:22:56 +02:00
char pooling_string [ 128 ] = { ' \0 ' } ;
2017-02-02 01:55:05 +01:00
SQLGetPrivateProfileString ( " ODBC " , " Pooling " , " 0 " , pooling_string , sizeof ( pooling_string ) , " ODBCINST.INI " ) ;
2018-08-01 02:22:56 +02:00
if ( pooling_string [ 0 ] = = ' 1 ' | | toupper ( pooling_string [ 0 ] ) = = ' Y ' | |
( toupper ( pooling_string [ 0 ] ) = = ' O ' & & toupper ( pooling_string [ 1 ] ) = = ' N ' ) )
2017-02-02 01:55:05 +01:00
{
henv = & henv_cp ;
2017-09-11 20:43:52 +02:00
is_pooled = true ;
2017-02-02 01:55:05 +01:00
}
2017-02-01 03:10:37 +01:00
# else
2017-09-30 00:54:34 +02:00
// check the connection pooling setting to determine which henv to use to allocate the connection handle
// we do this earlier because we have to allocate the connection handle prior to setting attributes on
// it in build_connection_string_and_set_conn_attr.
2018-05-18 23:05:18 +02:00
2018-05-06 02:08:01 +02:00
if ( options_ht & & zend_hash_num_elements ( options_ht ) > 0 ) {
2018-05-18 23:05:18 +02:00
zval * option_z = NULL ;
2018-05-06 02:08:01 +02:00
option_z = zend_hash_index_find ( options_ht , SQLSRV_CONN_OPTION_CONN_POOLING ) ;
if ( option_z ) {
// if the option was found and it's not true, then use the non pooled environment handle
2018-05-18 23:05:18 +02:00
if ( ( Z_TYPE_P ( option_z ) = = IS_STRING & & ! core_str_zval_is_true ( option_z ) ) | | ! zend_is_true ( option_z ) ) {
2017-09-11 20:43:52 +02:00
henv = & henv_ncp ;
is_pooled = false ;
2016-04-12 23:43:46 +02:00
}
}
}
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2016-04-12 23:43:46 +02:00
SQLHANDLE temp_conn_h ;
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 ) ;
2018-05-18 23:05:18 +02:00
2017-09-12 01:17:24 +02:00
build_connection_string_and_set_conn_attr ( conn , server , uid , pwd , options_ht , valid_conn_opts , driver , conn_str TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
2017-10-03 01:13:32 +02:00
// If column encryption is enabled, must use ODBC driver 17
if ( conn - > ce_option . enabled & & conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
CHECK_CUSTOM_ERROR ( conn - > driver_version ! = ODBC_DRIVER_17 , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
throw core : : CoreException ( ) ;
}
}
2017-09-28 22:22:00 +02:00
2017-10-03 01:13:32 +02:00
// In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states when an ODBC driver exists or not
2018-05-18 23:05:18 +02:00
// Therefore, it is unreliable to check for a certain sql state error
2017-10-03 01:13:32 +02:00
# ifndef _WIN32
if ( conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
// check if the ODBC driver actually exists, if not, throw an exception
CHECK_CUSTOM_ERROR ( ! core_search_odbc_driver_unix ( conn - > driver_version ) , conn , SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core : : CoreException ( ) ;
2017-09-22 21:55:38 +02:00
}
2017-10-03 01:13:32 +02:00
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-02-01 03:10:37 +01:00
}
2017-10-03 01:13:32 +02:00
else {
if ( conn - > ce_option . enabled ) {
2018-05-18 23:05:18 +02:00
// driver not specified, so check if ODBC 17 exists
2017-10-03 01:13:32 +02:00
CHECK_CUSTOM_ERROR ( ! core_search_odbc_driver_unix ( ODBC_DRIVER_17 ) , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
2017-09-28 22:22:00 +02:00
throw core : : CoreException ( ) ;
}
2017-10-03 01:13:32 +02:00
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME [ ODBC_DRIVER_17 ] ;
2017-09-27 01:38:34 +02:00
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
}
else {
2017-09-30 00:54:34 +02:00
// 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
2017-09-27 01:38:34 +02:00
DRIVER_VERSION odbc_version = ODBC_DRIVER_UNKNOWN ;
2018-01-03 00:26:33 +01:00
if ( core_search_odbc_driver_unix ( ODBC_DRIVER_17 ) ) {
2017-09-30 00:54:34 +02:00
odbc_version = ODBC_DRIVER_17 ;
2018-05-18 23:05:18 +02:00
}
2018-01-03 00:26:33 +01:00
else if ( core_search_odbc_driver_unix ( ODBC_DRIVER_13 ) ) {
odbc_version = ODBC_DRIVER_13 ;
2017-09-22 21:55:38 +02:00
}
2017-09-30 00:54:34 +02:00
CHECK_CUSTOM_ERROR ( odbc_version = = ODBC_DRIVER_UNKNOWN , conn , SQLSRV_ERROR_DRIVER_NOT_INSTALLED , get_processor_arch ( ) ) {
2017-09-25 22:37:42 +02:00
throw core : : CoreException ( ) ;
}
2017-09-30 00:54:34 +02:00
std : : string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME [ odbc_version ] ;
2017-09-27 01:38:34 +02:00
r = core_odbc_connect ( conn , conn_str_driver , is_pooled ) ;
2017-10-03 01:13:32 +02:00
} // else ce_option enabled
} // else driver_version not unknown
2017-09-27 01:38:34 +02:00
# else
2017-10-03 01:13:32 +02:00
if ( conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-09-28 22:22:00 +02:00
2017-10-03 21:42:19 +02:00
// check if the specified ODBC driver is there
2017-10-03 01:13:32 +02:00
CHECK_CUSTOM_ERROR ( core_compare_error_state ( conn , r , " IM002 " ) , conn , SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core : : CoreException ( ) ;
2017-09-28 22:22:00 +02:00
}
2017-10-03 01:13:32 +02:00
}
else {
if ( conn - > ce_option . enabled ) {
2017-09-28 22:22:00 +02:00
// driver not specified, so connect using ODBC 17
2017-09-30 00:54:34 +02:00
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME [ ODBC_DRIVER_17 ] ;
2017-09-28 22:22:00 +02:00
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-10-03 21:42:19 +02:00
// check if the specified ODBC driver is there
2017-09-30 00:54:34 +02:00
CHECK_CUSTOM_ERROR ( core_compare_error_state ( conn , r , " IM002 " ) , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
throw core : : CoreException ( ) ;
2017-09-28 22:22:00 +02:00
}
}
else {
2017-09-30 00:54:34 +02:00
bool done = false ;
for ( short i = DRIVER_VERSION : : FIRST ; i < = DRIVER_VERSION : : LAST & & ! done ; + + i ) {
std : : string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME [ i ] ;
2017-09-27 01:38:34 +02:00
r = core_odbc_connect ( conn , conn_str_driver , is_pooled ) ;
2018-05-18 23:05:18 +02:00
if ( SQL_SUCCEEDED ( r ) | | ! core_compare_error_state ( conn , r , " IM002 " ) ) {
2017-10-03 21:42:19 +02:00
// something else went wrong, exit the loop now other than ODBC driver not found
2017-09-30 00:54:34 +02:00
done = true ;
2017-09-27 01:38:34 +02:00
}
2017-09-30 00:54:34 +02:00
else {
2017-10-03 01:13:32 +02:00
// did it fail to find the last valid ODBC driver?
CHECK_CUSTOM_ERROR ( ( i = = DRIVER_VERSION : : LAST ) , conn , SQLSRV_ERROR_DRIVER_NOT_INSTALLED , get_processor_arch ( ) ) {
throw core : : CoreException ( ) ;
}
2017-09-30 00:54:34 +02:00
}
2018-05-18 23:05:18 +02:00
} // for
2017-10-03 01:13:32 +02:00
} // else ce_option enabled
} // else driver_version not unknown
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2017-09-28 22:22:00 +02:00
2018-09-06 20:32:04 +02:00
// time to free the access token, if not null
2018-12-03 21:08:21 +01:00
if ( conn - > azure_ad_access_token ) {
2018-09-06 20:32:04 +02:00
memset ( conn - > azure_ad_access_token - > data , 0 , conn - > azure_ad_access_token - > dataSize ) ; // clear the memory
conn - > azure_ad_access_token . reset ( ) ;
}
2017-08-29 01:34:44 +02:00
CHECK_SQL_ERROR ( r , conn ) {
throw core : : CoreException ( ) ;
}
2017-01-27 03:19:08 +01:00
2017-08-29 01:34:44 +02:00
CHECK_SQL_WARNING_AS_ERROR ( r , conn ) {
throw core : : CoreException ( ) ;
}
2017-01-28 00:27:44 +01:00
2018-09-26 23:51:16 +02:00
// After load_azure_key_vault, reset AKV related variables regardless
load_azure_key_vault ( conn ) ;
conn - > ce_option . akv_reset ( ) ;
2018-05-06 02:08:01 +02:00
2018-05-18 23:05:18 +02:00
// determine the version of the server we're connected to. The server version is left in the
2017-08-29 01:34:44 +02:00
// connection upon return.
2017-02-01 03:10:37 +01:00
//
// unixODBC 2.3.1:
// SQLGetInfo works when r = SQL_SUCCESS_WITH_INFO (non-pooled connection)
// 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.
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2017-05-06 01:39:51 +02:00
if ( r = = SQL_SUCCESS_WITH_INFO ) {
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2017-02-01 03:10:37 +01:00
determine_server_version ( conn TSRMLS_CC ) ;
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2017-05-06 01:39:51 +02:00
}
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2016-04-12 23:43:46 +02:00
}
catch ( std : : bad_alloc & ) {
2017-09-14 04:06:28 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
conn - > invalidate ( ) ;
DIE ( " C++ memory allocation failure building the connection string. " ) ;
}
catch ( std : : out_of_range const & ex ) {
2017-09-14 04:06:28 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
LOG ( SEV_ERROR , " C++ exception returned: %1!s! " , ex . what ( ) ) ;
conn - > invalidate ( ) ;
throw ;
}
catch ( std : : length_error const & ex ) {
2017-09-14 04:06:28 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
LOG ( SEV_ERROR , " C++ exception returned: %1!s! " , ex . what ( ) ) ;
conn - > invalidate ( ) ;
throw ;
}
catch ( core : : CoreException & ) {
2018-09-26 23:51:16 +02:00
conn - > ce_option . akv_reset ( ) ;
2017-09-14 04:06:28 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
conn - > invalidate ( ) ;
2018-05-18 23:05:18 +02:00
throw ;
2016-04-12 23:43:46 +02:00
}
2017-09-09 02:15:31 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
sqlsrv_conn * return_conn = conn ;
conn . transferred ( ) ;
return return_conn ;
}
2017-09-27 01:38:34 +02:00
// core_compare_error_state
// This method compares the error state to the one specified
// Parameters:
2018-05-18 23:05:18 +02:00
// conn - the connection structure on which we establish the connection
2017-09-27 01:38:34 +02:00
// 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 )
{
2018-05-18 23:05:18 +02:00
if ( SQL_SUCCEEDED ( rc ) )
2017-09-27 01:38:34 +02:00
return false ;
2018-08-01 02:22:56 +02:00
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] = { ' \0 ' } ;
2017-09-27 01:38:34 +02:00
SQLSMALLINT len ;
SQLRETURN sr = SQLGetDiagField ( SQL_HANDLE_DBC , conn - > handle ( ) , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
2017-09-30 00:54:34 +02:00
return ( SQL_SUCCEEDED ( sr ) & & ! strcmp ( error_state , reinterpret_cast < char * > ( state ) ) ) ;
2017-09-27 01:38:34 +02:00
}
// core_search_odbc_driver_unix
2018-05-18 23:05:18 +02:00
// This method is meant to be used in a non-Windows environment,
2017-09-23 01:37:05 +02:00
// 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
2017-09-27 01:38:34 +02:00
bool core_search_odbc_driver_unix ( _In_ DRIVER_VERSION driver_version )
2017-09-22 21:55:38 +02:00
{
2017-12-13 22:33:32 +01:00
# ifndef _WIN32
2018-07-31 23:58:21 +02:00
char szBuf [ DEFAULT_CONN_STR_LEN + 1 ] = { ' \0 ' } ; // use a large enough buffer size
2018-05-18 23:05:18 +02:00
WORD cbBufMax = DEFAULT_CONN_STR_LEN ;
2017-09-22 21:55:38 +02:00
WORD cbBufOut ;
char * pszBuf = szBuf ;
2017-09-23 01:37:05 +02:00
// get all the names of the installed drivers delimited by null characters
2017-09-22 21:55:38 +02:00
if ( ! SQLGetInstalledDrivers ( szBuf , cbBufMax , & cbBufOut ) )
{
return false ;
}
2018-05-18 23:05:18 +02:00
// extract the ODBC driver name
2017-09-30 00:54:34 +02:00
std : : string driver = CONNECTION_STRING_DRIVER_NAME [ driver_version ] ;
2017-09-22 21:55:38 +02:00
std : : size_t pos1 = driver . find_first_of ( " { " ) ;
std : : size_t pos2 = driver . find_first_of ( " } " ) ;
std : : string driver_str = driver . substr ( pos1 + 1 , pos2 - pos1 - 1 ) ;
2017-09-14 04:06:28 +02:00
2017-09-23 01:37:05 +02:00
// search for the ODBC driver...
2017-09-22 21:55:38 +02:00
const char * driver_name = driver_str . c_str ( ) ;
do
{
if ( strstr ( pszBuf , driver_name ) ! = 0 )
{
2017-09-30 00:54:34 +02:00
return true ;
2017-09-22 21:55:38 +02:00
}
2017-09-23 01:37:05 +02:00
// get the next driver
2017-09-22 21:55:38 +02:00
pszBuf = strchr ( pszBuf , ' \0 ' ) + 1 ;
}
2017-09-23 01:37:05 +02:00
while ( pszBuf [ 1 ] ! = ' \0 ' ) ; // end when there are two consecutive null characters
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2017-09-22 21:55:38 +02:00
2017-09-30 00:54:34 +02:00
return false ;
2017-09-22 21:55:38 +02:00
}
2017-09-23 01:37:05 +02:00
2017-09-14 04:06:28 +02:00
// core_odbc_connect
// calls odbc connect API to establish the connection to server
// Parameters:
2018-05-18 23:05:18 +02:00
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// is_pooled - indicate whether it is a pooled connection
2017-09-15 03:21:25 +02:00
// Return - SQLRETURN status returned by SQLDriverConnect
2017-09-14 04:06:28 +02:00
2017-09-27 01:38:34 +02:00
SQLRETURN core_odbc_connect ( _Inout_ sqlsrv_conn * conn , _Inout_ std : : string & conn_str , _In_ bool is_pooled )
2017-09-06 19:55:08 +02:00
{
SQLRETURN r = SQL_SUCCESS ;
2017-09-13 18:32:40 +02:00
sqlsrv_malloc_auto_ptr < SQLWCHAR > wconn_string ;
2017-09-15 03:21:25 +02:00
unsigned int wconn_len = static_cast < unsigned int > ( conn_str . length ( ) + 1 ) * sizeof ( SQLWCHAR ) ;
2017-09-06 19:55:08 +02:00
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
2017-09-15 03:21:25 +02:00
wconn_string = utf16_string_from_mbcs_string ( SQLSRV_ENCODING_UTF8 , conn_str . c_str ( ) , static_cast < unsigned int > ( conn_str . length ( ) ) , & wconn_len ) ;
2017-09-06 19:55:08 +02:00
2017-09-12 01:17:24 +02:00
CHECK_CUSTOM_ERROR ( wconn_string = = 0 , conn , SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE , get_last_error_message ( ) )
2017-09-06 19:55:08 +02:00
{
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
2017-09-30 00:54:34 +02:00
if ( is_pooled ) {
2017-09-15 03:21:25 +02:00
r = SQLDriverConnect ( conn - > handle ( ) , NULL , ( SQLCHAR * ) conn_str . c_str ( ) , SQL_NTS , NULL , 0 , & output_conn_size , SQL_DRIVER_NOPROMPT ) ;
2017-09-06 19:55:08 +02:00
}
2017-09-30 00:54:34 +02:00
else {
2017-09-15 03:21:25 +02:00
r = SQLDriverConnectW ( conn - > handle ( ) , NULL , wconn_string , static_cast < SQLSMALLINT > ( wconn_len ) , NULL , 0 , & output_conn_size , SQL_DRIVER_NOPROMPT ) ;
2017-09-06 19:55:08 +02:00
}
# else
2017-09-15 03:21:25 +02:00
r = SQLDriverConnectW ( conn - > handle ( ) , NULL , wconn_string , static_cast < SQLSMALLINT > ( wconn_len ) , NULL , 0 , & output_conn_size , SQL_DRIVER_NOPROMPT ) ;
2018-05-18 23:05:18 +02:00
# endif // !_WIN32
2017-09-06 19:55:08 +02:00
2018-05-18 23:05:18 +02:00
// clear the connection string from memory
2017-09-12 01:17:24 +02:00
memset ( wconn_string , 0 , wconn_len * sizeof ( SQLWCHAR ) ) ; // wconn_len is the number of characters, not bytes
2017-09-06 19:55:08 +02:00
conn_str . clear ( ) ;
return r ;
}
2016-04-12 23:43:46 +02:00
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
2018-05-18 23:05:18 +02:00
// the call to core_sqlsrv_begin_transaction and before any calls to
2016-04-12 23:43:46 +02:00
// core_sqlsrv_rollback or core_sqlsrv_commit.
// The default transaction mode is auto-commit. This means that all queries
// are automatically committed upon success unless they have been designated
// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
2018-05-18 23:05:18 +02:00
// Parameters:
2016-04-12 23:43:46 +02:00
// sqlsrv_conn*: The connection with which the transaction is associated.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_begin_transaction ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
DEBUG_SQLSRV_ASSERT ( conn ! = NULL , " core_sqlsrv_begin_transaction: connection object was null. " ) ;
2018-05-18 23:05:18 +02:00
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_OFF ) ,
2016-04-12 23:43:46 +02:00
SQL_IS_UINTEGER TSRMLS_CC ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_commit
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
2018-05-18 23:05:18 +02:00
// core_sqlsrv_commit.
2016-04-12 23:43:46 +02:00
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_commit ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
DEBUG_SQLSRV_ASSERT ( conn ! = NULL , " core_sqlsrv_commit: connection object was null. " ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
core : : SQLEndTran ( SQL_HANDLE_DBC , conn , SQL_COMMIT TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_ON ) ,
2016-04-12 23:43:46 +02:00
SQL_IS_UINTEGER TSRMLS_CC ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_rollback
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
// core_sqlsrv_commit.
// Parameters:
// sqlsrv_conn*: The connection on which the transaction is active.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_rollback ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
DEBUG_SQLSRV_ASSERT ( conn ! = NULL , " core_sqlsrv_rollback: connection object was null. " ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
core : : SQLEndTran ( SQL_HANDLE_DBC , conn , SQL_ROLLBACK TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_ON ) ,
2016-04-12 23:43:46 +02:00
SQL_IS_UINTEGER TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_close
2018-05-18 23:05:18 +02:00
// Called when a connection resource is destroyed by the Zend engine.
2016-04-12 23:43:46 +02:00
// Parameters:
// conn - The current active connection.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_close ( _Inout_opt_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
// if the connection wasn't successful, just return.
if ( conn = = NULL )
return ;
try {
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
// rollback any transaction in progress (we don't care about the return result)
core : : SQLEndTran ( SQL_HANDLE_DBC , conn , SQL_ROLLBACK TSRMLS_CC ) ;
}
catch ( core : : CoreException & ) {
LOG ( SEV_ERROR , " Transaction rollback failed when closing the connection. " ) ;
}
// disconnect from the server
SQLRETURN r = SQLDisconnect ( conn - > handle ( ) ) ;
2018-05-18 23:05:18 +02:00
if ( ! SQL_SUCCEEDED ( r ) ) {
2016-04-12 23:43:46 +02:00
LOG ( SEV_ERROR , " Disconnect failed when closing the connection. " ) ;
}
// free the connection handle
conn - > invalidate ( ) ;
sqlsrv_free ( conn ) ;
}
// core_sqlsrv_prepare
// Create a statement object and prepare the SQL query passed in for execution at a later time.
// Parameters:
// stmt - statement to be prepared
// sql - T-SQL command to prepare
// sql_len - length of the T-SQL string
2017-06-22 23:04:34 +02:00
void core_sqlsrv_prepare ( _Inout_ sqlsrv_stmt * stmt , _In_reads_bytes_ ( sql_len ) const char * sql , _In_ SQLLEN sql_len TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
// convert the string from its encoding to UTf-16
2018-05-18 23:05:18 +02:00
// if the string is empty, we initialize the fields and skip since an empty string is a
// failure case for utf16_string_from_mbcs_string
2017-01-19 22:00:41 +01:00
sqlsrv_malloc_auto_ptr < SQLWCHAR > wsql_string ;
2016-04-12 23:43:46 +02:00
unsigned int wsql_len = 0 ;
if ( sql_len = = 0 | | ( sql [ 0 ] = = ' \0 ' & & sql_len = = 1 ) ) {
2017-01-19 22:00:41 +01:00
wsql_string = reinterpret_cast < SQLWCHAR * > ( sqlsrv_malloc ( sizeof ( SQLWCHAR ) ) ) ;
2016-04-12 23:43:46 +02:00
wsql_string [ 0 ] = L ' \0 ' ;
wsql_len = 0 ;
2018-05-18 23:05:18 +02:00
}
2017-01-27 02:06:17 +01:00
else {
2017-01-26 23:26:42 +01:00
if ( sql_len > INT_MAX ) {
LOG ( SEV_ERROR , " Convert input parameter to utf16: buffer length exceeded. " ) ;
2016-04-12 23:43:46 +02:00
throw core : : CoreException ( ) ;
2017-01-26 23:26:42 +01:00
}
SQLSRV_ENCODING encoding = ( ( stmt - > encoding ( ) = = SQLSRV_ENCODING_DEFAULT ) ? stmt - > conn - > encoding ( ) : stmt - > encoding ( ) ) ;
wsql_string = utf16_string_from_mbcs_string ( encoding , reinterpret_cast < const char * > ( sql ) , static_cast < int > ( sql_len ) , & wsql_len ) ;
CHECK_CUSTOM_ERROR ( wsql_string = = 0 , stmt , SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
2016-04-12 23:43:46 +02:00
}
// prepare our wide char query string
core : : SQLPrepareW ( stmt , reinterpret_cast < SQLWCHAR * > ( wsql_string . get ( ) ) , wsql_len TSRMLS_CC ) ;
2017-11-16 19:30:44 +01:00
stmt - > param_descriptions . clear ( ) ;
// if AE is enabled, get meta data for all parameters before binding them
if ( stmt - > conn - > ce_option . enabled ) {
SQLSMALLINT num_params ;
core : : SQLNumParams ( stmt , & num_params ) ;
for ( int i = 0 ; i < num_params ; i + + ) {
param_meta_data param ;
core : : SQLDescribeParam ( stmt , i + 1 , & ( param . sql_type ) , & ( param . column_size ) , & ( param . decimal_digits ) , & ( param . nullable ) ) ;
stmt - > param_descriptions . push_back ( param ) ;
}
}
2016-04-12 23:43:46 +02:00
}
catch ( core : : CoreException & ) {
throw ;
}
2018-05-18 23:05:18 +02:00
}
2016-04-12 23:43:46 +02:00
// core_sqlsrv_get_server_version
// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
// get_server_version to get the version of SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_version - zval for returning results.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_get_server_version ( _Inout_ sqlsrv_conn * conn , _Inout_ zval * server_version TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
sqlsrv_malloc_auto_ptr < char > buffer ;
SQLSMALLINT buffer_len = 0 ;
get_server_version ( conn , & buffer , buffer_len TSRMLS_CC ) ;
2017-01-19 22:00:41 +01:00
core : : sqlsrv_zval_stringl ( server_version , buffer , buffer_len ) ;
if ( buffer ! = 0 ) {
sqlsrv_free ( buffer ) ;
}
2016-04-12 23:43:46 +02:00
buffer . transferred ( ) ;
}
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_get_server_info
// Returns the Database name, the name of the SQL Server we are connected to
// and the version of the SQL Server.
// Parameters:
// conn - The connection resource by which the client and server are connected.
// *server_info - zval for returning results.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_get_server_info ( _Inout_ sqlsrv_conn * conn , _Out_ zval * server_info TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
sqlsrv_malloc_auto_ptr < char > buffer ;
SQLSMALLINT buffer_len = 0 ;
// Get the database name
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_DATABASE_NAME , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
2017-02-01 03:10:37 +01:00
// initialize the array
core : : sqlsrv_array_init ( * conn , server_info TSRMLS_CC ) ;
2016-04-12 23:43:46 +02:00
core : : sqlsrv_add_assoc_string ( * conn , server_info , " CurrentDatabase " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
buffer . transferred ( ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
// Get the server version
get_server_version ( conn , & buffer , buffer_len TSRMLS_CC ) ;
core : : sqlsrv_add_assoc_string ( * conn , server_info , " SQLServerVersion " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
buffer . transferred ( ) ;
2016-04-12 23:43:46 +02:00
// Get the server name
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_SERVER_NAME , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
core : : sqlsrv_add_assoc_string ( * conn , server_info , " SQLServerName " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
buffer . transferred ( ) ;
2016-04-12 23:43:46 +02:00
}
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_get_client_info
// Returns the ODBC driver's dll name, version and the ODBC version.
// Parameters
// conn - The connection resource by which the client and server are connected.
// *client_info - zval for returning the results.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_get_client_info ( _Inout_ sqlsrv_conn * conn , _Out_ zval * client_info TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
sqlsrv_malloc_auto_ptr < char > buffer ;
SQLSMALLINT buffer_len = 0 ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
// Get the ODBC driver's dll name
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_DRIVER_NAME , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
2017-02-01 03:10:37 +01:00
// initialize the array
core : : sqlsrv_array_init ( * conn , client_info TSRMLS_CC ) ;
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2017-01-19 22:00:41 +01:00
core : : sqlsrv_add_assoc_string ( * conn , client_info , " DriverName " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
# else
2016-04-12 23:43:46 +02:00
core : : sqlsrv_add_assoc_string ( * conn , client_info , " DriverDllName " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2016-04-12 23:43:46 +02:00
buffer . transferred ( ) ;
// Get the ODBC driver's ODBC version
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_DRIVER_ODBC_VER , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
core : : sqlsrv_add_assoc_string ( * conn , client_info , " DriverODBCVer " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
buffer . transferred ( ) ;
// Get the OBDC driver's version
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_DRIVER_VER , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
core : : sqlsrv_add_assoc_string ( * conn , client_info , " DriverVer " , buffer , 0 /*duplicate*/ TSRMLS_CC ) ;
buffer . transferred ( ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_is_conn_opt_value_escaped
// determine if connection string value is properly escaped.
2018-05-18 23:05:18 +02:00
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
2016-04-12 23:43:46 +02:00
// the value will be surrounded by { and } by the caller after it has been validated
2017-07-01 00:17:14 +02:00
bool core_is_conn_opt_value_escaped ( _Inout_ const char * value , _Inout_ size_t value_len )
2016-04-12 23:43:46 +02:00
{
2018-05-18 23:05:18 +02:00
// if the value is already quoted, then only analyse the part inside the quotes and return it as
2016-04-12 23:43:46 +02:00
// unquoted since we quote it when adding it to the connection string.
2018-08-01 02:22:56 +02:00
if ( value_len > 0 & & value [ 0 ] = = ' { ' & & value [ value_len - 1 ] = = ' } ' ) {
2016-04-12 23:43:46 +02:00
+ + value ;
value_len - = 2 ;
}
// check to make sure that all right braces are escaped
size_t i = 0 ;
while ( ( value [ i ] ! = ' } ' | | ( value [ i ] = = ' } ' & & value [ i + 1 ] = = ' } ' ) ) & & i < value_len ) {
// skip both braces
if ( value [ i ] = = ' } ' )
+ + i ;
+ + i ;
}
if ( i < value_len & & value [ i ] = = ' } ' ) {
return false ;
}
return true ;
}
2017-05-06 01:28:33 +02:00
// core_is_authentication_option_valid
// if the option for the authentication is valid, returns true. This returns false otherwise.
2017-06-22 23:04:34 +02:00
bool core_is_authentication_option_valid ( _In_z_ const char * value , _In_ size_t value_len )
2017-05-06 01:28:33 +02:00
{
2017-05-06 01:39:51 +02:00
if ( value_len < = 0 )
return false ;
2017-05-06 01:28:33 +02:00
2019-01-12 02:17:45 +01:00
if ( ! stricmp ( value , AzureADOptions : : AZURE_AUTH_SQL_PASSWORD ) | | ! stricmp ( value , AzureADOptions : : AZURE_AUTH_AD_PASSWORD ) | | ! stricmp ( value , AzureADOptions : : AZURE_AUTH_AD_MSI ) ) {
2017-05-06 01:39:51 +02:00
return true ;
2017-05-06 01:28:33 +02:00
}
2017-05-06 01:39:51 +02:00
return false ;
2017-05-06 01:28:33 +02:00
}
2016-04-12 23:43:46 +02:00
// *** internal connection functions and classes ***
namespace {
2018-05-18 23:05:18 +02:00
connection_option const * get_connection_option ( sqlsrv_conn * conn , _In_ SQLULEN key ,
2017-06-22 23:04:34 +02:00
_In_ const connection_option conn_opts [ ] TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
2018-08-01 02:22:56 +02:00
for ( int opt_idx = 0 ; conn_opts [ opt_idx ] . conn_option_key ! = SQLSRV_CONN_OPTION_INVALID ; + + opt_idx ) {
2018-05-18 23:05:18 +02:00
2018-08-01 02:22:56 +02:00
if ( key = = conn_opts [ opt_idx ] . conn_option_key ) {
2016-04-12 23:43:46 +02:00
2018-08-01 02:22:56 +02:00
return & conn_opts [ opt_idx ] ;
2016-04-12 23:43:46 +02:00
}
}
SQLSRV_ASSERT ( false , " Invalid connection option, should have been validated by the driver layer. " ) ;
return NULL ; // avoid a compiler warning
}
// says what it does, and does what it says
// rather than have attributes and connection strings as ODBC does, we unify them into a hash table
// passed to the connection, and then break them out ourselves and either set attributes or put the
// option in the connection string.
2018-05-18 23:05:18 +02:00
void build_connection_string_and_set_conn_attr ( _Inout_ sqlsrv_conn * conn , _Inout_z_ const char * server , _Inout_opt_z_ const char * uid , _Inout_opt_z_ const char * pwd ,
_Inout_opt_ HashTable * options , _In_ const connection_option valid_conn_opts [ ] ,
2017-06-22 23:04:34 +02:00
void * driver , _Inout_ std : : string & connection_string TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
bool mars_mentioned = false ;
connection_option const * conn_opt ;
2018-09-06 20:32:04 +02:00
bool access_token_used = false ;
2019-01-12 02:17:45 +01:00
bool authentication_option_used = zend_hash_index_exists ( options , SQLSRV_CONN_OPTION_AUTHENTICATION ) ;
2016-04-12 23:43:46 +02:00
try {
2019-01-12 02:17:45 +01:00
// Since connection options access token and authentication cannot coexist, check if both of them are used.
// If access token is specified, check UID and<6E> PWD as well.
2018-09-06 20:32:04 +02:00
// No need to check the keyword Trusted_Connection<6F> because it is not among the acceptable options for SQLSRV drivers
if ( zend_hash_index_exists ( options , SQLSRV_CONN_OPTION_ACCESS_TOKEN ) ) {
bool invalidOptions = false ;
// UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string,
// even if they may be empty strings. Likewise if the keyword Authentication exists
2019-01-12 02:17:45 +01:00
if ( uid ! = NULL | | pwd ! = NULL | | authentication_option_used ) {
2018-09-06 20:32:04 +02:00
invalidOptions = true ;
}
2018-05-18 23:05:18 +02:00
2018-09-06 20:32:04 +02:00
CHECK_CUSTOM_ERROR ( invalidOptions , conn , SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) {
2016-04-12 23:43:46 +02:00
throw core : : CoreException ( ) ;
}
2018-09-06 20:32:04 +02:00
access_token_used = true ;
}
2016-04-12 23:43:46 +02:00
2019-01-12 02:17:45 +01:00
// Check if Authentication is ActiveDirectoryMSI
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false ;
if ( authentication_option_used ) {
zval * auth_option = NULL ;
auth_option = zend_hash_index_find ( options , SQLSRV_CONN_OPTION_AUTHENTICATION ) ;
char * option = Z_STRVAL_P ( auth_option ) ;
if ( ! stricmp ( option , AzureADOptions : : AZURE_AUTH_AD_MSI ) ) {
activeDirectoryMSI = true ;
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string
// In both cases, PWD must be NULL
bool invalid = false ;
if ( pwd ! = NULL ) {
invalid = true ;
} else {
if ( uid ! = NULL & & strnlen_s ( uid ) = = 0 ) {
invalid = true ;
}
}
CHECK_CUSTOM_ERROR ( invalid , conn , SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL ) {
throw core : : CoreException ( ) ;
}
}
}
2018-09-06 20:32:04 +02:00
// Add the server name
common_conn_str_append_func ( ODBCConnOptions : : SERVER , server , strnlen_s ( server ) , connection_string TSRMLS_CC ) ;
2019-01-12 02:17:45 +01:00
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// because they are incompatible
if ( ! access_token_used & & ! activeDirectoryMSI ) {
2018-09-06 20:32:04 +02:00
if ( uid = = NULL | | strnlen_s ( uid ) = = 0 ) {
connection_string + = CONNECTION_OPTION_NO_CREDENTIALS ; // "Trusted_Connection={Yes};"
}
else {
bool escaped = core_is_conn_opt_value_escaped ( uid , strnlen_s ( uid ) ) ;
CHECK_CUSTOM_ERROR ( ! escaped , conn , SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
2016-04-12 23:43:46 +02:00
throw core : : CoreException ( ) ;
}
2018-05-18 23:05:18 +02:00
2018-09-06 20:32:04 +02:00
common_conn_str_append_func ( ODBCConnOptions : : UID , uid , strnlen_s ( uid ) , connection_string TSRMLS_CC ) ;
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if ( pwd ! = NULL ) {
escaped = core_is_conn_opt_value_escaped ( pwd , strnlen_s ( pwd ) ) ;
CHECK_CUSTOM_ERROR ( ! escaped , conn , SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core : : CoreException ( ) ;
}
common_conn_str_append_func ( ODBCConnOptions : : PWD , pwd , strnlen_s ( pwd ) , connection_string TSRMLS_CC ) ;
}
2016-04-12 23:43:46 +02:00
}
}
// if no options were given, then we set MARS the defaults and return immediately.
if ( options = = NULL | | zend_hash_num_elements ( options ) = = 0 ) {
connection_string + = CONNECTION_STRING_DEFAULT_OPTIONS ;
return ;
}
2018-05-18 23:05:18 +02:00
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
2016-04-12 23:43:46 +02:00
// if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
// flag is set to false.
if ( zend_hash_index_exists ( options , SQLSRV_CONN_OPTION_TRACE_FILE ) ) {
2018-05-18 23:05:18 +02:00
zval * trace_value = NULL ;
2017-05-06 01:39:51 +02:00
trace_value = zend_hash_index_find ( options , SQLSRV_CONN_OPTION_TRACE_ON ) ;
2018-05-18 23:05:18 +02:00
2017-05-06 01:39:51 +02:00
if ( trace_value = = NULL | | ! zend_is_true ( trace_value ) ) {
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
zend_hash_index_del ( options , SQLSRV_CONN_OPTION_TRACE_FILE ) ;
}
}
2017-05-06 01:39:51 +02:00
zend_string * key = NULL ;
zend_ulong index = - 1 ;
zval * data = NULL ;
2016-04-12 23:43:46 +02:00
2017-05-06 01:39:51 +02:00
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 ;
2016-04-12 23:43:46 +02:00
2017-05-06 01:39:51 +02:00
// 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. " ) ;
2016-06-13 23:44:53 +02:00
2017-05-06 01:39:51 +02:00
conn_opt = get_connection_option ( conn , index , valid_conn_opts TSRMLS_CC ) ;
2016-06-13 23:44:53 +02:00
2017-05-06 01:39:51 +02:00
if ( index = = SQLSRV_CONN_OPTION_MARS ) {
mars_mentioned = true ;
}
2016-06-13 23:44:53 +02:00
2017-05-06 01:39:51 +02:00
conn_opt - > func ( conn_opt , data , conn , connection_string TSRMLS_CC ) ;
} ZEND_HASH_FOREACH_END ( ) ;
2016-04-12 23:43:46 +02:00
// MARS on if not explicitly turned off
if ( ! mars_mentioned ) {
connection_string + = CONNECTION_OPTION_MARS_ON ;
}
}
catch ( core : : CoreException & ) {
2018-09-26 23:51:16 +02:00
conn - > ce_option . akv_reset ( ) ;
2016-04-12 23:43:46 +02:00
throw ;
}
}
// get_server_version
// Helper function which returns the version of the SQL Server we are connected to.
2017-06-22 23:04:34 +02:00
void get_server_version ( _Inout_ sqlsrv_conn * conn , _Outptr_result_buffer_ ( len ) char * * server_version , _Out_ SQLSMALLINT & len TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
sqlsrv_malloc_auto_ptr < char > buffer ;
SQLSMALLINT buffer_len = 0 ;
buffer = static_cast < char * > ( sqlsrv_malloc ( INFO_BUFFER_LEN ) ) ;
core : : SQLGetInfo ( conn , SQL_DBMS_VER , buffer , INFO_BUFFER_LEN , & buffer_len TSRMLS_CC ) ;
* server_version = buffer ;
len = buffer_len ;
buffer . transferred ( ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// get_processor_arch
2018-05-18 23:05:18 +02:00
// Calls GetSystemInfo to verify the what architecture of the processor is supported
2016-04-12 23:43:46 +02:00
// and return the string of the processor name.
const char * get_processor_arch ( void )
{
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2017-01-19 22:00:41 +01:00
struct utsname sys_info ;
if ( uname ( & sys_info ) = = - 1 )
{
DIE ( " Error retrieving system info " ) ;
}
2017-01-30 22:11:11 +01:00
if ( strcmp ( sys_info . machine , " x86 " ) = = 0 ) {
2017-01-19 22:00:41 +01:00
return PROCESSOR_ARCH [ 0 ] ;
2017-01-30 22:11:11 +01:00
} else if ( strcmp ( sys_info . machine , " x86_64 " ) = = 0 ) {
2017-01-19 22:00:41 +01:00
return PROCESSOR_ARCH [ 1 ] ;
2017-01-30 22:11:11 +01:00
} else if ( strcmp ( sys_info . machine , " ia64 " ) = = 0 ) {
2017-01-19 22:00:41 +01:00
return PROCESSOR_ARCH [ 2 ] ;
} else {
DIE ( " Unknown processor architecture. " ) ;
2017-05-06 01:39:51 +02:00
}
2017-01-19 22:00:41 +01:00
return NULL ;
# else
2016-04-12 23:43:46 +02:00
SYSTEM_INFO sys_info ;
GetSystemInfo ( & sys_info ) ;
switch ( sys_info . wProcessorArchitecture ) {
case PROCESSOR_ARCHITECTURE_INTEL :
return PROCESSOR_ARCH [ 0 ] ;
case PROCESSOR_ARCHITECTURE_AMD64 :
return PROCESSOR_ARCH [ 1 ] ;
case PROCESSOR_ARCHITECTURE_IA64 :
return PROCESSOR_ARCH [ 2 ] ;
default :
DIE ( " Unknown Windows processor architecture. " ) ;
return NULL ;
}
2017-05-06 01:39:51 +02:00
return NULL ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2016-04-12 23:43:46 +02:00
}
// some features require a server of a certain version or later
// this function determines the version of the server we're connected to
// and stores it in the connection. Any errors are logged before return.
// Exception is thrown when the server version is either undetermined
// or is invalid (< 2000).
2017-06-22 23:04:34 +02:00
void determine_server_version ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
SQLSMALLINT info_len ;
2018-08-01 02:22:56 +02:00
char p [ INFO_BUFFER_LEN ] = { ' \0 ' } ;
2016-04-12 23:43:46 +02:00
core : : SQLGetInfo ( conn , SQL_DBMS_VER , p , INFO_BUFFER_LEN , & info_len TSRMLS_CC ) ;
errno = 0 ;
2018-08-01 02:22:56 +02:00
char version_major_str [ 3 ] = { ' \0 ' } ;
2016-04-12 23:43:46 +02:00
SERVER_VERSION version_major ;
2017-05-06 01:39:51 +02:00
memcpy_s ( version_major_str , sizeof ( version_major_str ) , p , 2 ) ;
2017-01-19 22:00:41 +01:00
2018-08-01 02:22:56 +02:00
version_major_str [ 2 ] = { ' \0 ' } ;
2016-04-12 23:43:46 +02:00
version_major = static_cast < SERVER_VERSION > ( atoi ( version_major_str ) ) ;
CHECK_CUSTOM_ERROR ( version_major = = 0 & & ( errno = = ERANGE | | errno = = EINVAL ) , conn , SQLSRV_ERROR_UNKNOWN_SERVER_VERSION )
{
throw core : : CoreException ( ) ;
}
// SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
// that high
conn - > server_version = version_major ;
}
2018-05-18 21:24:37 +02:00
void load_azure_key_vault ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2018-05-06 02:08:01 +02:00
{
2018-05-18 23:05:18 +02:00
// If column encryption is not enabled simply do nothing. Otherwise, check if Azure Key Vault
// is required for encryption or decryption. Note, in order to load and configure Azure Key Vault,
// all fields in conn->ce_option must be defined.
if ( ! conn - > ce_option . enabled | | ! conn - > ce_option . akv_required )
return ;
CHECK_CUSTOM_ERROR ( conn - > ce_option . akv_mode = = - 1 , conn , SQLSRV_ERROR_AKV_AUTH_MISSING ) {
throw core : : CoreException ( ) ;
}
2018-12-03 21:08:21 +01:00
CHECK_CUSTOM_ERROR ( ! conn - > ce_option . akv_id , conn , SQLSRV_ERROR_AKV_NAME_MISSING ) {
2018-05-18 23:05:18 +02:00
throw core : : CoreException ( ) ;
}
2018-12-03 21:08:21 +01:00
CHECK_CUSTOM_ERROR ( ! conn - > ce_option . akv_secret , conn , SQLSRV_ERROR_AKV_SECRET_MISSING ) {
2018-05-18 23:05:18 +02:00
throw core : : CoreException ( ) ;
}
2018-09-26 23:51:16 +02:00
char * akv_id = conn - > ce_option . akv_id . get ( ) ;
char * akv_secret = conn - > ce_option . akv_secret . get ( ) ;
unsigned int id_len = strnlen_s ( akv_id ) ;
unsigned int key_size = strnlen_s ( akv_secret ) ;
2018-05-18 23:05:18 +02:00
configure_azure_key_vault ( conn , AKV_CONFIG_FLAGS , conn - > ce_option . akv_mode , 0 ) ;
configure_azure_key_vault ( conn , AKV_CONFIG_PRINCIPALID , akv_id , id_len ) ;
configure_azure_key_vault ( conn , AKV_CONFIG_AUTHSECRET , akv_secret , key_size ) ;
2018-05-06 02:08:01 +02:00
}
2018-05-18 21:24:37 +02:00
void configure_azure_key_vault ( sqlsrv_conn * conn , BYTE config_attr , const DWORD config_value , size_t key_size )
2018-05-06 02:08:01 +02:00
{
2018-05-18 23:05:18 +02:00
BYTE akv_data [ sizeof ( CEKEYSTOREDATA ) + sizeof ( DWORD ) + 1 ] ;
CEKEYSTOREDATA * pData = reinterpret_cast < CEKEYSTOREDATA * > ( akv_data ) ;
2018-05-17 00:14:47 +02:00
2018-05-18 23:05:18 +02:00
char akv_name [ ] = " AZURE_KEY_VAULT " ;
unsigned int name_len = 15 ;
unsigned int wname_len = 0 ;
sqlsrv_malloc_auto_ptr < SQLWCHAR > wakv_name ;
wakv_name = utf16_string_from_mbcs_string ( SQLSRV_ENCODING_UTF8 , akv_name , name_len , & wname_len ) ;
2018-05-17 00:14:47 +02:00
2018-05-18 23:05:18 +02:00
CHECK_CUSTOM_ERROR ( wakv_name = = 0 , conn , SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) {
throw core : : CoreException ( ) ;
}
2018-05-18 21:24:37 +02:00
2018-05-18 23:05:18 +02:00
pData - > name = ( wchar_t * ) wakv_name . get ( ) ;
2018-05-18 21:24:37 +02:00
2018-05-18 23:05:18 +02:00
pData - > data [ 0 ] = config_attr ;
pData - > dataSize = sizeof ( config_attr ) + sizeof ( config_value ) ;
* reinterpret_cast < DWORD * > ( & pData - > data [ 1 ] ) = config_value ;
2018-05-18 21:24:37 +02:00
2018-05-18 23:05:18 +02:00
core : : SQLSetConnectAttr ( conn , SQL_COPT_SS_CEKEYSTOREDATA , reinterpret_cast < SQLPOINTER > ( pData ) , SQL_IS_POINTER ) ;
2018-05-06 02:08:01 +02:00
}
2018-05-18 21:24:37 +02:00
void configure_azure_key_vault ( sqlsrv_conn * conn , BYTE config_attr , const char * config_value , size_t key_size )
2018-05-06 02:08:01 +02:00
{
2018-05-18 23:05:18 +02:00
BYTE akv_data [ sizeof ( CEKEYSTOREDATA ) + MAX_CE_NAME_LEN ] ;
CEKEYSTOREDATA * pData = reinterpret_cast < CEKEYSTOREDATA * > ( akv_data ) ;
2018-05-17 00:14:47 +02:00
2018-05-18 23:05:18 +02:00
char akv_name [ ] = " AZURE_KEY_VAULT " ;
unsigned int name_len = 15 ;
unsigned int wname_len = 0 ;
sqlsrv_malloc_auto_ptr < SQLWCHAR > wakv_name ;
wakv_name = utf16_string_from_mbcs_string ( SQLSRV_ENCODING_UTF8 , akv_name , name_len , & wname_len ) ;
2018-05-17 00:14:47 +02:00
2018-05-18 23:05:18 +02:00
CHECK_CUSTOM_ERROR ( wakv_name = = 0 , conn , SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) {
throw core : : CoreException ( ) ;
}
2018-05-17 00:14:47 +02:00
2018-05-18 23:05:18 +02:00
pData - > name = ( wchar_t * ) wakv_name . get ( ) ;
2018-05-06 02:22:02 +02:00
2018-05-18 23:05:18 +02:00
pData - > data [ 0 ] = config_attr ;
pData - > dataSize = 1 + key_size ;
2018-05-18 21:24:37 +02:00
2018-05-18 23:05:18 +02:00
memcpy_s ( pData - > data + 1 , key_size * sizeof ( char ) , config_value , key_size ) ;
2018-05-18 21:24:37 +02:00
2018-05-18 23:05:18 +02:00
core : : SQLSetConnectAttr ( conn , SQL_COPT_SS_CEKEYSTOREDATA , reinterpret_cast < SQLPOINTER > ( pData ) , SQL_IS_POINTER ) ;
2018-05-06 02:08:01 +02:00
}
2017-06-22 23:04:34 +02:00
void common_conn_str_append_func ( _In_z_ const char * odbc_name , _In_reads_ ( val_len ) const char * val , _Inout_ size_t val_len , _Inout_ std : : string & conn_str TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
// be escaped, such as a closing }.
TSRMLS_C ;
2018-08-01 02:22:56 +02:00
if ( val_len > 0 & & val [ 0 ] = = ' { ' & & val [ val_len - 1 ] = = ' } ' ) {
2016-04-12 23:43:46 +02:00
+ + val ;
val_len - = 2 ;
}
conn_str + = odbc_name ;
conn_str + = " ={ " ;
conn_str . append ( val , val_len ) ;
conn_str + = " }; " ;
}
} // namespace
// simply add the parsed value to the connection string
2017-09-15 01:54:59 +02:00
void conn_str_append_func : : func ( _In_ connection_option const * option , _In_ zval * value , sqlsrv_conn * /*conn*/ , _Inout_ std : : string & conn_str TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
const char * val_str = Z_STRVAL_P ( value ) ;
size_t val_len = Z_STRLEN_P ( value ) ;
common_conn_str_append_func ( option - > odbc_name , val_str , val_len , conn_str TSRMLS_CC ) ;
}
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
2017-09-15 01:54:59 +02:00
void conn_null_func : : func ( connection_option const * /*option*/ , zval * /*value*/ , sqlsrv_conn * /*conn*/ , std : : string & /*conn_str*/ TSRMLS_DC )
2018-05-18 23:05:18 +02:00
{
2016-04-12 23:43:46 +02:00
TSRMLS_C ;
}
2017-09-09 01:47:43 +02:00
void driver_set_func : : func ( _In_ connection_option const * option , _In_ zval * value , _Inout_ sqlsrv_conn * conn , _Inout_ std : : string & conn_str TSRMLS_DC )
{
2017-09-15 01:54:59 +02:00
const char * val_str = Z_STRVAL_P ( value ) ;
size_t val_len = Z_STRLEN_P ( value ) ;
std : : string driver_option ( " " ) ;
common_conn_str_append_func ( option - > odbc_name , val_str , val_len , driver_option TSRMLS_CC ) ;
2018-05-18 23:05:18 +02:00
2017-09-27 01:38:34 +02:00
conn - > driver_version = ODBC_DRIVER_UNKNOWN ;
2017-09-30 00:54:34 +02:00
for ( short i = DRIVER_VERSION : : FIRST ; i < = DRIVER_VERSION : : LAST & & conn - > driver_version = = ODBC_DRIVER_UNKNOWN ; + + i ) {
std : : string driver_name = CONNECTION_STRING_DRIVER_NAME [ i ] ;
2017-09-27 01:38:34 +02:00
2018-05-18 23:05:18 +02:00
if ( ! driver_name . compare ( driver_option ) ) {
2017-09-27 01:38:34 +02:00
conn - > driver_version = DRIVER_VERSION ( i ) ;
}
}
2017-09-30 00:54:34 +02:00
CHECK_CUSTOM_ERROR ( conn - > driver_version = = ODBC_DRIVER_UNKNOWN , conn , SQLSRV_ERROR_CONNECT_INVALID_DRIVER , val_str ) {
2017-09-09 01:47:43 +02:00
throw core : : CoreException ( ) ;
2018-05-18 23:05:18 +02:00
}
2017-09-27 01:38:34 +02:00
2017-09-12 22:30:14 +02:00
conn_str + = driver_option ;
2017-09-09 01:47:43 +02:00
}
void column_encryption_set_func : : func ( _In_ connection_option const * option , _In_ zval * value , _Inout_ sqlsrv_conn * conn , _Inout_ std : : string & conn_str TSRMLS_DC )
{
convert_to_string ( value ) ;
const char * value_str = Z_STRVAL_P ( value ) ;
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
conn - > ce_option . enabled = false ;
if ( ! stricmp ( value_str , " enabled " ) ) {
conn - > ce_option . enabled = true ;
}
conn_str + = option - > odbc_name ;
conn_str + = " = " ;
conn_str + = value_str ;
conn_str + = " ; " ;
}
2018-05-18 23:05:18 +02:00
void ce_akv_str_set_func : : func ( _In_ connection_option const * option , _In_ zval * value , _Inout_ sqlsrv_conn * conn , _Inout_ std : : string & conn_str TSRMLS_DC )
2018-05-06 02:08:01 +02:00
{
2018-05-18 23:05:18 +02:00
SQLSRV_ASSERT ( Z_TYPE_P ( value ) = = IS_STRING , " Azure Key Vault keywords accept only strings. " ) ;
2018-09-26 23:51:16 +02:00
const char * value_str = Z_STRVAL_P ( value ) ;
2018-05-18 23:05:18 +02:00
size_t value_len = Z_STRLEN_P ( value ) ;
2018-05-08 23:12:14 +02:00
2018-05-18 23:05:18 +02:00
CHECK_CUSTOM_ERROR ( value_len < = 0 , conn , SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) {
2018-05-08 23:12:14 +02:00
throw core : : CoreException ( ) ;
}
2018-05-18 23:05:18 +02:00
switch ( option - > conn_option_key )
2018-05-06 02:08:01 +02:00
{
2018-05-18 23:05:18 +02:00
case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION :
{
if ( ! stricmp ( value_str , " KeyVaultPassword " ) ) {
conn - > ce_option . akv_mode = AKVCFG_AUTHMODE_PASSWORD ;
} else if ( ! stricmp ( value_str , " KeyVaultClientSecret " ) ) {
conn - > ce_option . akv_mode = AKVCFG_AUTHMODE_CLIENTKEY ;
} else {
CHECK_CUSTOM_ERROR ( 1 , conn , SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION ) {
throw core : : CoreException ( ) ;
2018-05-06 02:08:01 +02:00
}
}
2018-05-18 23:05:18 +02:00
conn - > ce_option . akv_required = true ;
break ;
}
case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID :
case SQLSRV_CONN_OPTION_KEYSTORE_SECRET :
{
2018-09-26 23:51:16 +02:00
// Create a new string to save a copy of the zvalue
char * pValue = static_cast < char * > ( sqlsrv_malloc ( value_len + 1 ) ) ;
memcpy_s ( pValue , value_len + 1 , value_str , value_len ) ;
pValue [ value_len ] = ' \0 ' ; // this makes sure there will be no trailing garbage
// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
if ( option - > conn_option_key = = SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID ) {
conn - > ce_option . akv_id = pValue ;
} else {
conn - > ce_option . akv_secret = pValue ;
}
2018-05-18 23:05:18 +02:00
conn - > ce_option . akv_required = true ;
break ;
}
default :
SQLSRV_ASSERT ( false , " ce_akv_str_set_func: Invalid AKV option! " ) ;
break ;
2018-05-06 02:08:01 +02:00
}
}
2016-04-12 23:43:46 +02:00
// helper function to evaluate whether a string value is true or false.
// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
// Returns 1 for true and 0 for false.
2018-05-18 23:05:18 +02:00
size_t core_str_zval_is_true ( _Inout_ zval * value_z )
{
2016-04-12 23:43:46 +02:00
SQLSRV_ASSERT ( Z_TYPE_P ( value_z ) = = IS_STRING , " core_str_zval_is_true: This function only accepts zval of type string. " ) ;
char * value_in = Z_STRVAL_P ( value_z ) ;
size_t val_len = Z_STRLEN_P ( value_z ) ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
size_t last_char = val_len - 1 ;
2018-08-01 02:22:56 +02:00
while ( isspace ( ( unsigned char ) value_in [ last_char ] ) ) {
value_in [ last_char ] = ' \0 ' ;
2016-04-12 23:43:46 +02:00
val_len = last_char ;
- - last_char ;
}
// save adjustments to the value made by stripping whitespace at the end
2017-05-06 01:39:51 +02:00
Z_STRLEN_P ( value_z ) = val_len ;
2016-04-12 23:43:46 +02:00
const char VALID_TRUE_VALUE_1 [ ] = " true " ;
const char VALID_TRUE_VALUE_2 [ ] = " 1 " ;
2018-05-18 23:05:18 +02:00
2016-04-12 23:43:46 +02:00
if ( ( val_len = = ( sizeof ( VALID_TRUE_VALUE_1 ) - 1 ) & & ! strnicmp ( value_in , VALID_TRUE_VALUE_1 , val_len ) ) | |
2018-05-18 23:05:18 +02:00
( val_len = = ( sizeof ( VALID_TRUE_VALUE_2 ) - 1 ) & & ! strnicmp ( value_in , VALID_TRUE_VALUE_2 , val_len ) )
2016-04-12 23:43:46 +02:00
) {
return 1 ; // true
}
return 0 ; // false
}
2018-09-06 20:32:04 +02:00
void access_token_set_func : : func ( _In_ connection_option const * option , _In_ zval * value , _Inout_ sqlsrv_conn * conn , _Inout_ std : : string & conn_str TSRMLS_DC )
{
SQLSRV_ASSERT ( Z_TYPE_P ( value ) = = IS_STRING , " An access token must be a byte string. " ) ;
size_t value_len = Z_STRLEN_P ( value ) ;
CHECK_CUSTOM_ERROR ( value_len < = 0 , conn , SQLSRV_ERROR_EMPTY_ACCESS_TOKEN ) {
throw core : : CoreException ( ) ;
}
const char * value_str = Z_STRVAL_P ( value ) ;
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
// {
// unsigned int dataSize;
// char data[];
// } ACCESSTOKEN;
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token
size_t dataSize = 2 * value_len ;
sqlsrv_malloc_auto_ptr < ACCESSTOKEN > accToken ;
accToken = reinterpret_cast < ACCESSTOKEN * > ( sqlsrv_malloc ( sizeof ( ACCESSTOKEN ) + dataSize ) ) ;
ACCESSTOKEN * pAccToken = accToken . get ( ) ;
SQLSRV_ASSERT ( pAccToken ! = NULL , " Something went wrong when trying to allocate memory for the access token. " ) ;
pAccToken - > dataSize = dataSize ;
// Expand access token with padding bytes
for ( size_t i = 0 , j = 0 ; i < dataSize ; i + = 2 , j + + ) {
pAccToken - > data [ i ] = value_str [ j ] ;
pAccToken - > data [ i + 1 ] = 0 ;
}
core : : SQLSetConnectAttr ( conn , SQL_COPT_SS_ACCESS_TOKEN , reinterpret_cast < SQLPOINTER > ( pAccToken ) , SQL_IS_POINTER ) ;
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn - > azure_ad_access_token = pAccToken ;
accToken . transferred ( ) ;
}