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
//
2017-09-14 00:34:06 +02:00
// Microsoft Drivers 5.1 for PHP for SQL Server
2016-04-12 23:43:46 +02:00
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// 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,
// 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.
// 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
// 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 ;
// 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
std : : vector < std : : string > CONNECTION_STRING_DRIVER_NAME { " Driver={ODBC Driver 13 for SQL Server}; " , " Driver={ODBC Driver 11 for SQL Server}; " , " Driver={ODBC Driver 17 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 ***
2017-06-22 23:04:34 +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 ,
2017-09-11 20:43:52 +02:00
_Inout_opt_ HashTable * options_ht , _In_ const connection_option valid_conn_opts [ ] ,
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 ) ;
2017-08-23 21:36:24 +02:00
void load_configure_ksp ( _Inout_ sqlsrv_conn * conn TSRMLS_DC ) ;
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
// uid - username
// 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 ,
_Inout_z_ const char * server , _Inout_opt_z_ const char * uid , _Inout_opt_z_ const char * pwd ,
_Inout_opt_ HashTable * options_ht , _In_ error_callback err , _In_ const connection_option valid_conn_opts [ ] ,
_In_ void * driver , _In_z_ const char * driver_func TSRMLS_DC )
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 ;
2017-02-02 02:36:41 +01:00
# endif // _WIN32
2016-04-12 23:43:46 +02:00
2017-02-01 03:10:37 +01:00
try {
2017-09-30 00:54:34 +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.
// Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section.
2017-05-06 01:39:51 +02:00
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2017-02-02 01:55:05 +01:00
char pooling_string [ 128 ] = { 0 } ;
SQLGetPrivateProfileString ( " ODBC " , " Pooling " , " 0 " , pooling_string , sizeof ( pooling_string ) , " ODBCINST.INI " ) ;
if ( pooling_string [ 0 ] = = ' 1 ' | | toupper ( pooling_string [ 0 ] ) = = ' Y ' | |
( toupper ( pooling_string [ 0 ] ) = = ' O ' & & toupper ( pooling_string [ 1 ] ) = = ' N ' ) )
{
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.
2016-04-12 23:43:46 +02:00
2017-01-26 23:26:42 +01:00
if ( options_ht & & zend_hash_num_elements ( options_ht ) > 0 ) {
2017-05-06 01:39:51 +02:00
2017-01-26 23:26:42 +01:00
zval * option_z = NULL ;
2017-09-12 01:17:24 +02:00
option_z = zend_hash_index_find ( options_ht , SQLSRV_CONN_OPTION_CONN_POOLING ) ;
2017-01-26 23:26:42 +01:00
if ( option_z ) {
// if the option was found and it's not true, then use the non pooled environment handle
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
}
}
}
2017-02-02 02:36:41 +01: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 ) ;
2017-08-29 01:34:44 +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 ) ;
2017-09-11 20:43:52 +02:00
2017-09-27 01:38:34 +02:00
if ( conn - > ce_option . enabled ) {
2017-09-30 00:54:34 +02:00
// when column encryption is enabled, must use ODBC driver 17
2017-09-27 01:38:34 +02:00
if ( conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
2017-09-30 00:54:34 +02:00
CHECK_CUSTOM_ERROR ( conn - > driver_version ! = ODBC_DRIVER_17 , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
2017-09-22 21:55:38 +02:00
throw core : : CoreException ( ) ;
}
2017-09-30 00:54:34 +02:00
# ifndef _WIN32
2017-09-28 22:22:00 +02:00
// check if ODBC 17 actually exists, if not, throw an exception
CHECK_CUSTOM_ERROR ( ! core_search_odbc_driver_unix ( ODBC_DRIVER_17 ) , conn , SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core : : CoreException ( ) ;
}
2017-09-27 01:38:34 +02:00
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-09-22 21:55:38 +02:00
}
2017-09-27 01:38:34 +02:00
else {
// driver not specified, so connect using ODBC 17
// In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states
// If it fails to connect using ODBC 17, it is unreliable to check for a certain sql state error.
// Thus, we will simply throw an exception.
2017-09-28 22:22:00 +02:00
if ( core_search_odbc_driver_unix ( ODBC_DRIVER_17 ) ) {
2017-09-30 00:54:34 +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
CHECK_CUSTOM_ERROR ( true , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
2017-09-27 01:38:34 +02:00
throw core : : CoreException ( ) ;
}
}
2017-09-28 22:22:00 +02:00
} // else driver_version not unknown
2017-02-01 03:10:37 +01:00
}
2017-09-27 01:38:34 +02:00
else {
2017-09-28 22:22:00 +02:00
// column encryption NOT enabled
2017-09-27 01:38:34 +02:00
if ( conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
2017-09-28 22:22:00 +02:00
// 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-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 ;
2017-09-30 00:54:34 +02:00
if ( core_search_odbc_driver_unix ( ODBC_DRIVER_13 ) ) {
odbc_version = ODBC_DRIVER_13 ;
}
else if ( core_search_odbc_driver_unix ( ODBC_DRIVER_17 ) ) {
odbc_version = ODBC_DRIVER_17 ;
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-09-28 22:22:00 +02:00
} // else driver_version not unknown
} // else ce_option enabled
2017-09-27 01:38:34 +02:00
# else
2017-09-28 22:22:00 +02:00
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-09-30 00:54:34 +02:00
if ( core_compare_error_state ( conn , r , " IM002 " ) ) {
2017-09-28 22:22:00 +02:00
// sql state IM002 means that the specified ODBC driver is not installed
CHECK_CUSTOM_ERROR ( true , conn , SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND ) {
throw core : : CoreException ( ) ;
}
}
}
else {
// 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-09-30 00:54:34 +02:00
// sql state IM002 means that the specified ODBC driver is not installed
CHECK_CUSTOM_ERROR ( core_compare_error_state ( conn , r , " IM002 " ) , conn , SQLSRV_ERROR_CE_DRIVER_REQUIRED , get_processor_arch ( ) ) {
throw core : : CoreException ( ) ;
2017-09-28 22:22:00 +02:00
}
}
}
else {
// column encryption NOT enabled
if ( conn - > driver_version ! = ODBC_DRIVER_UNKNOWN ) {
r = core_odbc_connect ( conn , conn_str , is_pooled ) ;
2017-09-30 00:54:34 +02:00
if ( core_compare_error_state ( conn , r , " IM002 " ) ) {
2017-09-28 22:22:00 +02:00
// sql state IM002 means that the specified ODBC driver is not installed
2017-09-30 00:54:34 +02:00
CHECK_CUSTOM_ERROR ( true , conn , SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND , CONNECTION_STRING_DRIVER_NAME [ conn - > driver_version ] . c_str ( ) ) {
2017-09-28 22:22:00 +02:00
throw core : : CoreException ( ) ;
}
}
}
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 ) ;
2017-09-30 00:54:34 +02:00
if ( SQL_SUCCEEDED ( r ) ) {
done = true ;
2017-09-27 01:38:34 +02:00
}
2017-09-30 00:54:34 +02:00
else if ( core_compare_error_state ( conn , r , " IM002 " ) ) {
if ( i = = DRIVER_VERSION : : LAST ) {
// sql state IM002 means that the specified ODBC driver is not installed
// failed to connect even using the last valid ODBC driver
CHECK_CUSTOM_ERROR ( true , conn , SQLSRV_ERROR_DRIVER_NOT_INSTALLED , get_processor_arch ( ) ) {
throw core : : CoreException ( ) ;
}
2017-09-27 01:38:34 +02:00
}
}
2017-09-30 00:54:34 +02:00
else {
// something else went wrong other than missing the ODBC driver, exit the loop now
done = true ;
}
} // for
2017-09-27 01:38:34 +02:00
}
2017-09-06 19:55:08 +02:00
} // else ce_option enabled
2017-09-28 22:22:00 +02:00
# endif // !_WIN32
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
2017-09-06 21:46:22 +02:00
load_configure_ksp ( conn ) ;
2017-08-23 21:36:24 +02:00
2017-08-29 01:34:44 +02:00
// determine the version of the server we're connected to. The server version is left in the
// 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 ) {
2017-02-02 02:36:41 +01: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
}
2017-02-02 02:36:41 +01: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 & ) {
2017-09-14 04:06:28 +02:00
conn_str . clear ( ) ;
2016-04-12 23:43:46 +02:00
conn - > invalidate ( ) ;
throw ;
}
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:
// conn - the connection structure on which we establish the connection
// rc - ODBC return code
// Return - a boolean flag that indicates if the error states are the same
bool core_compare_error_state ( _In_ sqlsrv_conn * conn , _In_ SQLRETURN rc , _In_ const char * error_state )
{
if ( SQL_SUCCEEDED ( rc ) )
return false ;
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] ;
SQLSMALLINT len ;
SQLRETURN sr = SQLGetDiagField ( SQL_HANDLE_DBC , conn - > handle ( ) , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
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
2017-09-23 01:37:05 +02:00
// This method is meant to be used in a non-Windows environment,
// searching for a particular ODBC driver name in the odbcinst.ini file
// Parameters:
// driver_version - a valid value in enum DRIVER_VERSION
// Return - a boolean flag that indicates if the specified driver version is found or not
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-09-30 00:54:34 +02:00
char szBuf [ DEFAULT_CONN_STR_LEN + 1 ] ; // use a large enough buffer size
2017-09-23 01:37:05 +02:00
WORD cbBufMax = DEFAULT_CONN_STR_LEN ;
2017-09-22 21:55:38 +02:00
WORD cbBufOut ;
char * pszBuf = szBuf ;
bool found_driver = false ;
2017-09-25 22:37:42 +02:00
# ifndef _WIN32
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 ;
}
// 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
2017-09-25 22:37:42 +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:
// 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 ) ;
2017-09-06 19:55:08 +02:00
# endif // !_WIN32
2017-09-30 00:54:34 +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
// the call to core_sqlsrv_begin_transaction and before any calls to
// 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.
// Parameters:
// 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. " ) ;
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_OFF ) ,
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
// 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_commit ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
try {
DEBUG_SQLSRV_ASSERT ( conn ! = NULL , " core_sqlsrv_commit: connection object was null. " ) ;
core : : SQLEndTran ( SQL_HANDLE_DBC , conn , SQL_COMMIT TSRMLS_CC ) ;
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_ON ) ,
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. " ) ;
core : : SQLEndTran ( SQL_HANDLE_DBC , conn , SQL_ROLLBACK TSRMLS_CC ) ;
core : : SQLSetConnectAttr ( conn , SQL_ATTR_AUTOCOMMIT , reinterpret_cast < SQLPOINTER > ( SQL_AUTOCOMMIT_ON ) ,
SQL_IS_UINTEGER TSRMLS_CC ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_sqlsrv_close
// Called when a connection resource is destroyed by the Zend engine.
// 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 {
// 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 ( ) ) ;
if ( ! SQL_SUCCEEDED ( r ) ) {
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
// 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 ;
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 ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// 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 {
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 ( ) ;
}
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 ( ) ;
// 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 ) ;
buffer . transferred ( ) ;
// 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 ) ;
buffer . transferred ( ) ;
}
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 ;
2017-02-01 03:10:37 +01: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 ( ) ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
// core_is_conn_opt_value_escaped
// determine if connection string value is properly escaped.
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
// 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
{
// if the value is already quoted, then only analyse the part inside the quotes and return it as
// unquoted since we quote it when adding it to the connection string.
if ( value_len > 0 & & value [ 0 ] = = ' { ' & & value [ value_len - 1 ] = = ' } ' ) {
+ + 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
2017-05-06 01:39:51 +02:00
if ( ! stricmp ( value , AzureADOptions : : AZURE_AUTH_SQL_PASSWORD ) | | ! stricmp ( value , AzureADOptions : : AZURE_AUTH_AD_PASSWORD ) ) {
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 {
2017-06-22 23:04:34 +02:00
connection_option const * get_connection_option ( sqlsrv_conn * conn , _In_ SQLULEN key ,
_In_ const connection_option conn_opts [ ] TSRMLS_DC )
2016-04-12 23:43:46 +02:00
{
for ( int opt_idx = 0 ; conn_opts [ opt_idx ] . conn_option_key ! = SQLSRV_CONN_OPTION_INVALID ; + + opt_idx ) {
if ( key = = conn_opts [ opt_idx ] . conn_option_key ) {
return & conn_opts [ opt_idx ] ;
}
}
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.
2017-06-22 23:04:34 +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 [ ] ,
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 ;
try {
2016-06-13 23:44:53 +02:00
2016-04-12 23:43:46 +02:00
// Add the server name
common_conn_str_append_func ( ODBCConnOptions : : SERVER , server , strlen ( server ) , connection_string TSRMLS_CC ) ;
// if uid is not present then we use trusted connection.
if ( uid = = NULL | | strlen ( uid ) = = 0 ) {
connection_string + = " Trusted_Connection={Yes}; " ;
}
else {
bool escaped = core_is_conn_opt_value_escaped ( uid , strlen ( uid ) ) ;
CHECK_CUSTOM_ERROR ( ! escaped , conn , SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core : : CoreException ( ) ;
}
common_conn_str_append_func ( ODBCConnOptions : : UID , uid , strlen ( 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 , strlen ( pwd ) ) ;
CHECK_CUSTOM_ERROR ( ! escaped , conn , SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
throw core : : CoreException ( ) ;
}
common_conn_str_append_func ( ODBCConnOptions : : PWD , pwd , strlen ( pwd ) , connection_string TSRMLS_CC ) ;
}
}
// 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 ;
}
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
// 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 ) ) {
zval * trace_value = NULL ;
2017-05-06 01:39:51 +02:00
trace_value = zend_hash_index_find ( options , SQLSRV_CONN_OPTION_TRACE_ON ) ;
2016-04-12 23:43:46 +02:00
2017-05-06 01:39:51 +02:00
if ( trace_value = = NULL | | ! zend_is_true ( trace_value ) ) {
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 & ) {
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 {
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
// Calls GetSystemInfo to verify the what architecture of the processor is supported
// 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 ;
char p [ INFO_BUFFER_LEN ] ;
core : : SQLGetInfo ( conn , SQL_DBMS_VER , p , INFO_BUFFER_LEN , & info_len TSRMLS_CC ) ;
errno = 0 ;
char version_major_str [ 3 ] ;
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
2016-04-12 23:43:46 +02:00
version_major_str [ 2 ] = ' \0 ' ;
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 ;
}
2017-08-23 21:36:24 +02:00
// Column Encryption feature: if a custom keystore provider is specified,
2017-08-29 22:42:15 +02:00
// load and configure it when column encryption is enabled, but this step has
2017-08-23 21:36:24 +02:00
// to be executed after the connection has been established
void load_configure_ksp ( _Inout_ sqlsrv_conn * conn TSRMLS_DC )
{
// If column encryption is not enabled simply do nothing. Otherwise, check if a custom keystore provider
// is required for encryption or decryption. Note, in order to load and configure a custom keystore provider,
// all KSP fields in conn->ce_option must be defined.
if ( ! conn - > ce_option . enabled | | ! conn - > ce_option . ksp_required )
return ;
// Do something like the following sample
// use the KSP related fields in conn->ce_option
// CEKEYSTOREDATA is defined in msodbcsql.h
// https://docs.microsoft.com/en-us/sql/connect/odbc/custom-keystore-providers
CHECK_CUSTOM_ERROR ( conn - > ce_option . ksp_name = = NULL , conn , SQLSRV_ERROR_KEYSTORE_NAME_MISSING ) {
throw core : : CoreException ( ) ;
}
CHECK_CUSTOM_ERROR ( conn - > ce_option . ksp_path = = NULL , conn , SQLSRV_ERROR_KEYSTORE_PATH_MISSING ) {
throw core : : CoreException ( ) ;
}
CHECK_CUSTOM_ERROR ( conn - > ce_option . key_size = = 0 , conn , SQLSRV_ERROR_KEYSTORE_KEY_MISSING ) {
throw core : : CoreException ( ) ;
}
char * ksp_name = Z_STRVAL_P ( conn - > ce_option . ksp_name ) ;
char * ksp_path = Z_STRVAL_P ( conn - > ce_option . ksp_path ) ;
unsigned int name_len = Z_STRLEN_P ( conn - > ce_option . ksp_name ) ;
unsigned int key_size = conn - > ce_option . key_size ;
sqlsrv_malloc_auto_ptr < unsigned char > ksp_data ;
ksp_data = reinterpret_cast < unsigned char * > ( sqlsrv_malloc ( sizeof ( CEKEYSTOREDATA ) + key_size ) ) ;
2017-08-29 21:52:01 +02:00
CEKEYSTOREDATA * pKsd = reinterpret_cast < CEKEYSTOREDATA * > ( ksp_data . get ( ) ) ;
2017-08-23 21:36:24 +02:00
pKsd - > dataSize = key_size ;
// First, convert conn->ce_option.ksp_name to a WCHAR version
unsigned int wname_len = 0 ;
sqlsrv_malloc_auto_ptr < SQLWCHAR > wksp_name ;
wksp_name = utf16_string_from_mbcs_string ( SQLSRV_ENCODING_UTF8 , ksp_name , name_len , & wname_len ) ;
CHECK_CUSTOM_ERROR ( wksp_name = = 0 , conn , SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) {
2017-08-29 22:42:15 +02:00
throw core : : CoreException ( ) ;
2017-08-23 21:36:24 +02:00
}
pKsd - > name = ( wchar_t * ) wksp_name . get ( ) ;
// Next, extract the character string from conn->ce_option.ksp_encrypt_key into encrypt_key
char * encrypt_key = Z_STRVAL_P ( conn - > ce_option . ksp_encrypt_key ) ;
memcpy_s ( pKsd - > data , key_size * sizeof ( char ) , encrypt_key , key_size ) ;
2017-08-24 19:55:27 +02:00
core : : SQLSetConnectAttr ( conn , SQL_COPT_SS_CEKEYSTOREPROVIDER , ksp_path , SQL_NTS ) ;
core : : SQLSetConnectAttr ( conn , SQL_COPT_SS_CEKEYSTOREDATA , reinterpret_cast < SQLPOINTER > ( pKsd ) , SQL_IS_POINTER ) ;
2017-08-23 21:36:24 +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 ;
if ( val_len > 0 & & val [ 0 ] = = ' { ' & & val [ val_len - 1 ] = = ' } ' ) {
+ + 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 )
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 ) ;
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
if ( ! driver_name . compare ( driver_option ) ) {
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 ( ) ;
2017-09-15 01:54:59 +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 + = " ; " ;
}
void ce_ksp_provider_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 , " Wrong zval type for this keyword " )
size_t value_len = Z_STRLEN_P ( value ) ;
CHECK_CUSTOM_ERROR ( value_len = = 0 , conn , SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) {
throw core : : CoreException ( ) ;
}
switch ( option - > conn_option_key ) {
case SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER :
conn - > ce_option . ksp_path = value ;
conn - > ce_option . ksp_required = true ;
break ;
case SQLSRV_CONN_OPTION_CEKEYSTORE_NAME :
conn - > ce_option . ksp_name = value ;
conn - > ce_option . ksp_required = true ;
break ;
case SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY :
conn - > ce_option . ksp_encrypt_key = value ;
conn - > ce_option . key_size = value_len ;
conn - > ce_option . ksp_required = true ;
break ;
default :
SQLSRV_ASSERT ( false , " ce_ksp_provider_set_func: Invalid KSP option! " ) ;
break ;
}
}
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.
2017-06-22 23:04:34 +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 ) ;
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
size_t last_char = val_len - 1 ;
2017-06-22 23:04:34 +02:00
while ( isspace ( ( unsigned char ) value_in [ last_char ] ) ) {
2016-04-12 23:43:46 +02:00
value_in [ last_char ] = ' \0 ' ;
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 " ;
if ( ( val_len = = ( sizeof ( VALID_TRUE_VALUE_1 ) - 1 ) & & ! strnicmp ( value_in , VALID_TRUE_VALUE_1 , val_len ) ) | |
( val_len = = ( sizeof ( VALID_TRUE_VALUE_2 ) - 1 ) & & ! strnicmp ( value_in , VALID_TRUE_VALUE_2 , val_len ) )
) {
return 1 ; // true
}
return 0 ; // false
}