xref: /PHP-8.2/ext/oci8/oci8.c (revision 91d3aaaa)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Stig Sæther Bakken <ssb@php.net>                            |
14    |          Thies C. Arntzen <thies@thieso.net>                         |
15    |          Maxim Maletsky <maxim@maxim.cx>                             |
16    |                                                                      |
17    | Collection support by Andy Sautins <asautins@veripost.net>           |
18    | Temporary LOB support by David Benson <dbenson@mancala.com>          |
19    | ZTS per process OCIPLogon by Harald Radi <harald.radi@nme.at>        |
20    |                                                                      |
21    | Redesigned by: Antony Dovgal <antony@zend.com>                       |
22    |                Andi Gutmans <andi@php.net>                           |
23    |                Wez Furlong <wez@omniti.com>                          |
24    +----------------------------------------------------------------------+
25 */
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 
31 #include "php.h"
32 #include "ext/standard/info.h"
33 #include "php_ini.h"
34 #include "zend_attributes.h"
35 #include "zend_smart_str.h"
36 
37 #ifdef HAVE_OCI8
38 
39 /* Note to maintainers: config.m4 should/does check the minimum PHP version so
40  * the below checks are obsolete - but are kept 'just in case'.  Also bump the
41  * minimum version in package.xml, as appropriate. */
42 
43 /* PHP 5.2 is the minimum supported version for OCI8 2.0 */
44 #if PHP_MAJOR_VERSION < 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 1)
45 #error Use PHP OCI8 1.4 for your version of PHP
46 #elif PHP_MAJOR_VERSION < 7
47 /* PHP 7 is the minimum supported version for OCI8 2.1 */
48 #error Use PHP OCI8 2.0 for your version of PHP
49 #elif PHP_MAJOR_VERSION < 8
50 /* PHP 8 is the minimum supported version for OCI8 3 */
51 #error Use PHP OCI8 2.2 for your version of PHP
52 #elif PHP_MAJOR_VERSION == 8 && PHP_MINOR_VERSION < 1
53 #error Use PHP OCI8 3.0 for your version of PHP
54 #elif PHP_MAJOR_VERSION == 8 && PHP_MINOR_VERSION < 2
55 #error Use PHP OCI8 3.2 for your version of PHP
56 #endif
57 
58 #include "php_oci8.h"
59 #include "php_oci8_int.h"
60 #include "zend_hash.h"
61 
62 ZEND_DECLARE_MODULE_GLOBALS(oci)
63 static PHP_GINIT_FUNCTION(oci);
64 static PHP_GSHUTDOWN_FUNCTION(oci);
65 
66 /* Allow PHP 5.3 branch to be used in PECL for 5.x compatible builds */
67 #ifndef Z_ADDREF_P
68 #define Z_ADDREF_P(x) ZVAL_ADDREF(x)
69 #endif
70 
71 /* For a user friendly message about environment setup */
72 #if defined(PHP_WIN32)
73 #define PHP_OCI8_LIB_PATH_MSG "PATH"
74 #elif defined(__APPLE__)
75 #define PHP_OCI8_LIB_PATH_MSG "DYLD_LIBRARY_PATH"
76 #elif defined(_AIX)
77 #define PHP_OCI8_LIB_PATH_MSG "LIBPATH"
78 #elif defined(__hpux)
79 #define PHP_OCI8_LIB_PATH_MSG "SHLIB_PATH"
80 #else
81 #define PHP_OCI8_LIB_PATH_MSG "LD_LIBRARY_PATH"
82 #endif
83 
84 /* True globals, no need for thread safety */
85 int le_connection;
86 int le_pconnection;
87 int le_statement;
88 int le_descriptor;
89 int le_psessionpool;
90 int le_collection;
91 
92 zend_class_entry *oci_lob_class_entry_ptr;
93 zend_class_entry *oci_coll_class_entry_ptr;
94 
95 #ifndef SQLT_BFILEE
96 #define SQLT_BFILEE 114
97 #endif
98 #ifndef SQLT_CFILEE
99 #define SQLT_CFILEE 115
100 #endif
101 
102 #ifdef ZTS
103 #define PHP_OCI_INIT_MODE (OCI_DEFAULT | OCI_OBJECT | OCI_THREADED | OCI_NO_MUTEX)
104 #else
105 #define PHP_OCI_INIT_MODE (OCI_DEFAULT | OCI_OBJECT)
106 #endif
107 
108 /* {{{ static protos */
109 static void php_oci_connection_list_dtor (zend_resource *);
110 static void php_oci_pconnection_list_dtor (zend_resource *);
111 static void php_oci_pconnection_list_np_dtor (zend_resource *);
112 static void php_oci_statement_list_dtor (zend_resource *);
113 static void php_oci_descriptor_list_dtor (zend_resource *);
114 static void php_oci_spool_list_dtor(zend_resource *entry);
115 static void php_oci_collection_list_dtor (zend_resource *);
116 
117 static int php_oci_persistent_helper(zval *zv);
118 static int php_oci_connection_ping(php_oci_connection *);
119 static int php_oci_connection_status(php_oci_connection *);
120 static int php_oci_connection_close(php_oci_connection *);
121 static void php_oci_spool_close(php_oci_spool *session_pool);
122 
123 static OCIEnv *php_oci_create_env(ub2 charsetid);
124 static int php_oci_create_session(php_oci_connection *connection, php_oci_spool *session_pool, char *dbname, int dbname_len, char *username, int username_len, char *password, int password_len, char *new_password, int new_password_len, int session_mode);
125 static int php_oci_old_create_session(php_oci_connection *connection, char *dbname, int dbname_len, char *username, int username_len, char *password, int password_len, char *new_password, int new_password_len, int session_mode);
126 static php_oci_spool *php_oci_get_spool(char *username, int username_len, char *password, int password_len, char *dbname, int dbname_len, int charsetid);
127 static php_oci_spool *php_oci_create_spool(char *username, int username_len, char *password, int password_len, char *dbname, int dbname_len, zend_string *hash_key, int charsetid);
128 static sword php_oci_ping_init(php_oci_connection *connection, OCIError *errh);
129 /* }}} */
130 
131 /* {{{ dynamically loadable module stuff */
132 #if defined(COMPILE_DL_OCI8) || defined(COMPILE_DL_OCI8_11G) || defined(COMPILE_DL_OCI8_12C) || defined(COMPILE_DL_OCI8_19)
133 ZEND_GET_MODULE(oci8)
134 #endif /* COMPILE_DL */
135 /* }}} */
136 
137 #include "oci8_arginfo.h"
138 
PHP_INI_MH(OnUpdateOldCloseSemantics)139 static PHP_INI_MH(OnUpdateOldCloseSemantics)
140 {
141 	bool *p = (bool *) ZEND_INI_GET_ADDR();
142 	*p = zend_ini_parse_bool(new_value);
143 
144 	if (*p) {
145 		zend_error(E_DEPRECATED, "Directive oci8.old_oci_close_semantics is deprecated");
146 	}
147 
148 	return SUCCESS;
149 }
150 
151 /* {{{ extension definition structures */
152 
153 zend_module_entry oci8_module_entry = {
154 	STANDARD_MODULE_HEADER,
155 	"oci8",				  /* extension name */
156 	ext_functions,	      /* extension function list */
157 	PHP_MINIT(oci),		  /* extension-wide startup function */
158 	PHP_MSHUTDOWN(oci),	  /* extension-wide shutdown function */
159 	PHP_RINIT(oci),		  /* per-request startup function */
160 	PHP_RSHUTDOWN(oci),	  /* per-request shutdown function */
161 	PHP_MINFO(oci),		  /* information function */
162 	PHP_OCI8_VERSION,
163 	PHP_MODULE_GLOBALS(oci),  /* globals descriptor */
164 	PHP_GINIT(oci),			  /* globals ctor */
165 	PHP_GSHUTDOWN(oci),		  /* globals dtor */
166 	NULL,					  /* post deactivate */
167 	STANDARD_MODULE_PROPERTIES_EX
168 };
169 /* }}} */
170 
171 /* {{{ PHP_INI */
172 PHP_INI_BEGIN()
173 	STD_PHP_INI_ENTRY(	"oci8.max_persistent",			"-1",	PHP_INI_SYSTEM,	OnUpdateLong,	max_persistent,			zend_oci_globals,	oci_globals)
174 	STD_PHP_INI_ENTRY(	"oci8.persistent_timeout",		"-1",	PHP_INI_SYSTEM,	OnUpdateLong,	persistent_timeout,		zend_oci_globals,	oci_globals)
175 	STD_PHP_INI_ENTRY(	"oci8.ping_interval",			"60",	PHP_INI_SYSTEM,	OnUpdateLong,	ping_interval,			zend_oci_globals,	oci_globals)
176 	STD_PHP_INI_BOOLEAN("oci8.privileged_connect",		"0",	PHP_INI_SYSTEM,	OnUpdateBool,		privileged_connect,		zend_oci_globals,	oci_globals)
177 	STD_PHP_INI_ENTRY(	"oci8.statement_cache_size",	"20",	PHP_INI_SYSTEM,	OnUpdateLong,	statement_cache_size,	zend_oci_globals,	oci_globals)
178 	STD_PHP_INI_ENTRY(	"oci8.default_prefetch",		"100",	PHP_INI_SYSTEM,	OnUpdateLong,	default_prefetch,		zend_oci_globals,	oci_globals)
179 	STD_PHP_INI_BOOLEAN("oci8.old_oci_close_semantics",	"0",	PHP_INI_SYSTEM,	OnUpdateOldCloseSemantics,		old_oci_close_semantics,zend_oci_globals,	oci_globals)
180 	STD_PHP_INI_ENTRY(	"oci8.connection_class",		"",		PHP_INI_ALL,	OnUpdateString,		connection_class,		zend_oci_globals,	oci_globals)
181 	STD_PHP_INI_BOOLEAN("oci8.events",					"0",	PHP_INI_SYSTEM,	OnUpdateBool,		events,					zend_oci_globals,	oci_globals)
182 	STD_PHP_INI_ENTRY(	"oci8.prefetch_lob_size",		"0",	PHP_INI_SYSTEM,	OnUpdateLong,	prefetch_lob_size,		zend_oci_globals,	oci_globals)
PHP_INI_END()183 PHP_INI_END()
184 /* }}} */
185 
186 /* {{{ startup, shutdown and info functions */
187 
188 /* {{{	php_oci_init_global_handles()
189  *
190  * Initialize global handles only when they are needed
191  */
192 static void php_oci_init_global_handles(void)
193 {
194 	sword errstatus;
195 	sb4   ora_error_code = 0;
196 	text  tmp_buf[PHP_OCI_ERRBUF_LEN];  /* Use traditional smaller size: non-PL/SQL errors should fit and it keeps the stack smaller */
197 
198 	errstatus = OCIEnvNlsCreate(&OCI_G(env), PHP_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, 0, 0);
199 
200 	if (errstatus == OCI_ERROR) {
201 #ifdef HAVE_OCI_INSTANT_CLIENT
202 		php_error_docref(NULL, E_WARNING, "OCIEnvNlsCreate() failed. There is something wrong with your system - please check that " PHP_OCI8_LIB_PATH_MSG " includes the directory with Oracle Instant Client libraries");
203 #else
204 		php_error_docref(NULL, E_WARNING, "OCIEnvNlsCreate() failed. There is something wrong with your system - please check that ORACLE_HOME and " PHP_OCI8_LIB_PATH_MSG " are set and point to the right directories");
205 #endif
206 		if (OCI_G(env)
207 			&& OCIErrorGet(OCI_G(env), (ub4)1, NULL, &ora_error_code, tmp_buf, (ub4)PHP_OCI_ERRBUF_LEN, (ub4)OCI_HTYPE_ENV) == OCI_SUCCESS
208 			&& *tmp_buf) {
209 			php_error_docref(NULL, E_WARNING, "%s", tmp_buf);
210 		}
211 
212 		OCI_G(env) = NULL;
213 		OCI_G(err) = NULL;
214 		return;
215 	}
216 
217 	errstatus = OCIHandleAlloc (OCI_G(env), (dvoid **)&OCI_G(err), OCI_HTYPE_ERROR, 0, NULL);
218 
219 	if (errstatus != OCI_SUCCESS) {
220 		OCIErrorGet(OCI_G(env), (ub4)1, NULL, &ora_error_code, tmp_buf, (ub4)PHP_OCI_ERRBUF_LEN, (ub4)OCI_HTYPE_ERROR);
221 
222 		if (ora_error_code) {
223 			int tmp_buf_len = (int) strlen((char *)tmp_buf);
224 
225 			if (tmp_buf_len > 0 && tmp_buf[tmp_buf_len - 1] == '\n') {
226 				tmp_buf[tmp_buf_len - 1] = '\0';
227 			}
228 
229 			if (errstatus == OCI_SUCCESS_WITH_INFO) {
230 				php_error_docref(NULL, E_WARNING, "Initialization error: OCI_SUCCESS_WITH_INFO: %s", tmp_buf);
231 			} else {
232 				php_error_docref(NULL, E_WARNING, "Initialization error: OCI_ERROR: %s", tmp_buf);
233 
234 				OCIHandleFree((dvoid *) OCI_G(env), OCI_HTYPE_ENV);
235 
236 				OCI_G(env) = NULL;
237 				OCI_G(err) = NULL;
238 			}
239 		}
240 	}
241 }
242 /* }}} */
243 
244 /* {{{ PHP_GINIT_FUNCTION
245  *
246  * Zerofill globals during module init
247  */
PHP_GINIT_FUNCTION(oci)248 static PHP_GINIT_FUNCTION(oci)
249 {
250 	memset(oci_globals, 0, sizeof(zend_oci_globals));
251 }
252 /* }}} */
253 
254 /* {{{ PHP_GSHUTDOWN_FUNCTION
255  *
256  * Called for thread shutdown in ZTS, after module shutdown for non-ZTS
257  * Free global handles (if they were initialized before)
258  */
PHP_GSHUTDOWN_FUNCTION(oci)259 static PHP_GSHUTDOWN_FUNCTION(oci)
260 {
261 	if (oci_globals->err) {
262 		oci_globals->in_call = 1;
263 		OCIHandleFree((dvoid *) oci_globals->err, OCI_HTYPE_ERROR);
264 		oci_globals->in_call = 0;
265 		oci_globals->err = NULL;
266 	}
267 
268 	if (oci_globals->env) {
269 		oci_globals->in_call = 1;
270 		OCIHandleFree((dvoid *) oci_globals->env, OCI_HTYPE_ENV);
271 		oci_globals->in_call = 0;
272 		oci_globals->env = NULL;
273 	}
274 }
275 /* }}} */
276 
PHP_MINIT_FUNCTION(oci)277 PHP_MINIT_FUNCTION(oci)
278 {
279 	REGISTER_INI_ENTRIES();
280 
281 	le_statement = zend_register_list_destructors_ex(php_oci_statement_list_dtor, NULL, "oci8 statement", module_number);
282 	le_connection = zend_register_list_destructors_ex(php_oci_connection_list_dtor, NULL, "oci8 connection", module_number);
283 	le_pconnection = zend_register_list_destructors_ex(php_oci_pconnection_list_np_dtor, php_oci_pconnection_list_dtor, "oci8 persistent connection", module_number);
284 	le_psessionpool = zend_register_list_destructors_ex(NULL, php_oci_spool_list_dtor, "oci8 persistent session pool", module_number);
285 	le_descriptor = zend_register_list_destructors_ex(php_oci_descriptor_list_dtor, NULL, "oci8 descriptor", module_number);
286 	le_collection = zend_register_list_destructors_ex(php_oci_collection_list_dtor, NULL, "oci8 collection", module_number);
287 
288 	oci_lob_class_entry_ptr = register_class_OCILob();
289 	oci_coll_class_entry_ptr = register_class_OCICollection();
290 
291 	register_oci8_symbols(module_number);
292 
293 	return SUCCESS;
294 }
295 
PHP_RINIT_FUNCTION(oci)296 PHP_RINIT_FUNCTION(oci)
297 {
298 	OCI_G(num_links) = OCI_G(num_persistent);
299 	OCI_G(errcode) = 0;
300 	OCI_G(edition) = NULL;
301 
302 	return SUCCESS;
303 }
304 
PHP_MSHUTDOWN_FUNCTION(oci)305 PHP_MSHUTDOWN_FUNCTION(oci)
306 {
307 	OCI_G(shutdown) = 1;
308 
309 	UNREGISTER_INI_ENTRIES();
310 
311 	return SUCCESS;
312 }
313 
PHP_RSHUTDOWN_FUNCTION(oci)314 PHP_RSHUTDOWN_FUNCTION(oci)
315 {
316 	/* Check persistent connections and do the necessary actions if needed. If persistent_helper is
317 	 * unable to process a pconnection because of a refcount, the processing would happen from
318 	 * np-destructor which is called when refcount goes to zero - php_oci_pconnection_list_np_dtor
319 	 */
320 	zend_hash_apply(&EG(persistent_list), php_oci_persistent_helper);
321 
322 	if (OCI_G(edition)) {
323 		efree(OCI_G(edition));
324 	}
325 
326 	return SUCCESS;
327 }
328 
PHP_MINFO_FUNCTION(oci)329 PHP_MINFO_FUNCTION(oci)
330 {
331 	char buf[32];
332 	char ver[256];
333 
334 	php_info_print_table_start();
335 	php_info_print_table_row(2, "OCI8 Support", "enabled");
336 #if defined(HAVE_OCI8_DTRACE)
337 	php_info_print_table_row(2, "OCI8 DTrace Support", "enabled");
338 #else
339 	php_info_print_table_row(2, "OCI8 DTrace Support", "disabled");
340 #endif
341 	php_info_print_table_row(2, "OCI8 Version", PHP_OCI8_VERSION);
342 
343 	php_oci_client_get_version(ver, sizeof(ver));
344 	php_info_print_table_row(2, "Oracle Run-time Client Library Version", ver);
345 	snprintf(buf, sizeof(buf), "%d.%d", OCI_MAJOR_VERSION, OCI_MINOR_VERSION);
346 #if defined(HAVE_OCI_INSTANT_CLIENT)
347 	php_info_print_table_row(2, "Oracle Compile-time Instant Client Version", buf);
348 #else
349 	php_info_print_table_row(2, "Oracle Compile-time Version", buf);
350 #endif
351 
352 #if !defined(PHP_WIN32) && !defined(HAVE_OCI_INSTANT_CLIENT)
353 #if defined(PHP_OCI8_DEF_DIR)
354 	php_info_print_table_row(2, "Compile-time ORACLE_HOME", PHP_OCI8_DEF_DIR);
355 #endif
356 #if defined(PHP_OCI8_DEF_SHARED_LIBADD)
357 	php_info_print_table_row(2, "Libraries Used", PHP_OCI8_DEF_SHARED_LIBADD);
358 #endif
359 #endif
360 
361 
362 	php_info_print_table_end();
363 
364 	DISPLAY_INI_ENTRIES();
365 
366 	php_info_print_table_start();
367 	php_info_print_table_header(2, "Statistics", "");
368 	snprintf(buf, sizeof(buf), ZEND_LONG_FMT, OCI_G(num_persistent));
369 	php_info_print_table_row(2, "Active Persistent Connections", buf);
370 	snprintf(buf, sizeof(buf), ZEND_LONG_FMT, OCI_G(num_links));
371 	php_info_print_table_row(2, "Active Connections", buf);
372 	php_info_print_table_end();
373 }
374 /* }}} */
375 
376 /* {{{ list destructors */
377 
378 /* {{{ php_oci_connection_list_dtor()
379  *
380  * Non-persistent connection destructor
381  */
php_oci_connection_list_dtor(zend_resource * entry)382 static void php_oci_connection_list_dtor(zend_resource *entry)
383 {
384 	php_oci_connection *connection = (php_oci_connection *)entry->ptr;
385 
386 	if (connection) {
387 		php_oci_connection_close(connection);
388 		OCI_G(num_links)--;
389 	}
390 }
391 /* }}} */
392 
393 /* {{{ php_oci_pconnection_list_dtor()
394  *
395  * Persistent connection destructor
396  */
php_oci_pconnection_list_dtor(zend_resource * entry)397 static void php_oci_pconnection_list_dtor(zend_resource *entry)
398 {
399 	php_oci_connection *connection = (php_oci_connection *)entry->ptr;
400 
401 	if (connection) {
402 		php_oci_connection_close(connection);
403 		OCI_G(num_persistent)--;
404 		OCI_G(num_links)--;
405 	}
406 }
407 /* }}} */
408 
409 /* {{{ php_oci_pconnection_list_np_dtor()
410  *
411  * Non-Persistent destructor for persistent connection - This gets invoked when
412  * the refcount of this goes to zero in the regular list
413  */
php_oci_pconnection_list_np_dtor(zend_resource * entry)414 static void php_oci_pconnection_list_np_dtor(zend_resource *entry)
415 {
416 	php_oci_connection *connection = (php_oci_connection *)entry->ptr;
417 	zval *zvp;
418 	zend_resource *le;
419 
420 	/*
421 	 * We cannot get connection as NULL or as a stub in this function. This is the function that
422 	 * turns a pconnection to a stub
423 	 *
424 	 * If oci_password_change() changed the password of a persistent connection, close the
425 	 * connection and remove it from the persistent connection cache.  This means subsequent scripts
426 	 * will be prevented from being able to present the old (now invalid) password to a usable
427 	 * connection to the database; they must use the new password.
428 	 *
429 	 * Check for conditions that warrant removal of the hash entry
430 	 */
431 
432 	if (!connection->is_open ||
433 		connection->passwd_changed ||
434 		(PG(connection_status) & PHP_CONNECTION_TIMEOUT) ||
435 		OCI_G(in_call)) {
436 
437 		/* Remove the hash entry if present */
438 		if (connection->hash_key) {
439 			zvp = zend_hash_find(&EG(persistent_list), connection->hash_key);
440 			le = zvp ? Z_RES_P(zvp) : NULL;
441 			if (le != NULL && le->type == le_pconnection && le->ptr == connection) {
442 				zend_hash_del(&EG(persistent_list), connection->hash_key);
443 			}
444 			else {
445 				php_oci_connection_close(connection);
446 				OCI_G(num_persistent)--;
447 			}
448 		}
449 
450 #ifdef HAVE_OCI8_DTRACE
451 		if (DTRACE_OCI8_CONNECT_P_DTOR_CLOSE_ENABLED()) {
452 			DTRACE_OCI8_CONNECT_P_DTOR_CLOSE(connection);
453 		}
454 #endif /* HAVE_OCI8_DTRACE */
455 	} else {
456 		/*
457 		 * Release the connection to underlying pool.  We do this unconditionally so that
458 		 * out-of-scope pconnects are now consistent with oci_close and out-of-scope new connect
459 		 * semantics. With the PECL OCI 1.3.x extensions, we release pconnections when oci_close
460 		 * takes the refcount to zero.
461 		 *
462 		 * If oci_old_close_semantics is set, we artificially bump up the refcount and decremented
463 		 * only at request shutdown.
464 		 */
465 		php_oci_connection_release(connection);
466 
467 #ifdef HAVE_OCI8_DTRACE
468 		if (DTRACE_OCI8_CONNECT_P_DTOR_RELEASE_ENABLED()) {
469 			DTRACE_OCI8_CONNECT_P_DTOR_RELEASE(connection);
470 		}
471 #endif /* HAVE_OCI8_DTRACE */
472 	}
473 }
474 /* }}} */
475 
476 /* {{{ php_oci_statement_list_dtor()
477  *
478  * Statement destructor
479  */
php_oci_statement_list_dtor(zend_resource * entry)480 static void php_oci_statement_list_dtor(zend_resource *entry)
481 {
482 	php_oci_statement *statement = (php_oci_statement *)entry->ptr;
483 	php_oci_statement_free(statement);
484 }
485 /* }}} */
486 
487 /* {{{ php_oci_descriptor_list_dtor()
488  *
489  *	Descriptor destructor
490  */
php_oci_descriptor_list_dtor(zend_resource * entry)491 static void php_oci_descriptor_list_dtor(zend_resource *entry)
492 {
493 	php_oci_descriptor *descriptor = (php_oci_descriptor *)entry->ptr;
494 	php_oci_lob_free(descriptor);
495 }
496 /* }}} */
497 
498 /* {{{ php_oci_collection_list_dtor()
499  *
500  * Collection destructor
501  */
php_oci_collection_list_dtor(zend_resource * entry)502 static void php_oci_collection_list_dtor(zend_resource *entry)
503 {
504 	php_oci_collection *collection = (php_oci_collection *)entry->ptr;
505 	php_oci_collection_close(collection);
506 }
507 /* }}} */
508 
509 /* }}} */
510 
511 /* {{{ Hash Destructors */
512 
513 /* {{{ php_oci_define_hash_dtor()
514  *
515  * Define hash destructor
516  */
php_oci_define_hash_dtor(zval * data)517 void php_oci_define_hash_dtor(zval *data)
518 {
519 	php_oci_define *define = (php_oci_define *) Z_PTR_P(data);
520 
521 	if (define->name) {
522 		efree(define->name);
523 		define->name = NULL;
524 	}
525 
526 	zval_ptr_dtor(&define->val);
527 
528 	efree(define);
529 }
530 /* }}} */
531 
532 /* {{{ php_oci_bind_hash_dtor()
533  *
534  * Bind hash destructor
535  */
php_oci_bind_hash_dtor(zval * data)536 void php_oci_bind_hash_dtor(zval *data)
537 {
538 	php_oci_bind *bind = (php_oci_bind *) Z_PTR_P(data);
539 
540 	if (!Z_ISUNDEF(bind->val)) {
541 		zval_ptr_dtor(&bind->val);
542 		ZVAL_UNDEF(&bind->val);
543 	}
544 
545 	if (bind->array.elements) {
546 		efree(bind->array.elements);
547 		bind->array.elements = NULL;
548 	}
549 
550 	if (bind->array.element_lengths) {
551 		efree(bind->array.element_lengths);
552 		bind->array.element_lengths = NULL;
553 	}
554 
555 	if (bind->array.indicators) {
556 		efree(bind->array.indicators);
557 		bind->array.indicators = NULL;
558 	}
559 
560 	efree(bind);
561 }
562 /* }}} */
563 
564 /* {{{ php_oci_column_hash_dtor()
565  *
566  * Column hash destructor
567  */
php_oci_column_hash_dtor(zval * data)568 void php_oci_column_hash_dtor(zval *data)
569 {
570 	php_oci_out_column *column = (php_oci_out_column *) Z_PTR_P(data);
571 
572 	if (column->stmtid) {
573 		zend_list_close(column->stmtid);
574 	}
575 
576 	if (column->descid) {
577 		if (GC_REFCOUNT(column->descid) == 1)
578 			zend_list_close(column->descid);
579 		else {
580 			GC_DELREF(column->descid);
581 		}
582 	}
583 
584 	if (column->data) {
585 		efree(column->data);
586 	}
587 
588 	if (column->name) {
589 		efree(column->name);
590 	}
591 
592 	efree(column);
593 }
594 /* }}} */
595 
596 /* {{{ php_oci_descriptor_flush_hash_dtor()
597  *
598  * Flush descriptors on commit
599  */
php_oci_descriptor_flush_hash_dtor(zval * data)600 void php_oci_descriptor_flush_hash_dtor(zval *data)
601 {
602 	php_oci_descriptor *descriptor = (php_oci_descriptor *) Z_PTR_P(data);
603 
604 	if (descriptor && descriptor->buffering == PHP_OCI_LOB_BUFFER_USED && (descriptor->type == OCI_DTYPE_LOB || descriptor->type == OCI_DTYPE_FILE)) {
605 		php_oci_lob_flush(descriptor, OCI_LOB_BUFFER_FREE);
606 		descriptor->buffering = PHP_OCI_LOB_BUFFER_ENABLED;
607 	}
608 	data = NULL;
609 }
610 /* }}} */
611 
612 /* }}} */
613 
614 /* {{{ php_oci_connection_descriptors_free()
615  *
616  * Free descriptors for a connection
617  */
php_oci_connection_descriptors_free(php_oci_connection * connection)618 void php_oci_connection_descriptors_free(php_oci_connection *connection)
619 {
620 	zend_hash_destroy(connection->descriptors);
621 	efree(connection->descriptors);
622 	connection->descriptors = NULL;
623 	connection->descriptor_count = 0;
624 }
625 /* }}} */
626 
627 /* {{{ php_oci_error()
628  *
629  * Fetch & print out error message if we get an error
630  * Returns an Oracle error number
631  */
php_oci_error(OCIError * err_p,sword errstatus)632 sb4 php_oci_error(OCIError *err_p, sword errstatus)
633 {
634 	text errbuf[PHP_OCI_ERRBUF_LEN];
635 	sb4 errcode = 0; /* Oracle error number */
636 
637 	switch (errstatus) {
638 		case OCI_SUCCESS:
639 			break;
640 		case OCI_SUCCESS_WITH_INFO:
641 			errcode = php_oci_fetch_errmsg(err_p, errbuf, sizeof(errbuf));
642 			if (errcode) {
643 				php_error_docref(NULL, E_WARNING, "OCI_SUCCESS_WITH_INFO: %s", errbuf);
644 			} else {
645 				php_error_docref(NULL, E_WARNING, "OCI_SUCCESS_WITH_INFO: failed to fetch error message");
646 			}
647 			break;
648 		case OCI_NEED_DATA:
649 			php_error_docref(NULL, E_WARNING, "OCI_NEED_DATA");
650 			break;
651 		case OCI_NO_DATA:
652 			errcode = php_oci_fetch_errmsg(err_p, errbuf, sizeof(errbuf));
653 			if (errcode) {
654 				php_error_docref(NULL, E_WARNING, "%s", errbuf);
655 			} else {
656 				php_error_docref(NULL, E_WARNING, "OCI_NO_DATA: failed to fetch error message");
657 			}
658 			break;
659 		case OCI_ERROR:
660 			errcode = php_oci_fetch_errmsg(err_p, errbuf, sizeof(errbuf));
661 			if (errcode) {
662 				php_error_docref(NULL, E_WARNING, "%s", errbuf);
663 			} else {
664 				php_error_docref(NULL, E_WARNING, "Failed to fetch error message");
665 			}
666 			break;
667 		case OCI_INVALID_HANDLE:
668 			php_error_docref(NULL, E_WARNING, "OCI_INVALID_HANDLE");
669 			break;
670 		case OCI_STILL_EXECUTING:
671 			php_error_docref(NULL, E_WARNING, "OCI_STILL_EXECUTING");
672 			break;
673 		case OCI_CONTINUE:
674 			php_error_docref(NULL, E_WARNING, "OCI_CONTINUE");
675 			break;
676 		default:
677 			php_error_docref(NULL, E_WARNING, "Unknown OCI error code: %d", errstatus);
678 			break;
679 	}
680 
681 #ifdef HAVE_OCI8_DTRACE
682 	if (DTRACE_OCI8_ERROR_ENABLED()) {
683 		DTRACE_OCI8_ERROR((int)errstatus, (long)errcode);
684 	}
685 #endif /* HAVE_OCI8_DTRACE */
686 
687 	return errcode;
688 }
689 /* }}} */
690 
691 /* {{{ php_oci_fetch_errmsg()
692  *
693  * Fetch error message into the buffer from the error handle provided
694  */
php_oci_fetch_errmsg(OCIError * error_handle,text * error_buf,size_t error_buf_size)695 sb4 php_oci_fetch_errmsg(OCIError *error_handle, text *error_buf, size_t error_buf_size)
696 {
697 	sb4 error_code = 0;
698 
699 	PHP_OCI_CALL(OCIErrorGet, (error_handle, (ub4)1, NULL, &error_code, error_buf, (ub4)error_buf_size, (ub4)OCI_HTYPE_ERROR));
700 
701 	if (error_code) {
702 		int err_buf_len = (int) strlen((char *)error_buf);
703 
704 		if (err_buf_len && error_buf[err_buf_len - 1] == '\n') {
705 			error_buf[err_buf_len - 1] = '\0';
706 		}
707 	}
708 	return error_code;
709 }
710 /* }}} */
711 
712 /* {{{ php_oci_fetch_sqltext_offset()
713  *
714  * Compute offset in the SQL statement
715  */
php_oci_fetch_sqltext_offset(php_oci_statement * statement,text ** sqltext,ub2 * error_offset)716 int php_oci_fetch_sqltext_offset(php_oci_statement *statement, text **sqltext, ub2 *error_offset)
717 {
718 	sword errstatus;
719 
720 	*sqltext = NULL;
721 	*error_offset = 0;
722 	PHP_OCI_CALL_RETURN(errstatus, OCIAttrGet, ((dvoid *)statement->stmt, OCI_HTYPE_STMT, (dvoid *) sqltext, (ub4 *)0, OCI_ATTR_STATEMENT, statement->err));
723 
724 	if (errstatus != OCI_SUCCESS) {
725 		statement->errcode = php_oci_error(statement->err, errstatus);
726 		PHP_OCI_HANDLE_ERROR(statement->connection, statement->errcode);
727 		return 1;
728 	}
729 
730 	PHP_OCI_CALL_RETURN(errstatus, OCIAttrGet, ((dvoid *)statement->stmt, OCI_HTYPE_STMT, (ub2 *)error_offset, (ub4 *)0, OCI_ATTR_PARSE_ERROR_OFFSET, statement->err));
731 
732 	if (errstatus != OCI_SUCCESS) {
733 		statement->errcode = php_oci_error(statement->err, errstatus);
734 		PHP_OCI_HANDLE_ERROR(statement->connection, statement->errcode);
735 		return 1;
736 	}
737 	return 0;
738 }
739 /* }}} */
740 
741 /* {{{ php_oci_do_connect()
742  *
743  * Connect wrapper
744  */
php_oci_do_connect(INTERNAL_FUNCTION_PARAMETERS,int persistent,int exclusive)745 void php_oci_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent, int exclusive)
746 {
747 	php_oci_connection *connection;
748 	char *username, *password;
749 	char *dbname = NULL, *charset = NULL;
750 	size_t username_len = 0, password_len = 0;
751 	size_t dbname_len = 0, charset_len = 0;
752 	zend_long session_mode = OCI_DEFAULT;
753 
754 	/* if a fourth parameter is handed over, it is the charset identifier (but is only used in Oracle 9i+) */
755 	ZEND_PARSE_PARAMETERS_START(2, 5)
756 		Z_PARAM_STRING(username, username_len)
757 		Z_PARAM_STRING(password, password_len)
758 		Z_PARAM_OPTIONAL
759 		Z_PARAM_STRING_OR_NULL(dbname, dbname_len)
760 		Z_PARAM_STRING(charset, charset_len)
761 		Z_PARAM_LONG(session_mode)
762 	ZEND_PARSE_PARAMETERS_END();
763 
764 #ifdef HAVE_OCI8_DTRACE
765 	if (DTRACE_OCI8_CONNECT_ENTRY_ENABLED()) {
766 		DTRACE_OCI8_CONNECT_ENTRY(username, dbname, charset, session_mode, persistent, exclusive);
767 	}
768 #endif /* HAVE_OCI8_DTRACE */
769 
770 	if (!charset_len) {
771 		charset = NULL;
772 	}
773 
774 	connection = php_oci_do_connect_ex(username, (int) username_len, password, (int) password_len, NULL, 0, dbname, (int) dbname_len, charset, session_mode, persistent, exclusive);
775 
776 #ifdef HAVE_OCI8_DTRACE
777 	if (DTRACE_OCI8_CONNECT_RETURN_ENABLED()) {
778 		DTRACE_OCI8_CONNECT_RETURN(connection);
779 	}
780 #endif /* HAVE_OCI8_DTRACE */
781 
782 
783 	if (!connection) {
784 		RETURN_FALSE;
785 	}
786 	RETURN_RES(connection->id);
787 
788 }
789 /* }}} */
790 
791 /* {{{ php_oci_do_connect_ex()
792  *
793  * The real connect function. Allocates all the resources needed, establishes the connection and
794  * returns the result handle (or NULL)
795  */
php_oci_do_connect_ex(char * username,int username_len,char * password,int password_len,char * new_password,int new_password_len,char * dbname,int dbname_len,char * charset,zend_long session_mode,int persistent,int exclusive)796 php_oci_connection *php_oci_do_connect_ex(char *username, int username_len, char *password, int password_len, char *new_password, int new_password_len, char *dbname, int dbname_len, char *charset, zend_long session_mode, int persistent, int exclusive)
797 {
798 	zval *zvp;
799 	zend_resource *le = NULL;
800 	zend_resource new_le;
801 	php_oci_connection *connection = NULL;
802 	smart_str hashed_details = {0};
803 	time_t timestamp;
804 	php_oci_spool *session_pool = NULL;
805 	bool use_spool = 1;	   /* Default is to use client-side session pool */
806 	bool ping_done = 0;
807 
808 	ub2 charsetid = 0;
809 	ub2 charsetid_nls_lang = 0;
810 
811 	if (session_mode & ~(OCI_SYSOPER | OCI_SYSDBA | PHP_OCI_CRED_EXT)) {
812 		php_error_docref(NULL, E_WARNING, "Invalid session mode specified (" ZEND_LONG_FMT ")", session_mode);
813 		return NULL;
814 	}
815 	if (session_mode & (OCI_SYSOPER | OCI_SYSDBA | PHP_OCI_CRED_EXT)) {
816 		if ((session_mode & OCI_SYSOPER) && (session_mode & OCI_SYSDBA)) {
817 			php_error_docref(NULL, E_WARNING, "OCI_SYSDBA and OCI_SYSOPER cannot be used together");
818 			return NULL;
819 		}
820 		if (session_mode & PHP_OCI_CRED_EXT) {
821 #ifdef PHP_WIN32
822 			/* Disable external authentication on Windows as Impersonation is not yet handled.
823 			 * TODO: Re-enable this once OCI provides capability.
824 			 */
825 			php_error_docref(NULL, E_WARNING, "External Authentication is not supported on Windows");
826 			return NULL;
827 #endif
828 			if (username_len != 1 || username[0] != '/' || password_len != 0) {
829 				php_error_docref(NULL, E_WARNING, "OCI_CRED_EXT can only be used with a username of \"/\" and a NULL password");
830 				return NULL;
831 			}
832 		}
833 		if (session_mode & (OCI_SYSOPER | OCI_SYSDBA)) {
834 			/* Increase security by not caching privileged oci_pconnect() connections. The
835 			 * connection becomes equivalent to oci_connect() or oci_new_connect().
836 			 */
837 			persistent = 0;
838 			if (!OCI_G(privileged_connect)) {
839 				php_error_docref(NULL, E_WARNING, "Privileged connect is disabled. Enable oci8.privileged_connect to be able to connect as SYSOPER or SYSDBA");
840 				return NULL;
841 			}
842 		}
843 	}
844 
845 	/* Initialize global handles if they weren't initialized before */
846 	if (OCI_G(env) == NULL) {
847 		php_oci_init_global_handles();
848 		if (OCI_G(env) == NULL) {
849 			return NULL;
850 		}
851 	}
852 
853 	/* We cannot use the new session create logic (OCISessionGet from
854 	 * client-side session pool) when privileged connect or password
855 	 * change is attempted or OCI_CRED_EXT mode is specified.
856 	 * TODO: Re-enable this when OCI provides support.
857 	 */
858 	if ((session_mode & (OCI_SYSOPER | OCI_SYSDBA | PHP_OCI_CRED_EXT)) || (new_password_len)) {
859 		use_spool = 0;
860 	}
861 
862 	smart_str_appendl_ex(&hashed_details, "oci8***", sizeof("oci8***") - 1, 0);
863 	smart_str_appendl_ex(&hashed_details, username, username_len, 0);
864 	smart_str_appendl_ex(&hashed_details, "**", sizeof("**") - 1, 0);
865 
866 	/* DRCP: connection_class is an attribute of a connection */
867 	if (OCI_G(connection_class)){
868 		smart_str_appendl_ex(&hashed_details, OCI_G(connection_class), strlen(OCI_G(connection_class)), 0);
869 	}
870 	smart_str_appendl_ex(&hashed_details, "**", sizeof("**") - 1, 0);
871 
872 	/* Add edition attribute to the hash */
873 	if (OCI_G(edition)){
874 		smart_str_appendl_ex(&hashed_details, OCI_G(edition), strlen(OCI_G(edition)), 0);
875 	}
876 	smart_str_appendl_ex(&hashed_details, "**", sizeof("**") - 1, 0);
877 
878 	if (password_len) {
879 		zend_ulong password_hash;
880 		password_hash = zend_hash_func(password, password_len);
881 		smart_str_append_unsigned_ex(&hashed_details, password_hash, 0);
882 	}
883 	smart_str_appendl_ex(&hashed_details, "**", sizeof("**") - 1, 0);
884 
885 	if (dbname) {
886 		smart_str_appendl_ex(&hashed_details, dbname, dbname_len, 0);
887 	}
888 	smart_str_appendl_ex(&hashed_details, "**", sizeof("**") - 1, 0);
889 
890 	if (charset && *charset) {
891 		PHP_OCI_CALL_RETURN(charsetid, OCINlsCharSetNameToId, (OCI_G(env), (CONST oratext *)charset));
892 		if (!charsetid) {
893 			php_error_docref(NULL, E_WARNING, "Invalid character set name: %s", charset);
894 		} else {
895 			smart_str_append_unsigned_ex(&hashed_details, charsetid, 0);
896 		}
897 	}
898 
899 	/* use NLS_LANG if no or invalid charset specified */
900 	if (!charsetid) {
901 		size_t rsize = 0;
902 		sword result;
903 
904 		PHP_OCI_CALL_RETURN(result, OCINlsEnvironmentVariableGet, (&charsetid_nls_lang, 0, OCI_NLS_CHARSET_ID, 0, &rsize));
905 		if (result != OCI_SUCCESS) {
906 			charsetid_nls_lang = 0;
907 		}
908 		smart_str_append_unsigned_ex(&hashed_details, charsetid_nls_lang, 0);
909 	}
910 
911 	timestamp = time(NULL);
912 
913 	smart_str_append_unsigned_ex(&hashed_details, session_mode, 0);
914 	if (persistent) {
915 		smart_str_appendl_ex(&hashed_details, "pc", sizeof("pc") - 1, 0);
916 	}
917 
918 	smart_str_0(&hashed_details);
919 
920 	/* make it lowercase */
921 	zend_str_tolower(ZSTR_VAL(hashed_details.s), ZSTR_LEN(hashed_details.s));
922 
923 	if (!exclusive && !new_password) {
924 		bool found = 0;
925 
926 		if (persistent && ((zvp = zend_hash_find(&EG(persistent_list), hashed_details.s))) != NULL) {
927 			zend_resource *le = Z_RES_P(zvp);
928 
929 			found = 1;
930 			/* found */
931 			if (le->type == le_pconnection) {
932 				connection = (php_oci_connection *)le->ptr;
933 			}
934 		} else if (!persistent && ((zvp = zend_hash_find(&EG(regular_list), hashed_details.s)) != NULL)) {
935 			le = Z_RES_P(zvp);
936 			found = 1;
937 			if (le->type == le_index_ptr) {
938 				zend_resource *ptr;
939 
940 				ptr = (zend_resource *) le->ptr;
941 				if (ptr && (ptr->type == le_connection)) {
942 					connection = (php_oci_connection *)ptr->ptr;
943 				}
944 			}
945 		}
946 
947 #ifdef HAVE_OCI8_DTRACE
948 		if (DTRACE_OCI8_CONNECT_LOOKUP_ENABLED()) {
949 			DTRACE_OCI8_CONNECT_LOOKUP(connection, connection && connection->is_stub ? 1 : 0);
950 		}
951 #endif /* HAVE_OCI8_DTRACE */
952 
953 		/* If we got a pconnection stub, then 'load'(OCISessionGet) the real connection from its
954 		 * private spool A connection is a stub if it is only a cached structure and the real
955 		 * connection is released to its underlying private session pool.  We currently do not have
956 		 * stub support for non-persistent conns.
957 		 *
958 		 * TODO: put in negative code for non-persistent stubs
959 		 */
960 		if (connection && connection->is_persistent && connection->is_stub) {
961 			if (php_oci_create_session(connection, NULL, dbname, dbname_len, username, username_len, password, password_len, new_password, new_password_len, (int) session_mode)) {
962 				smart_str_free(&hashed_details);
963 				zend_hash_del(&EG(persistent_list), connection->hash_key);
964 
965 				return NULL;
966 			}
967 			/* We do the ping in php_oci_create_session, no need to ping again below */
968 			ping_done = 1;
969 		}
970 
971 		if (connection) {
972 			if (connection->is_open) {
973 				/* found an open connection. now ping it */
974 				if (connection->is_persistent) {
975 
976 					/* Check connection liveness in the following order:
977 					 * 1) always check OCI_ATTR_SERVER_STATUS
978 					 * 2) see if it's time to ping it
979 					 * 3) ping it if needed
980 					 */
981 					if (php_oci_connection_status(connection)) {
982 						/* Only ping if:
983 						 *
984 						 * 1) next_ping > 0, which means that ping_interval is not -1 (aka "Off")
985 						 *
986 						 * 2) current_timestamp > next_ping, which means "it's time to check if it's
987 						 * still alive"
988 						 */
989 						if (!ping_done && (*(connection->next_pingp) > 0) && (timestamp >= *(connection->next_pingp)) && !php_oci_connection_ping(connection)) {
990 							/* server died */
991 						} else {
992 							php_oci_connection *tmp = (php_oci_connection *) NULL;
993 							zval *tmp_val = (zval *) NULL;
994 
995 							/* okay, the connection is open and the server is still alive */
996 							connection->used_this_request = 1;
997 							if (connection->id) {
998 								tmp_val = zend_hash_index_find(&EG(regular_list), connection->id->handle);
999 								if ((tmp_val != NULL) && (Z_TYPE_P(tmp_val) == IS_RESOURCE)) {
1000 									tmp = Z_RES_VAL_P(tmp_val);
1001 								}
1002 
1003 								if ((tmp_val != NULL) && (tmp != NULL) &&
1004 									zend_string_equals(tmp->hash_key, hashed_details.s)) {
1005 									connection = tmp;
1006 									GC_ADDREF(connection->id);
1007 								}
1008 							} else {
1009 								PHP_OCI_REGISTER_RESOURCE(connection, le_pconnection);
1010 								/* Persistent connections: For old close semantics we artificially
1011 								 * bump up the refcount to prevent the non-persistent destructor
1012 								 * from getting called until request shutdown. The refcount is
1013 								 * decremented in the persistent helper
1014 								 */
1015 								if (OCI_G(old_oci_close_semantics)) {
1016 									GC_ADDREF(connection->id);
1017 								}
1018 							}
1019 							smart_str_free(&hashed_details);
1020 							return connection;
1021 						}
1022 					}
1023 					/* server died */
1024 				} else {
1025 					/* we do not ping non-persistent connections */
1026 					smart_str_free(&hashed_details);
1027 					GC_ADDREF(connection->id);
1028 					return connection;
1029 				}
1030 			} /* is_open is true? */
1031 
1032 			/* Server died - connection not usable. The is_open=true can also fall through to here,
1033 			 * if ping fails
1034 			 */
1035 			if (persistent){
1036 
1037 				connection->is_open = 0;
1038 				connection->used_this_request = 1;
1039 
1040 				/* We have to do a hash_del but need to preserve the resource if there is a positive
1041 				 * refcount. Set the data pointer in the list entry to NULL
1042 				 */
1043 				if (connection == connection->id->ptr && le) {
1044 					le->ptr = NULL;
1045 				}
1046 
1047 				zend_hash_del(&EG(persistent_list), hashed_details.s);
1048 			} else {
1049 				/* We only remove the hash entry. The resource and the list entry with its pointer
1050 				 * to the resource are still intact
1051 				 */
1052 				zend_hash_del(&EG(regular_list), hashed_details.s);
1053 			}
1054 
1055 			connection = NULL;
1056 		} else if (found) {
1057 			/* found something, but it's not a connection, delete it */
1058 			if (persistent) {
1059 				zend_hash_del(&EG(persistent_list), hashed_details.s);
1060 			} else {
1061 				zend_hash_del(&EG(regular_list), hashed_details.s);
1062 			}
1063 		}
1064 	}
1065 
1066 	/* Check if we have reached max_persistent. If so, try to remove a few timed-out connections. As
1067 	 * a last resort, return a non-persistent connection.
1068 	 */
1069 	if (persistent) {
1070 		bool alloc_non_persistent = 0;
1071 
1072 		if (OCI_G(max_persistent) != -1 && OCI_G(num_persistent) >= OCI_G(max_persistent)) {
1073 			/* try to find an idle connection and kill it */
1074 			zend_hash_apply(&EG(persistent_list), php_oci_persistent_helper);
1075 
1076 			if (OCI_G(max_persistent) != -1 && OCI_G(num_persistent) >= OCI_G(max_persistent)) {
1077 				/* all persistent connactions are in use, fallback to non-persistent connection creation */
1078 				php_error_docref(NULL, E_NOTICE, "Too many open persistent connections (" ZEND_LONG_FMT ")", OCI_G(num_persistent));
1079 				alloc_non_persistent = 1;
1080 			}
1081 		}
1082 
1083 		if (alloc_non_persistent) {
1084 			connection = (php_oci_connection *) ecalloc(1, sizeof(php_oci_connection));
1085 			connection->hash_key = zend_string_dup(hashed_details.s, 0);
1086 			connection->is_persistent = 0;
1087 			ZVAL_UNDEF(&connection->taf_callback);
1088 #ifdef HAVE_OCI8_DTRACE
1089 			connection->client_id = NULL;
1090 #endif
1091 		} else {
1092 			connection = (php_oci_connection *) calloc(1, sizeof(php_oci_connection));
1093 			if (connection == NULL) {
1094 				return NULL;
1095 			}
1096 			connection->hash_key = zend_string_dup(hashed_details.s, 1);
1097 			if (connection->hash_key == NULL) {
1098 				free(connection);
1099 				return NULL;
1100 			}
1101 			connection->is_persistent = 1;
1102 			ZVAL_UNDEF(&connection->taf_callback);
1103 #ifdef HAVE_OCI8_DTRACE
1104 			connection->client_id = NULL;
1105 #endif
1106 		}
1107 	} else {
1108 		connection = (php_oci_connection *) ecalloc(1, sizeof(php_oci_connection));
1109 		connection->hash_key = zend_string_dup(hashed_details.s, 0);
1110 		connection->is_persistent = 0;
1111 		ZVAL_UNDEF(&connection->taf_callback);
1112 #ifdef HAVE_OCI8_DTRACE
1113 		connection->client_id = NULL;
1114 #endif
1115 	}
1116 
1117 	/* {{{ Get the session pool that suits this connection request from the persistent list. This
1118 	 * step is only for non-persistent connections as persistent connections have private session
1119 	 * pools. Non-persistent conns use shared session pool to allow for optimizations such as
1120 	 * caching the physical connection (for DRCP) even when the non-persistent php connection is
1121 	 * destroyed.
1122 	 *
1123 	 * TODO: Unconditionally do this once OCI provides extended OCISessionGet capability
1124 	 */
1125 	if (use_spool && !connection->is_persistent) {
1126 		if ((session_pool = php_oci_get_spool(username, username_len, password, password_len, dbname, dbname_len, charsetid ? charsetid:charsetid_nls_lang))==NULL)
1127 		{
1128 			php_oci_connection_close(connection);
1129 			smart_str_free(&hashed_details);
1130 			return NULL;
1131 		}
1132 	}
1133 	/* }}} */
1134 
1135 	connection->idle_expiry = (OCI_G(persistent_timeout) > 0) ? (timestamp + OCI_G(persistent_timeout)) : 0;
1136 
1137 	/* Mark password as unchanged by PHP during the duration of the database session */
1138 	connection->passwd_changed = 0;
1139 
1140 	smart_str_free(&hashed_details);
1141 
1142 	if (charsetid) {
1143 		connection->charset = charsetid;
1144 	} else {
1145 		connection->charset = charsetid_nls_lang;
1146 	}
1147 
1148 	/* Old session creation semantics when session pool cannot be used Eg: privileged
1149 	 * connect/password change
1150 	 */
1151 	if (!use_spool) {
1152 		if (php_oci_old_create_session(connection, dbname, dbname_len, username, username_len, password, password_len, new_password, new_password_len, (int) session_mode)) {
1153 			php_oci_connection_close(connection);
1154 			return NULL;
1155 		}
1156 	} else {
1157 		/* create using the client-side session pool */
1158 		if (php_oci_create_session(connection, session_pool, dbname, dbname_len, username, username_len, password, password_len, new_password, new_password_len, (int) session_mode)) {
1159 			php_oci_connection_close(connection);
1160 			return NULL;
1161 		}
1162 	}
1163 
1164 	/* Mark it as open */
1165 	connection->is_open = 1;
1166 
1167 	/* add to the appropriate hash */
1168 	if (connection->is_persistent) {
1169 		connection->used_this_request = 1;
1170 		PHP_OCI_REGISTER_RESOURCE(connection, le_pconnection);
1171 
1172 		/* Persistent connections: For old close semantics we artificially bump up the refcount to
1173 		 * prevent the non-persistent destructor from getting called until request shutdown. The
1174 		 * refcount is decremented in the persistent helper
1175 		 */
1176 		if (OCI_G(old_oci_close_semantics)) {
1177 			GC_ADDREF(connection->id);
1178 		}
1179 		zend_register_persistent_resource_ex(connection->hash_key, connection, le_pconnection);
1180 		OCI_G(num_persistent)++;
1181 		OCI_G(num_links)++;
1182 	} else if (!exclusive) {
1183 		PHP_OCI_REGISTER_RESOURCE(connection, le_connection);
1184 		new_le.ptr = connection->id;
1185 		new_le.type = le_index_ptr;
1186 		zend_hash_update_mem(&EG(regular_list), connection->hash_key, (void *)&new_le, sizeof(zend_resource));
1187 		OCI_G(num_links)++;
1188 	} else {
1189 		PHP_OCI_REGISTER_RESOURCE(connection, le_connection);
1190 		OCI_G(num_links)++;
1191 	}
1192 
1193 #ifdef HAVE_OCI8_DTRACE
1194 	if (DTRACE_OCI8_CONNECT_TYPE_ENABLED()) {
1195 		DTRACE_OCI8_CONNECT_TYPE(connection->is_persistent ? 1 : 0, exclusive ? 1 : 0, connection, OCI_G(num_persistent), OCI_G(num_links));
1196 	}
1197 #endif /* HAVE_OCI8_DTRACE */
1198 
1199 	return connection;
1200 }
1201 /* }}} */
1202 
1203 /* {{{ php_oci_connection_ping()
1204  *
1205  * Ping connection. Uses OCIPing() or OCIServerVersion() depending on the Oracle Client version
1206  */
php_oci_connection_ping(php_oci_connection * connection)1207 static int php_oci_connection_ping(php_oci_connection *connection)
1208 {
1209 	sword errstatus;
1210 
1211 	OCI_G(errcode) = 0;  		/* assume ping is successful */
1212 
1213 	/* If OCIPing returns ORA-1010 (invalid OCI operation) such as from
1214 	 * pre-10.1 servers, the error is still from the server and we would have
1215 	 * successfully performed a roundtrip and validated the connection.
1216 	 */
1217 	PHP_OCI_CALL_RETURN(errstatus, OCIPing, (connection->svc, OCI_G(err), OCI_DEFAULT));
1218 
1219 	if (errstatus == OCI_SUCCESS) {
1220 		return 1;
1221 	} else {
1222 		sb4 error_code = 0;
1223 		text tmp_buf[PHP_OCI_ERRBUF_LEN];
1224 
1225 		/* Treat ORA-1010 as a successful Ping */
1226 		OCIErrorGet(OCI_G(err), (ub4)1, NULL, &error_code, tmp_buf, (ub4)PHP_OCI_ERRBUF_LEN, (ub4)OCI_HTYPE_ERROR);
1227 		if (error_code == 1010) {
1228 			return 1;
1229 		}
1230 		OCI_G(errcode) = error_code;
1231 	}
1232 
1233 	return 0;
1234 }
1235 /* }}} */
1236 
1237 /* {{{ php_oci_connection_status()
1238  *
1239  * Check connection status (pre-ping check)
1240  */
php_oci_connection_status(php_oci_connection * connection)1241 static int php_oci_connection_status(php_oci_connection *connection)
1242 {
1243 	ub4 ss = OCI_SERVER_NOT_CONNECTED;
1244 	sword errstatus;
1245 
1246 	/* get OCI_ATTR_SERVER_STATUS */
1247 	PHP_OCI_CALL_RETURN(errstatus, OCIAttrGet, ((dvoid *)connection->server, OCI_HTYPE_SERVER, (dvoid *)&ss, (ub4 *)0, OCI_ATTR_SERVER_STATUS, OCI_G(err)));
1248 
1249 	if (errstatus == OCI_SUCCESS && ss == OCI_SERVER_NORMAL) {
1250 		return 1;
1251 	}
1252 
1253 	/* ignore errors here, just return failure */
1254 	return 0;
1255 }
1256 /* }}} */
1257 
1258 /* {{{ php_oci_connection_rollback()
1259  *
1260  * Rollback connection
1261  */
php_oci_connection_rollback(php_oci_connection * connection)1262 int php_oci_connection_rollback(php_oci_connection *connection)
1263 {
1264 	sword errstatus;
1265 
1266 	PHP_OCI_CALL_RETURN(errstatus, OCITransRollback, (connection->svc, connection->err, (ub4) 0));
1267 	connection->rb_on_disconnect = 0;
1268 
1269 	if (errstatus != OCI_SUCCESS) {
1270 		connection->errcode = php_oci_error(connection->err, errstatus);
1271 		PHP_OCI_HANDLE_ERROR(connection, connection->errcode);
1272 		return 1;
1273 	}
1274 	connection->errcode = 0; /* retain backwards compat with OCI8 1.4 */
1275 	return 0;
1276 }
1277 /* }}} */
1278 
1279 /* {{{ php_oci_connection_commit()
1280  *
1281  * Commit connection
1282  */
php_oci_connection_commit(php_oci_connection * connection)1283 int php_oci_connection_commit(php_oci_connection *connection)
1284 {
1285 	sword errstatus;
1286 
1287 	PHP_OCI_CALL_RETURN(errstatus, OCITransCommit, (connection->svc, connection->err, (ub4) 0));
1288 	connection->rb_on_disconnect = 0;
1289 
1290 	if (errstatus != OCI_SUCCESS) {
1291 		connection->errcode = php_oci_error(connection->err, errstatus);
1292 		PHP_OCI_HANDLE_ERROR(connection, connection->errcode);
1293 		return 1;
1294 	}
1295 	connection->errcode = 0; /* retain backwards compat with OCI8 1.4 */
1296 	return 0;
1297 }
1298 /* }}} */
1299 
1300 /* {{{ php_oci_connection_close()
1301  *
1302  * Close the connection and free all its resources
1303  */
php_oci_connection_close(php_oci_connection * connection)1304 static int php_oci_connection_close(php_oci_connection *connection)
1305 {
1306 	int result = 0;
1307 	bool in_call_save = OCI_G(in_call);
1308 
1309 #ifdef HAVE_OCI8_DTRACE
1310 	if (DTRACE_OCI8_CONNECTION_CLOSE_ENABLED()) {
1311 		DTRACE_OCI8_CONNECTION_CLOSE(connection);
1312 	}
1313 #endif /* HAVE_OCI8_DTRACE */
1314 
1315 	if (!connection->is_stub) {
1316 		/* Release resources associated with connection */
1317 		php_oci_connection_release(connection);
1318 	}
1319 
1320 	if (!connection->using_spool && connection->svc) {
1321 		PHP_OCI_CALL(OCISessionEnd, (connection->svc, connection->err, connection->session, (ub4) 0));
1322 	}
1323 
1324 	if (connection->err) {
1325 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->err, (ub4) OCI_HTYPE_ERROR));
1326 	}
1327 	if (connection->authinfo) {
1328 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->authinfo, (ub4) OCI_HTYPE_AUTHINFO));
1329 	}
1330 
1331 	/* No Handlefrees for session pool connections */
1332 	if (!connection->using_spool) {
1333 		if (connection->session) {
1334 			PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->session, OCI_HTYPE_SESSION));
1335 		}
1336 
1337 		if (connection->is_attached) {
1338 			PHP_OCI_CALL(OCIServerDetach, (connection->server, OCI_G(err), OCI_DEFAULT));
1339 		}
1340 
1341 		if (connection->svc) {
1342 			PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->svc, (ub4) OCI_HTYPE_SVCCTX));
1343 		}
1344 
1345 		if (connection->server) {
1346 			PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->server, (ub4) OCI_HTYPE_SERVER));
1347 		}
1348 
1349 		if (connection->env) {
1350 			PHP_OCI_CALL(OCIHandleFree, ((dvoid *) connection->env, OCI_HTYPE_ENV));
1351 		}
1352 	} else if (connection->private_spool) {
1353 	/* Keep this as the last member to be freed, as there are dependencies
1354 	 * (like env) on the session pool
1355 	 */
1356 		php_oci_spool_close(connection->private_spool);
1357 		connection->private_spool = NULL;
1358 	}
1359 
1360 	if (GC_REFCOUNT(connection->hash_key) >= 2) {
1361 		zend_hash_del(&EG(regular_list), connection->hash_key);
1362 	}
1363 
1364 	if (connection->hash_key) {
1365 		pefree(connection->hash_key, connection->is_persistent);
1366 		connection->hash_key = NULL;
1367 	}
1368 #ifdef HAVE_OCI8_DTRACE
1369 	if (connection->client_id) {
1370 		pefree(connection->client_id, connection->is_persistent);
1371 		connection->client_id = NULL;
1372 	}
1373 #endif /* HAVE_OCI8_DTRACE */
1374 
1375 	if (!Z_ISUNDEF(connection->taf_callback)) {
1376 		/* If it's NULL, then its value should be freed already */
1377 		if (!Z_ISNULL(connection->taf_callback)) {
1378 			zval_ptr_dtor(&connection->taf_callback);
1379 		}
1380 		ZVAL_UNDEF(&connection->taf_callback);
1381 	}
1382 
1383 	pefree(connection, connection->is_persistent);
1384 	connection = NULL;
1385 	OCI_G(in_call) = in_call_save;
1386 	return result;
1387 }
1388 /* }}} */
1389 
1390 /* {{{ php_oci_connection_release()
1391  *
1392  * Release the connection's resources. This involves freeing descriptors and rolling back
1393  * transactions, setting timeout-related parameters etc. For session-pool using connections, the
1394  * underlying connection is released to its session pool.
1395  */
php_oci_connection_release(php_oci_connection * connection)1396 int php_oci_connection_release(php_oci_connection *connection)
1397 {
1398 	int result = 0;
1399 	bool in_call_save = OCI_G(in_call);
1400 	time_t timestamp = time(NULL);
1401 
1402 	if (connection->is_stub) {
1403 		return 0;
1404 	}
1405 
1406 	if (connection->descriptors) {
1407 		php_oci_connection_descriptors_free(connection);
1408 	}
1409 
1410 	if (connection->svc) {
1411 		/* rollback outstanding transactions */
1412 		if (connection->rb_on_disconnect) {
1413 			if (php_oci_connection_rollback(connection)) {
1414 				/* rollback failed */
1415 				result = 1;
1416 			}
1417 		}
1418 	}
1419 
1420 	if (OCI_G(persistent_timeout) > 0) {
1421 		connection->idle_expiry = timestamp + OCI_G(persistent_timeout);
1422 	}
1423 
1424 	/* We may have half-cooked connections to clean up */
1425 	if (connection->next_pingp) {
1426 		if (OCI_G(ping_interval) >= 0) {
1427 			*(connection->next_pingp) = timestamp + OCI_G(ping_interval);
1428 		} else {
1429 			/* ping_interval is -1 */
1430 			*(connection->next_pingp) = 0;
1431 		}
1432 	}
1433 
1434 	/* Release the session (stubs are filtered out at the beginning)*/
1435 	if (connection->using_spool) {
1436 		ub4 rlsMode = OCI_DEFAULT;
1437 
1438 		if (result) {
1439 			rlsMode |= OCI_SESSRLS_DROPSESS;
1440 		}
1441 
1442 		if (connection->svc) {
1443 			PHP_OCI_CALL(OCISessionRelease, (connection->svc, connection->err, NULL,
1444 										 0, rlsMode));
1445 		}
1446 		/* It no longer has relation with the database session. However authinfo and env are
1447 		 * cached
1448 		 */
1449 		connection->svc = NULL;
1450 		connection->server = NULL;
1451 		connection->session = NULL;
1452 
1453 		connection->is_attached = connection->is_open = connection->rb_on_disconnect = connection->used_this_request = 0;
1454 		connection->is_stub = 1;
1455 
1456 		/* Cut the link between the connection structure and the time_t structure allocated within
1457 		 * the OCI session
1458 		 */
1459 		connection->next_pingp = NULL;
1460 #ifdef HAVE_OCI8_DTRACE
1461 		if (connection->client_id) {
1462 			pefree(connection->client_id, connection->is_persistent);
1463 			connection->client_id = NULL;
1464 		}
1465 #endif /* HAVE_OCI8_DTRACE */
1466 	}
1467 
1468 	/* Always set id to null, so next time a new resource is being registered. */
1469 	connection->id = NULL;
1470 
1471 	OCI_G(in_call) = in_call_save;
1472 	return result;
1473 }
1474 /* }}} */
1475 
1476 /* {{{ php_oci_password_change()
1477  *
1478  * Change password for the user with the username given
1479  */
php_oci_password_change(php_oci_connection * connection,char * user,int user_len,char * pass_old,int pass_old_len,char * pass_new,int pass_new_len)1480 int php_oci_password_change(php_oci_connection *connection, char *user, int user_len, char *pass_old, int pass_old_len, char *pass_new, int pass_new_len)
1481 {
1482 	sword errstatus;
1483 
1484 	PHP_OCI_CALL_RETURN(errstatus, OCIPasswordChange, (connection->svc, connection->err, (text *)user, user_len, (text *)pass_old, pass_old_len, (text *)pass_new, pass_new_len, OCI_DEFAULT));
1485 
1486 	if (errstatus != OCI_SUCCESS) {
1487 		connection->errcode = php_oci_error(connection->err, errstatus);
1488 		PHP_OCI_HANDLE_ERROR(connection, connection->errcode);
1489 		return 1;
1490 	}
1491 	connection->errcode = 0; /* retain backwards compat with OCI8 1.4 */
1492 	connection->passwd_changed = 1;
1493 	return 0;
1494 }
1495 /* }}} */
1496 
1497 /* {{{ php_oci_client_get_version()
1498  *
1499  * Get Oracle client library version
1500  */
php_oci_client_get_version(char * version,size_t version_size)1501 void php_oci_client_get_version(char *version, size_t version_size)
1502 {
1503 	sword major_version = 0;
1504 	sword minor_version = 0;
1505 	sword update_num = 0;
1506 	sword patch_num = 0;
1507 	sword port_update_num = 0;
1508 
1509 	PHP_OCI_CALL(OCIClientVersion, (&major_version, &minor_version, &update_num, &patch_num, &port_update_num));
1510 	snprintf(version, version_size, "%d.%d.%d.%d.%d", major_version, minor_version, update_num, patch_num, port_update_num);
1511 }
1512 /* }}} */
1513 
1514 /* {{{ php_oci_server_get_version()
1515  *
1516  * Get Oracle server version
1517  */
php_oci_server_get_version(php_oci_connection * connection,char * version,size_t version_size)1518 int php_oci_server_get_version(php_oci_connection *connection, char *version, size_t version_size)
1519 {
1520 	sword errstatus;
1521 
1522 	PHP_OCI_CALL_RETURN(errstatus, OCIServerVersion, (connection->svc, connection->err, (text *)version, (ub4) version_size, OCI_HTYPE_SVCCTX));
1523 
1524 	if (errstatus != OCI_SUCCESS) {
1525 		connection->errcode = php_oci_error(connection->err, errstatus);
1526 		PHP_OCI_HANDLE_ERROR(connection, connection->errcode);
1527 		return 1;
1528 	}
1529 
1530 	return 0;
1531 }
1532 /* }}} */
1533 
1534 /* {{{ php_oci_column_to_zval()
1535  *
1536  * Convert php_oci_out_column struct into zval
1537  */
php_oci_column_to_zval(php_oci_out_column * column,zval * value,int mode)1538 int php_oci_column_to_zval(php_oci_out_column *column, zval *value, int mode)
1539 {
1540 	php_oci_descriptor *descriptor;
1541 	ub4 lob_length;
1542 	int column_size;
1543 	char *lob_buffer = (char *)0;
1544 	int lob_fetch_status;
1545 
1546 	if (column->indicator == -1) { /* column is NULL */
1547 		ZVAL_NULL(value);
1548 		return 0;
1549 	}
1550 
1551 	if (column->is_cursor) { /* REFCURSOR -> simply return the statement id */
1552 		ZVAL_RES(value, column->stmtid);
1553 		GC_ADDREF(column->stmtid);
1554 	} else if (column->is_descr) {
1555 
1556 		if (column->data_type != SQLT_RDD) {
1557 
1558 			/* reset descriptor's length */
1559 			descriptor = (php_oci_descriptor *) column->descid->ptr;
1560 
1561 			if (!descriptor) {
1562 				php_error_docref(NULL, E_WARNING, "Unable to find LOB descriptor #" ZEND_LONG_FMT, column->descid->handle);
1563 				return 1;
1564 			}
1565 
1566 			descriptor->lob_size = -1;
1567 			descriptor->lob_current_position = 0;
1568 			descriptor->buffering = 0;
1569 		}
1570 
1571 		if (column->data_type != SQLT_RDD && (mode & PHP_OCI_RETURN_LOBS)) {
1572 			/* PHP_OCI_RETURN_LOBS means that we want the content of the LOB back instead of the locator */
1573 
1574 			if (column->chunk_size)
1575 				descriptor->chunk_size = column->chunk_size;
1576 			lob_fetch_status = php_oci_lob_read(descriptor, -1, 0, &lob_buffer, &lob_length);
1577 			if (descriptor->chunk_size)  /* Cache the chunk_size to avoid recalling OCILobGetChunkSize */
1578 				column->chunk_size = descriptor->chunk_size;
1579 			php_oci_temp_lob_close(descriptor);
1580 			if (lob_fetch_status) {
1581 				ZVAL_FALSE(value);
1582 				return 1;
1583 			} else {
1584 				if (lob_length > 0) {
1585 					ZVAL_STRINGL(value, lob_buffer, lob_length);
1586 				} else {
1587 					ZVAL_EMPTY_STRING(value);
1588 				}
1589 				if (lob_buffer)
1590 					efree(lob_buffer);
1591 				return 0;
1592 			}
1593 		} else {
1594 			/* return the locator */
1595 			object_init_ex(value, oci_lob_class_entry_ptr);
1596 			add_property_resource(value, "descriptor", column->descid);
1597 			GC_ADDREF(column->descid);
1598 		}
1599 	} else {
1600 		switch (column->retcode) {
1601 			case 0:
1602 				/* intact value */
1603 				if (column->piecewise) {
1604 					column_size = column->retlen4;
1605 				} else {
1606 					column_size = column->retlen;
1607 				}
1608 				break;
1609 
1610 			default:
1611 				ZVAL_FALSE(value);
1612 				return 0;
1613 		}
1614 
1615 		ZVAL_STRINGL(value, column->data, column_size);
1616 	}
1617 	return 0;
1618 }
1619 /* }}} */
1620 
1621 
1622 /* {{{ php_oci_fetch_row()
1623  *
1624  * Fetch the next row from the given statement
1625  * Has logic for Oracle 12c Implicit Result Sets
1626  */
php_oci_fetch_row(INTERNAL_FUNCTION_PARAMETERS,int mode,int expected_args)1627 void php_oci_fetch_row (INTERNAL_FUNCTION_PARAMETERS, int mode, int expected_args)
1628 {
1629 	zval *z_statement, *array;
1630 	zval *placeholder = (zval*) NULL;
1631 /*	zend_array *temp_array = (zend_array *) NULL;*/
1632 	php_oci_statement *statement;		  /* statement that will be fetched from */
1633 #if (OCI_MAJOR_VERSION >= 12)
1634 	php_oci_statement *invokedstatement;  /* statement this function was invoked with */
1635 #endif /* OCI_MAJOR_VERSION */
1636 	php_oci_out_column *column;
1637 	ub4 nrows = 1;
1638 	int i;
1639 	zend_long fetch_mode = 0;
1640 
1641 	if (expected_args > 2) {
1642 		/* only for ocifetchinto BC */
1643 
1644 		ZEND_PARSE_PARAMETERS_START(2, 3)
1645 			Z_PARAM_RESOURCE(z_statement)
1646 			Z_PARAM_ZVAL(array)
1647 			Z_PARAM_OPTIONAL
1648 			Z_PARAM_LONG(fetch_mode)
1649 		ZEND_PARSE_PARAMETERS_END();
1650 
1651 		if (ZEND_NUM_ARGS() == 2) {
1652 			fetch_mode = mode;
1653 		}
1654 
1655 		if (Z_ISREF_P(array))
1656 			placeholder = Z_REFVAL_P(array);
1657 		else
1658 			placeholder = array;
1659 
1660 	} else if (expected_args == 2) {
1661 		/* only for oci_fetch_array() */
1662 
1663 		ZEND_PARSE_PARAMETERS_START(1, 2)
1664 			Z_PARAM_RESOURCE(z_statement)
1665 			Z_PARAM_OPTIONAL
1666 			Z_PARAM_LONG(fetch_mode)
1667 		ZEND_PARSE_PARAMETERS_END();
1668 
1669 		if (ZEND_NUM_ARGS() == 1) {
1670 			fetch_mode = mode;
1671 		}
1672 	} else {
1673 		/* for all oci_fetch_*() */
1674 
1675 		ZEND_PARSE_PARAMETERS_START(1, 1)
1676 			Z_PARAM_RESOURCE(z_statement)
1677 		ZEND_PARSE_PARAMETERS_END();
1678 
1679 		fetch_mode = mode;
1680 	}
1681 
1682 	if (!(fetch_mode & PHP_OCI_NUM) && !(fetch_mode & PHP_OCI_ASSOC)) {
1683 		/* none of the modes present, use the default one */
1684 		if (mode & PHP_OCI_ASSOC) {
1685 			fetch_mode |= PHP_OCI_ASSOC;
1686 		}
1687 		if (mode & PHP_OCI_NUM) {
1688 			fetch_mode |= PHP_OCI_NUM;
1689 		}
1690 	}
1691 
1692 #if (OCI_MAJOR_VERSION < 12)
1693 	PHP_OCI_ZVAL_TO_STATEMENT(z_statement, statement);
1694 
1695 	if (php_oci_statement_fetch(statement, nrows)) {
1696 		RETURN_FALSE;			/* end of fetch */
1697 	}
1698 #else /* OCI_MAJOR_VERSION */
1699 	PHP_OCI_ZVAL_TO_STATEMENT(z_statement, invokedstatement);
1700 
1701 	if (invokedstatement->impres_flag == PHP_OCI_IMPRES_NO_CHILDREN ||
1702         invokedstatement->impres_flag == PHP_OCI_IMPRES_IS_CHILD) {
1703 		/* Already know there are no Implicit Result Sets */
1704 	    statement = invokedstatement;
1705 	} else if (invokedstatement->impres_flag == PHP_OCI_IMPRES_HAS_CHILDREN) {
1706 		/* Previously saw an Implicit Result Set in an earlier invocation of php_oci_fetch_row */
1707 		statement = (php_oci_statement *)invokedstatement->impres_child_stmt;
1708 	} else {
1709 		sword errstatus;
1710 
1711 		/* Check for an Implicit Result Set on this statement handle */
1712 		PHP_OCI_CALL_RETURN(errstatus, OCIAttrGet, ((dvoid *)invokedstatement->stmt, OCI_HTYPE_STMT,
1713 						    (dvoid *) &invokedstatement->impres_count,
1714 						    (ub4 *)NULL, OCI_ATTR_IMPLICIT_RESULT_COUNT, invokedstatement->err));
1715 		if (errstatus) {
1716 			RETURN_FALSE;
1717 		}
1718 		if (invokedstatement->impres_count > 0) {
1719 			/* Make it so the fetch occurs on the first Implicit Result Set */
1720 			statement = php_oci_get_implicit_resultset(invokedstatement);
1721 			if (!statement || php_oci_statement_execute(statement, (ub4)OCI_DEFAULT))
1722 				RETURN_FALSE;
1723 			invokedstatement->impres_count--;
1724 			invokedstatement->impres_child_stmt = (struct php_oci_statement *)statement;
1725 			invokedstatement->impres_flag = PHP_OCI_IMPRES_HAS_CHILDREN;
1726 		} else {
1727 			statement = invokedstatement; /* didn't find Implicit Result Sets */
1728 			invokedstatement->impres_flag = PHP_OCI_IMPRES_NO_CHILDREN;  /* Don't bother checking again */
1729 		}
1730 	}
1731 
1732 	if (php_oci_statement_fetch(statement, nrows)) {
1733 		/* End of fetch */
1734 		if (invokedstatement->impres_count > 0) {
1735 			/* Check next Implicit Result Set */
1736 	        statement = php_oci_get_implicit_resultset(invokedstatement);
1737 			if (!statement || php_oci_statement_execute(statement, (ub4)OCI_DEFAULT))
1738 				RETURN_FALSE;
1739 			invokedstatement->impres_count--;
1740 			invokedstatement->impres_child_stmt = (struct php_oci_statement *)statement;
1741 			if (php_oci_statement_fetch(statement, nrows)) {
1742 				/* End of all fetches */
1743 	            RETURN_FALSE;
1744 			}
1745 		} else {
1746 			RETURN_FALSE;
1747 		}
1748 	}
1749 #endif /* OCI_MAJOR_VERSION */
1750 
1751 	if (placeholder == NULL) {
1752 		placeholder = return_value;
1753 	} else {
1754 		zval_ptr_dtor(placeholder);
1755 	}
1756 
1757 	array_init(placeholder);
1758 
1759 	for (i = 0; i < statement->ncolumns; i++) {
1760 
1761 		column = php_oci_statement_get_column(statement, i + 1, NULL, 0);
1762 
1763 		if (column == NULL) {
1764 			continue;
1765 		}
1766 		if ((column->indicator == -1) && ((fetch_mode & PHP_OCI_RETURN_NULLS) == 0)) {
1767 			continue;
1768 		}
1769 
1770 		if (!(column->indicator == -1)) {
1771 			zval element;
1772 
1773 			php_oci_column_to_zval(column, &element, (int) fetch_mode);
1774 
1775 			if (fetch_mode & PHP_OCI_NUM || !(fetch_mode & PHP_OCI_ASSOC)) {
1776 				add_index_zval(placeholder, i, &element);
1777 			}
1778 			if (fetch_mode & PHP_OCI_ASSOC) {
1779 				if (fetch_mode & PHP_OCI_NUM) {
1780 					Z_TRY_ADDREF_P(&element);
1781 				}
1782 				add_assoc_zval(placeholder, column->name, &element);
1783 			}
1784 
1785 		} else {
1786 			if (fetch_mode & PHP_OCI_NUM || !(fetch_mode & PHP_OCI_ASSOC)) {
1787 				add_index_null(placeholder, i);
1788 			}
1789 			if (fetch_mode & PHP_OCI_ASSOC) {
1790 				add_assoc_null(placeholder, column->name);
1791 			}
1792 		}
1793 	}
1794 
1795 	if (expected_args > 2) {
1796 		RETURN_LONG(statement->ncolumns);
1797 	}
1798 }
1799 /* }}} */
1800 
1801 /* {{{ php_oci_persistent_helper()
1802  *
1803  * Helper function to close/rollback persistent connections at the end of request. A return value of
1804  * 1 indicates that the connection is to be destroyed
1805  */
php_oci_persistent_helper(zval * zv)1806 static int php_oci_persistent_helper(zval *zv)
1807 {
1808 	zend_resource *le = Z_RES_P(zv);
1809 	time_t timestamp;
1810 	php_oci_connection *connection;
1811 
1812 	timestamp = time(NULL);
1813 
1814 	/* Persistent connection stubs are also counted as they have private session pools */
1815 	if (le->type == le_pconnection) {
1816 		connection = (php_oci_connection *)le->ptr;
1817 
1818 		/* Remove TAF callback function as it's bound to current request */
1819 		if (connection->used_this_request && !Z_ISUNDEF(connection->taf_callback) && !Z_ISNULL(connection->taf_callback)) {
1820 			php_oci_unregister_taf_callback(connection);
1821 		}
1822 
1823 		if (!connection->used_this_request && OCI_G(persistent_timeout) != -1) {
1824 #ifdef HAVE_OCI8_DTRACE
1825 			if (DTRACE_OCI8_CONNECT_EXPIRY_ENABLED()) {
1826 				DTRACE_OCI8_CONNECT_EXPIRY(connection, connection->is_stub ? 1 : 0, (long)connection->idle_expiry, (long)timestamp);
1827 			}
1828 #endif /* HAVE_OCI8_DTRACE */
1829 			if (connection->idle_expiry < timestamp) {
1830 				/* connection has timed out */
1831 				return ZEND_HASH_APPLY_REMOVE;
1832 			}
1833 		}
1834 	}
1835 	return ZEND_HASH_APPLY_KEEP;
1836 }
1837 /* }}} */
1838 
1839 /* {{{ php_oci_create_spool()
1840  *
1841  *	 Create(alloc + Init) Session pool for the given dbname and charsetid
1842  */
php_oci_create_spool(char * username,int username_len,char * password,int password_len,char * dbname,int dbname_len,zend_string * hash_key,int charsetid)1843 static php_oci_spool *php_oci_create_spool(char *username, int username_len, char *password, int password_len, char *dbname, int dbname_len, zend_string *hash_key, int charsetid)
1844 {
1845 	php_oci_spool *session_pool = NULL;
1846 	bool iserror = 0;
1847 	ub4 poolmode = OCI_DEFAULT;	/* Mode to be passed to OCISessionPoolCreate */
1848 	OCIAuthInfo *spoolAuth = NULL;
1849 	sword errstatus;
1850 
1851 	/* Allocate sessionpool out of persistent memory */
1852 	session_pool = (php_oci_spool *) calloc(1, sizeof(php_oci_spool));
1853 	if (session_pool == NULL) {
1854 		iserror = 1;
1855 		goto exit_create_spool;
1856 	}
1857 
1858 	/* Populate key if passed */
1859 	if (hash_key && (ZSTR_LEN(hash_key) > 0)) {
1860 		session_pool->spool_hash_key = zend_string_dup(hash_key, 1);
1861 		if (session_pool->spool_hash_key == NULL) {
1862 			iserror = 1;
1863 			goto exit_create_spool;
1864 		}
1865 	}
1866 
1867 	/* Create the session pool's env */
1868 	if (!(session_pool->env = php_oci_create_env(charsetid))) {
1869 		iserror = 1;
1870 		goto exit_create_spool;
1871 	}
1872 
1873 	/* Allocate the pool handle */
1874 	PHP_OCI_CALL_RETURN(errstatus, OCIHandleAlloc, (session_pool->env, (dvoid **) &session_pool->poolh, OCI_HTYPE_SPOOL, (size_t) 0, (dvoid **) 0));
1875 
1876 	if (errstatus != OCI_SUCCESS) {
1877 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1878 		iserror = 1;
1879 		goto exit_create_spool;
1880 	}
1881 
1882 	/* Allocate the session pool error handle - This only for use in the destructor, as there is a
1883 	 * generic bug which can free up the OCI_G(err) variable before destroying connections. We
1884 	 * cannot use this for other roundtrip calls as there is no way the user can access this error
1885 	 */
1886 	PHP_OCI_CALL_RETURN(errstatus, OCIHandleAlloc, ((dvoid *) session_pool->env, (dvoid **)&(session_pool->err), (ub4) OCI_HTYPE_ERROR,(size_t) 0, (dvoid **) 0));
1887 
1888 	if (errstatus != OCI_SUCCESS) {
1889 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1890 		iserror = 1;
1891 		goto exit_create_spool;
1892 	}
1893 
1894 /* Disable RLB as we mostly have single-connection pools */
1895 	poolmode = OCI_SPC_NO_RLB | OCI_SPC_HOMOGENEOUS;
1896 
1897 	/* {{{ Allocate auth handle for session pool */
1898 	PHP_OCI_CALL_RETURN(errstatus, OCIHandleAlloc, (session_pool->env, (dvoid **)&(spoolAuth), OCI_HTYPE_AUTHINFO, 0, NULL));
1899 
1900 	if (errstatus != OCI_SUCCESS) {
1901 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1902 		iserror = 1;
1903 		goto exit_create_spool;
1904 	}
1905 	/* }}} */
1906 
1907 	/* {{{ Set the edition attribute on the auth handle */
1908 	if (OCI_G(edition)) {
1909 		PHP_OCI_CALL_RETURN(errstatus, OCIAttrSet, ((dvoid *) spoolAuth, (ub4) OCI_HTYPE_AUTHINFO, (dvoid *) OCI_G(edition), (ub4)(strlen(OCI_G(edition))), (ub4)OCI_ATTR_EDITION, OCI_G(err)));
1910 
1911 		if (errstatus != OCI_SUCCESS) {
1912 			OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1913 			iserror = 1;
1914 			goto exit_create_spool;
1915 		}
1916 	}
1917 	/* }}} */
1918 
1919 	/* {{{ Set the driver name attribute on the auth handle */
1920 	PHP_OCI_CALL_RETURN(errstatus, OCIAttrSet, ((dvoid *) spoolAuth, (ub4) OCI_HTYPE_AUTHINFO, (dvoid *) PHP_OCI8_DRIVER_NAME, (ub4) sizeof(PHP_OCI8_DRIVER_NAME)-1, (ub4) OCI_ATTR_DRIVER_NAME, OCI_G(err)));
1921 
1922 	if (errstatus != OCI_SUCCESS) {
1923 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1924 		iserror = 1;
1925 		goto exit_create_spool;
1926 	}
1927 	/* }}} */
1928 
1929 	/* {{{ Set the auth handle on the session pool */
1930 	PHP_OCI_CALL_RETURN(errstatus, OCIAttrSet, ((dvoid *) (session_pool->poolh),(ub4) OCI_HTYPE_SPOOL, (dvoid *) spoolAuth, (ub4)0, (ub4)OCI_ATTR_SPOOL_AUTH, OCI_G(err)));
1931 
1932 	if (errstatus != OCI_SUCCESS) {
1933 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1934 		iserror = 1;
1935 		goto exit_create_spool;
1936 	}
1937 	/* }}} */
1938 
1939 	/* Create the homogeneous session pool - We have different session pools for every different
1940 	 * username, password, charset and dbname.
1941 	 */
1942 	PHP_OCI_CALL_RETURN(errstatus, OCISessionPoolCreate,(session_pool->env, OCI_G(err), session_pool->poolh, (OraText **)&session_pool->poolname, &session_pool->poolname_len, (OraText *)dbname, (ub4)dbname_len, 0, UB4MAXVAL, 1,(OraText *)username, (ub4)username_len, (OraText *)password,(ub4)password_len, poolmode));
1943 
1944 	if (errstatus != OCI_SUCCESS) {
1945 		OCI_G(errcode) = php_oci_error(OCI_G(err), errstatus);
1946 		iserror = 1;
1947 	}
1948 
1949 exit_create_spool:
1950 	if (iserror && session_pool) {
1951 		php_oci_spool_close(session_pool);
1952 		session_pool = NULL;
1953 	}
1954 
1955 	if (spoolAuth) {
1956 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) spoolAuth, (ub4) OCI_HTYPE_AUTHINFO));
1957 	}
1958 
1959 #ifdef HAVE_OCI8_DTRACE
1960 	if (DTRACE_OCI8_SESSPOOL_CREATE_ENABLED()) {
1961 		DTRACE_OCI8_SESSPOOL_CREATE(session_pool);
1962 	}
1963 #endif /* HAVE_OCI8_DTRACE */
1964 
1965 	return session_pool;
1966 }
1967 /* }}} */
1968 
1969 /* {{{ php_oci_get_spool()
1970  *
1971  * Get Session pool for the given dbname and charsetid from the persistent list. Function called for
1972  * non-persistent connections.
1973  */
php_oci_get_spool(char * username,int username_len,char * password,int password_len,char * dbname,int dbname_len,int charsetid)1974 static php_oci_spool *php_oci_get_spool(char *username, int username_len, char *password, int password_len, char *dbname, int dbname_len, int charsetid)
1975 {
1976 	smart_str spool_hashed_details = {0};
1977 	php_oci_spool *session_pool = NULL;
1978 	zend_resource *spool_out_le = NULL;
1979 	bool iserror = 0;
1980 	zval *spool_out_zv = NULL;
1981 
1982 	/* {{{ Create the spool hash key */
1983 	smart_str_appendl_ex(&spool_hashed_details, "oci8spool***", sizeof("oci8spool***") - 1, 0);
1984 	smart_str_appendl_ex(&spool_hashed_details, username, username_len, 0);
1985 	smart_str_appendl_ex(&spool_hashed_details, "**", sizeof("**") - 1, 0);
1986 	/* Add edition attribute to the hash */
1987 	if (OCI_G(edition)){
1988 		smart_str_appendl_ex(&spool_hashed_details, OCI_G(edition), strlen(OCI_G(edition)), 0);
1989 	}
1990 	smart_str_appendl_ex(&spool_hashed_details, "**", sizeof("**") - 1, 0);
1991 	if (password_len) {
1992 		zend_ulong password_hash;
1993 		password_hash = zend_hash_func(password, password_len);
1994 		smart_str_append_unsigned_ex(&spool_hashed_details, password_hash, 0);
1995 	}
1996 	smart_str_appendl_ex(&spool_hashed_details, "**", sizeof("**") - 1, 0);
1997 
1998 	if (dbname_len) {
1999 		smart_str_appendl_ex(&spool_hashed_details, dbname, dbname_len, 0);
2000 	}
2001 	smart_str_appendl_ex(&spool_hashed_details, "**", sizeof("**") - 1, 0);
2002 
2003 	smart_str_append_unsigned_ex(&spool_hashed_details, charsetid, 0);
2004 
2005 	/* Session Pool Hash Key : oci8spool***username**edition**hashedpassword**dbname**charset */
2006 
2007 	smart_str_0(&spool_hashed_details);
2008 	zend_str_tolower(ZSTR_VAL(spool_hashed_details.s), ZSTR_LEN(spool_hashed_details.s));
2009 	/* }}} */
2010 
2011 	spool_out_zv = zend_hash_find(&EG(persistent_list), spool_hashed_details.s);
2012 	if (spool_out_zv != NULL) {
2013 		spool_out_le = Z_RES_P(spool_out_zv);
2014 	}
2015 
2016 	if (spool_out_le == NULL) {
2017 
2018 		session_pool = php_oci_create_spool(username, username_len, password, password_len, dbname, dbname_len, spool_hashed_details.s, charsetid);
2019 
2020 		if (session_pool == NULL) {
2021 			iserror = 1;
2022 			goto exit_get_spool;
2023 		}
2024 		zend_register_persistent_resource_ex(session_pool->spool_hash_key, session_pool, le_psessionpool);
2025 	} else if (spool_out_le->type == le_psessionpool &&
2026 		zend_string_equals(((php_oci_spool *)(spool_out_le->ptr))->spool_hash_key, spool_hashed_details.s)) {
2027 		/* retrieve the cached session pool */
2028 		session_pool = (php_oci_spool *)(spool_out_le->ptr);
2029 	}
2030 
2031 exit_get_spool:
2032 	smart_str_free(&spool_hashed_details);
2033 	if (iserror && session_pool) {
2034 		php_oci_spool_close(session_pool);
2035 		session_pool = NULL;
2036 	}
2037 
2038 	return session_pool;
2039 
2040 }
2041 /* }}} */
2042 
2043 /* {{{ php_oci_create_env()
2044  *
2045  * Create the OCI environment choosing the correct function for the OCI version
2046  */
php_oci_create_env(ub2 charsetid)2047 static OCIEnv *php_oci_create_env(ub2 charsetid)
2048 {
2049 	OCIEnv *retenv = NULL;
2050 
2051 	/* create an environment using the character set id */
2052 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIEnvNlsCreate, (&retenv, OCI_G(events) ? PHP_OCI_INIT_MODE | OCI_EVENTS : PHP_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, charsetid, charsetid));
2053 
2054 	if (OCI_G(errcode) != OCI_SUCCESS) {
2055 		sb4   ora_error_code = 0;
2056 		text  ora_msg_buf[PHP_OCI_ERRBUF_LEN];  /* Use traditional smaller size: non-PL/SQL errors should fit and it keeps the stack smaller */
2057 
2058 #ifdef HAVE_OCI_INSTANT_CLIENT
2059 		php_error_docref(NULL, E_WARNING, "OCIEnvNlsCreate() failed. There is something wrong with your system - please check that " PHP_OCI8_LIB_PATH_MSG " includes the directory with Oracle Instant Client libraries");
2060 #else
2061 		php_error_docref(NULL, E_WARNING, "OCIEnvNlsCreate() failed. There is something wrong with your system - please check that ORACLE_HOME and " PHP_OCI8_LIB_PATH_MSG " are set and point to the right directories");
2062 #endif
2063 		if (retenv
2064 			&& OCIErrorGet(retenv, (ub4)1, NULL, &ora_error_code, ora_msg_buf, (ub4)PHP_OCI_ERRBUF_LEN, (ub4)OCI_HTYPE_ENV) == OCI_SUCCESS
2065 			&& *ora_msg_buf) {
2066 			php_error_docref(NULL, E_WARNING, "%s", ora_msg_buf);
2067 		}
2068 
2069 		return NULL;
2070 	}
2071 	return retenv;
2072 }
2073 /* }}} */
2074 
2075 /* {{{ php_oci_old_create_session()
2076  *
2077  * This function is to be deprecated in future in favour of OCISessionGet which is used in
2078  * php_oci_do_connect_ex
2079  */
php_oci_old_create_session(php_oci_connection * connection,char * dbname,int dbname_len,char * username,int username_len,char * password,int password_len,char * new_password,int new_password_len,int session_mode)2080 static int php_oci_old_create_session(php_oci_connection *connection, char *dbname, int dbname_len, char *username, int username_len, char *password, int password_len, char *new_password, int new_password_len, int session_mode)
2081 {
2082 	ub4 statement_cache_size = 0;
2083 
2084 	if (OCI_G(statement_cache_size) > 0) {
2085 		if (OCI_G(statement_cache_size) > SB4MAXVAL)
2086 			statement_cache_size = (ub4) SB4MAXVAL;
2087 		else
2088 			statement_cache_size = (ub4) OCI_G(statement_cache_size);
2089 	}
2090 
2091 	/* Create the OCI environment separate for each connection */
2092 	if (!(connection->env = php_oci_create_env(connection->charset))) {
2093 		return 1;
2094 	}
2095 
2096 	/* {{{ Allocate our server handle */
2097 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->server), OCI_HTYPE_SERVER, 0, NULL));
2098 
2099 	if (OCI_G(errcode) != OCI_SUCCESS) {
2100 		php_oci_error(OCI_G(err), OCI_G(errcode));
2101 		return 1;
2102 	}
2103 	/* }}} */
2104 
2105 	/* {{{ Attach to the server */
2106 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIServerAttach, (connection->server, OCI_G(err), (text *)dbname, dbname_len, (ub4) OCI_DEFAULT));
2107 
2108 	if (OCI_G(errcode) != OCI_SUCCESS) {
2109 		php_oci_error(OCI_G(err), OCI_G(errcode));
2110 		return 1;
2111 	}
2112 	/* }}} */
2113 	connection->is_attached = 1;
2114 
2115 	/* {{{ Allocate our session handle */
2116 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->session), OCI_HTYPE_SESSION, 0, NULL));
2117 
2118 	if (OCI_G(errcode) != OCI_SUCCESS) {
2119 		php_oci_error(OCI_G(err), OCI_G(errcode));
2120 		return 1;
2121 	}
2122 	/* }}} */
2123 
2124 	/* {{{ Allocate our private error-handle */
2125 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->err), OCI_HTYPE_ERROR, 0, NULL));
2126 
2127 	if (OCI_G(errcode) != OCI_SUCCESS) {
2128 		php_oci_error(OCI_G(err), OCI_G(errcode));
2129 		return 1;
2130 	}
2131 	/* }}} */
2132 
2133 	/* {{{ Allocate our service-context */
2134 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->svc), OCI_HTYPE_SVCCTX, 0, NULL));
2135 
2136 	if (OCI_G(errcode) != OCI_SUCCESS) {
2137 		php_oci_error(OCI_G(err), OCI_G(errcode));
2138 		return 1;
2139 	}
2140 	/* }}} */
2141 
2142 	/* {{{ Set the username */
2143 	if (username) {
2144 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->session, (ub4) OCI_HTYPE_SESSION, (dvoid *) username, (ub4) username_len, (ub4) OCI_ATTR_USERNAME, OCI_G(err)));
2145 
2146 		if (OCI_G(errcode) != OCI_SUCCESS) {
2147 			php_oci_error(OCI_G(err), OCI_G(errcode));
2148 			return 1;
2149 		}
2150 	}
2151 	/* }}} */
2152 
2153 	/* {{{ Set the password */
2154 	if (password) {
2155 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->session, (ub4) OCI_HTYPE_SESSION, (dvoid *) password, (ub4) password_len, (ub4) OCI_ATTR_PASSWORD, OCI_G(err)));
2156 
2157 		if (OCI_G(errcode) != OCI_SUCCESS) {
2158 			php_oci_error(OCI_G(err), OCI_G(errcode));
2159 			return 1;
2160 		}
2161 	}
2162 	/* }}} */
2163 
2164 	/* {{{ Set the edition attribute on the session handle */
2165 	if (OCI_G(edition)) {
2166 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->session, (ub4) OCI_HTYPE_SESSION, (dvoid *) OCI_G(edition), (ub4) (strlen(OCI_G(edition))), (ub4) OCI_ATTR_EDITION, OCI_G(err)));
2167 
2168 		if (OCI_G(errcode) != OCI_SUCCESS) {
2169 			php_oci_error(OCI_G(err), OCI_G(errcode));
2170 			return 1;
2171 		}
2172 	}
2173 /* }}} */
2174 
2175 	/* {{{ Set the driver name attribute on the session handle */
2176 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->session, (ub4) OCI_HTYPE_SESSION, (dvoid *) PHP_OCI8_DRIVER_NAME, (ub4) sizeof(PHP_OCI8_DRIVER_NAME)-1, (ub4) OCI_ATTR_DRIVER_NAME, OCI_G(err)));
2177 
2178 	if (OCI_G(errcode) != OCI_SUCCESS) {
2179 		php_oci_error(OCI_G(err), OCI_G(errcode));
2180 		return 1;
2181 	}
2182 /* }}} */
2183 
2184 	/* {{{ Set the server handle in the service handle */
2185 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, (connection->svc, OCI_HTYPE_SVCCTX, connection->server, 0, OCI_ATTR_SERVER, OCI_G(err)));
2186 
2187 	if (OCI_G(errcode) != OCI_SUCCESS) {
2188 		php_oci_error(OCI_G(err), OCI_G(errcode));
2189 		return 1;
2190 	}
2191 	/* }}} */
2192 
2193 	/* {{{ Set the authentication handle in the service handle */
2194 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, (connection->svc, OCI_HTYPE_SVCCTX, connection->session, 0, OCI_ATTR_SESSION, OCI_G(err)));
2195 
2196 	if (OCI_G(errcode) != OCI_SUCCESS) {
2197 		php_oci_error(OCI_G(err), OCI_G(errcode));
2198 		return 1;
2199 	}
2200 	/* }}} */
2201 
2202 	if (new_password) {
2203 		/* {{{ Try to change password if new one was provided */
2204 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIPasswordChange, (connection->svc, OCI_G(err), (text *)username, username_len, (text *)password, password_len, (text *)new_password, new_password_len, OCI_AUTH));
2205 
2206 		if (OCI_G(errcode) != OCI_SUCCESS) {
2207 			php_oci_error(OCI_G(err), OCI_G(errcode));
2208 			return 1;
2209 		}
2210 
2211 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrGet, ((dvoid *)connection->svc, OCI_HTYPE_SVCCTX, (dvoid *)&(connection->session), (ub4 *)0, OCI_ATTR_SESSION, OCI_G(err)));
2212 
2213 		if (OCI_G(errcode) != OCI_SUCCESS) {
2214 			php_oci_error(OCI_G(err), OCI_G(errcode));
2215 			return 1;
2216 		}
2217 		/* }}} */
2218 	} else {
2219 		/* {{{ start the session */
2220 		ub4 cred_type = OCI_CRED_RDBMS;
2221 
2222 		/* Extract the overloaded session_mode parameter into valid Oracle credential and session mode values */
2223 		if (session_mode & PHP_OCI_CRED_EXT) {
2224 			cred_type = OCI_CRED_EXT;
2225 			session_mode ^= PHP_OCI_CRED_EXT;
2226 		}
2227 
2228 		session_mode |= OCI_STMT_CACHE;
2229 
2230 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCISessionBegin, (connection->svc, OCI_G(err), connection->session, (ub4) cred_type, (ub4) session_mode));
2231 
2232 		if (OCI_G(errcode) != OCI_SUCCESS) {
2233 			php_oci_error(OCI_G(err), OCI_G(errcode));
2234 			/* OCISessionBegin returns OCI_SUCCESS_WITH_INFO when
2235 			 * user's password has expired, but is still usable.
2236 			 */
2237 			if (OCI_G(errcode) != OCI_SUCCESS_WITH_INFO) {
2238 				return 1;
2239 			}
2240 		}
2241 		/* }}} */
2242 	}
2243 
2244 	/* Brand new connection: Init and update the next_ping in the connection */
2245 	if (php_oci_ping_init(connection, OCI_G(err)) != OCI_SUCCESS) {
2246 		php_oci_error(OCI_G(err), OCI_G(errcode));
2247 		return 1;
2248 	}
2249 
2250 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->svc, (ub4) OCI_HTYPE_SVCCTX, (ub4 *) &statement_cache_size, 0, (ub4) OCI_ATTR_STMTCACHESIZE, OCI_G(err)));
2251 
2252 	if (OCI_G(errcode) != OCI_SUCCESS) {
2253 		php_oci_error(OCI_G(err), OCI_G(errcode));
2254 		return 1;
2255 	}
2256 
2257 	/* Successfully created session */
2258 	return 0;
2259 }
2260 /* }}} */
2261 
2262 /* {{{ php_oci_create_session()
2263  *
2264  * Create session using client-side session pool - new norm
2265  */
php_oci_create_session(php_oci_connection * connection,php_oci_spool * session_pool,char * dbname,int dbname_len,char * username,int username_len,char * password,int password_len,char * new_password,int new_password_len,int session_mode)2266 static int php_oci_create_session(php_oci_connection *connection, php_oci_spool *session_pool, char *dbname, int dbname_len, char *username, int username_len, char *password, int password_len, char *new_password, int new_password_len, int session_mode)
2267 {
2268 	php_oci_spool *actual_spool = NULL;
2269 	ub4 purity = -2;				/* Illegal value to initialize */
2270 	time_t timestamp = time(NULL);
2271 	ub4 statement_cache_size = 0;
2272 
2273 	if (OCI_G(statement_cache_size) > 0) {
2274 		if (OCI_G(statement_cache_size) > SB4MAXVAL)
2275 			statement_cache_size = (ub4) SB4MAXVAL;
2276 		else
2277 			statement_cache_size = (ub4) OCI_G(statement_cache_size);
2278 	}
2279 
2280 	/* Persistent connections have private session pools */
2281 	if (connection->is_persistent && !connection->private_spool &&
2282 		!(connection->private_spool = php_oci_create_spool(username, username_len, password, password_len, dbname, dbname_len, NULL, connection->charset))) {
2283 			return 1;
2284 	}
2285 	actual_spool = (connection->is_persistent) ? (connection->private_spool) : (session_pool);
2286 
2287 	connection->env = actual_spool->env;
2288 
2289 	/* Do this upfront so that connection close on an error would know that this is a session pool
2290 	 * connection. Failure to do this would result in crashes in error scenarios
2291 	 */
2292 	if (!connection->using_spool) {
2293 		connection->using_spool = 1;
2294 	}
2295 
2296 #ifdef HAVE_OCI8_DTRACE
2297 	if (DTRACE_OCI8_SESSPOOL_TYPE_ENABLED()) {
2298 		DTRACE_OCI8_SESSPOOL_TYPE(session_pool ? 1 : 0, session_pool ? session_pool : connection->private_spool);
2299 	}
2300 #endif /* HAVE_OCI8_DTRACE */
2301 
2302 	/* The passed in "connection" can be a cached stub from plist or freshly created. In the former
2303 	 * case, we do not have to allocate any handles
2304 	 */
2305 
2306 	if (!connection->err) {
2307 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->err), OCI_HTYPE_ERROR, 0, NULL));
2308 
2309 		if (OCI_G(errcode) != OCI_SUCCESS) {
2310 			php_oci_error(OCI_G(err), OCI_G(errcode));
2311 			return 1;
2312 		}
2313 	}
2314 
2315 	/* {{{ Allocate and initialize the connection-private authinfo handle if not allocated yet */
2316 	if (!connection->authinfo) {
2317 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIHandleAlloc, (connection->env, (dvoid **)&(connection->authinfo), OCI_HTYPE_AUTHINFO, 0, NULL));
2318 
2319 		if (OCI_G(errcode) != OCI_SUCCESS) {
2320 			php_oci_error(OCI_G(err), OCI_G(errcode));
2321 			return 1;
2322 		}
2323 
2324 		/* Set the Connection class and purity if OCI client version >= 11g */
2325 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->authinfo,(ub4) OCI_HTYPE_SESSION, (dvoid *) OCI_G(connection_class), (ub4)(strlen(OCI_G(connection_class))), (ub4)OCI_ATTR_CONNECTION_CLASS, OCI_G(err)));
2326 
2327 		if (OCI_G(errcode) != OCI_SUCCESS) {
2328 			php_oci_error(OCI_G(err), OCI_G(errcode));
2329 			return 1;
2330 		}
2331 
2332 		if (connection->is_persistent)
2333 			purity = OCI_ATTR_PURITY_SELF;
2334 		else
2335 			purity = OCI_ATTR_PURITY_NEW;
2336 
2337 		PHP_OCI_CALL_RETURN(OCI_G(errcode),OCIAttrSet, ((dvoid *) connection->authinfo,(ub4) OCI_HTYPE_AUTHINFO, (dvoid *) &purity, (ub4)0, (ub4)OCI_ATTR_PURITY, OCI_G(err)));
2338 
2339 		if (OCI_G(errcode) != OCI_SUCCESS) {
2340 			php_oci_error(OCI_G(err), OCI_G(errcode));
2341 			return 1;
2342 		}
2343 	}
2344 	/* }}} */
2345 
2346 	/* {{{ Debug statements */
2347 #ifdef HAVE_OCI8_DTRACE
2348 	if (DTRACE_OCI8_SESSPOOL_STATS_ENABLED()) {
2349 		ub4 numfree = 0, numbusy = 0, numopen = 0;
2350 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrGet, ((dvoid *)actual_spool->poolh, OCI_HTYPE_SPOOL, (dvoid *)&numopen, (ub4 *)0, OCI_ATTR_SPOOL_OPEN_COUNT, OCI_G(err)));
2351 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrGet, ((dvoid *)actual_spool->poolh, OCI_HTYPE_SPOOL, (dvoid *)&numbusy, (ub4 *)0, OCI_ATTR_SPOOL_BUSY_COUNT, OCI_G(err)));
2352 		numfree = numopen - numbusy;	/* number of free connections in the pool */
2353 		DTRACE_OCI8_SESSPOOL_STATS(numfree, numbusy, numopen);
2354 	}
2355 #endif /* HAVE_OCI8_DTRACE */
2356 	/* }}} */
2357 
2358 		/* Ping loop: Ping and loop till we get a good connection. When a database instance goes
2359 		 * down, it can leave several bad connections that need to be flushed out before getting a
2360 		 * good one. In non-RAC, we always get a brand new connection at the end of the loop and in
2361 		 * RAC, we can get a good connection from a different instance before flushing out all bad
2362 		 * ones. We do not need to ping brand new connections.
2363 		 */
2364 	do {
2365 		/* Continue to use the global error handle as the connection is closed when an error occurs */
2366 		PHP_OCI_CALL_RETURN(OCI_G(errcode),OCISessionGet, (connection->env, OCI_G(err), &(connection->svc), (OCIAuthInfo *)connection->authinfo, (OraText *)actual_spool->poolname, (ub4)actual_spool->poolname_len, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL));
2367 
2368 		if (OCI_G(errcode) != OCI_SUCCESS) {
2369 			php_oci_error(OCI_G(err), OCI_G(errcode));
2370 
2371 			/* Session creation returns OCI_SUCCESS_WITH_INFO when user's password has expired, but
2372 			 * is still usable.
2373 			 */
2374 
2375 			if (OCI_G(errcode) != OCI_SUCCESS_WITH_INFO) {
2376 				return 1;
2377 			}
2378 		}
2379 
2380 		/* {{{ Populate the session and server fields of the connection */
2381 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrGet, ((dvoid *)connection->svc, OCI_HTYPE_SVCCTX, (dvoid *)&(connection->server), (ub4 *)0, OCI_ATTR_SERVER, OCI_G(err)));
2382 
2383 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrGet, ((dvoid *)connection->svc, OCI_HTYPE_SVCCTX, (dvoid *)&(connection->session), (ub4 *)0, OCI_ATTR_SESSION, OCI_G(err)));
2384 		/* }}} */
2385 
2386 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIContextGetValue, (connection->session, OCI_G(err), (ub1 *)"NEXT_PING", (ub1)sizeof("NEXT_PING"), (void **)&(connection->next_pingp)));
2387 		if (OCI_G(errcode) != OCI_SUCCESS) {
2388 			php_oci_error(OCI_G(err), OCI_G(errcode));
2389 			return 1;
2390 		}
2391 
2392 		if (!(connection->next_pingp)){
2393 			/* This is a brand new connection, we need not ping, but have to initialize ping */
2394 			if (php_oci_ping_init(connection, OCI_G(err)) != OCI_SUCCESS) {
2395 				php_oci_error(OCI_G(err), OCI_G(errcode));
2396 				return 1;
2397 			}
2398 		} else if ((*(connection->next_pingp) > 0) && (timestamp >= *(connection->next_pingp))) {
2399 			if (php_oci_connection_ping(connection)) {
2400 				/* Got a good connection - update next_ping and get out of ping loop */
2401 				*(connection->next_pingp) = timestamp + OCI_G(ping_interval);
2402 			} else {
2403 				/* Bad connection - remove from pool */
2404 				PHP_OCI_CALL(OCISessionRelease, (connection->svc, connection->err, NULL,0, (ub4) OCI_SESSRLS_DROPSESS));
2405 				connection->svc = NULL;
2406 				connection->server = NULL;
2407 				connection->session = NULL;
2408 			}
2409 		}	/* If ping applicable */
2410 	} while (!(connection->svc));
2411 
2412 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIAttrSet, ((dvoid *) connection->svc, (ub4) OCI_HTYPE_SVCCTX, (ub4 *) &statement_cache_size, 0, (ub4) OCI_ATTR_STMTCACHESIZE, OCI_G(err)));
2413 
2414 	if (OCI_G(errcode) != OCI_SUCCESS) {
2415 		php_oci_error(OCI_G(err), OCI_G(errcode));
2416 		return 1;
2417 	}
2418 
2419 	/* Session is now taken from the session pool and attached and open */
2420 	connection->is_stub = 0;
2421 	connection->is_attached = connection->is_open = 1;
2422 
2423 	return 0;
2424 }
2425 /* }}} */
2426 
2427 /* {{{ php_oci_spool_list_dtor()
2428  *
2429  * Session pool destructor function
2430  */
php_oci_spool_list_dtor(zend_resource * entry)2431 static void php_oci_spool_list_dtor(zend_resource *entry)
2432 {
2433 	php_oci_spool *session_pool = (php_oci_spool *)entry->ptr;
2434 
2435 	if (session_pool) {
2436 		php_oci_spool_close(session_pool);
2437 	}
2438 
2439 	return;
2440 }
2441 /* }}} */
2442 
2443 /* {{{	php_oci_spool_close()
2444  *
2445  * Destroys the OCI Session Pool
2446  */
php_oci_spool_close(php_oci_spool * session_pool)2447 static void php_oci_spool_close(php_oci_spool *session_pool)
2448 {
2449 	if (session_pool->poolname_len) {
2450 		PHP_OCI_CALL(OCISessionPoolDestroy, ((dvoid *) session_pool->poolh,
2451 			(dvoid *) session_pool->err, OCI_SPD_FORCE));
2452 	}
2453 
2454 	if (session_pool->poolh) {
2455 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) session_pool->poolh, OCI_HTYPE_SPOOL));
2456 	}
2457 
2458 	if (session_pool->err) {
2459 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) session_pool->err, OCI_HTYPE_ERROR));
2460 	}
2461 
2462 	if (session_pool->env) {
2463 		PHP_OCI_CALL(OCIHandleFree, ((dvoid *) session_pool->env, OCI_HTYPE_ENV));
2464 	}
2465 
2466 	if (session_pool->spool_hash_key) {
2467 		free(session_pool->spool_hash_key);
2468 	}
2469 
2470 	free(session_pool);
2471 }
2472 /* }}} */
2473 
2474 /* {{{ php_oci_ping_init()
2475  *
2476  * Initializes the next_ping time as a context value in the connection.	 We now use
2477  * OCIContext{Get,Set}Value to store the next_ping because we need to support ping for
2478  * non-persistent DRCP connections
2479  */
php_oci_ping_init(php_oci_connection * connection,OCIError * errh)2480 static sword php_oci_ping_init(php_oci_connection *connection, OCIError *errh)
2481 {
2482 	time_t *next_pingp = NULL;
2483 
2484 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIContextGetValue, (connection->session, errh, (ub1 *)"NEXT_PING", (ub1)sizeof("NEXT_PING"), (void **)&next_pingp));
2485 	if (OCI_G(errcode) != OCI_SUCCESS) {
2486 		return OCI_G(errcode);
2487 	}
2488 
2489 	/* This must be a brand-new connection. Allocate memory for the ping */
2490 	if (!next_pingp) {
2491 		PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIMemoryAlloc, (connection->session, errh, (void **)&next_pingp, OCI_DURATION_SESSION, sizeof(time_t), OCI_MEMORY_CLEARED));
2492 		if (OCI_G(errcode) != OCI_SUCCESS) {
2493 			return OCI_G(errcode);
2494 		}
2495 	}
2496 
2497 	if (OCI_G(ping_interval) >= 0) {
2498 		time_t timestamp = time(NULL);
2499 		*next_pingp = timestamp + OCI_G(ping_interval);
2500 	} else {
2501 		*next_pingp = 0;
2502 	}
2503 
2504 	/* Set the new ping value into the connection */
2505 	PHP_OCI_CALL_RETURN(OCI_G(errcode), OCIContextSetValue, (connection->session, errh, OCI_DURATION_SESSION, (ub1 *)"NEXT_PING", (ub1)sizeof("NEXT_PING"), next_pingp));
2506 	if (OCI_G(errcode) != OCI_SUCCESS) {
2507 		OCIMemoryFree(connection->session, errh, next_pingp);
2508 		return OCI_G(errcode);
2509 	}
2510 
2511 	/* Cache the pointer so we do not have to do OCIContextGetValue repeatedly */
2512 	connection->next_pingp = next_pingp;
2513 
2514 	return OCI_SUCCESS;
2515 }
2516 /* }}} */
2517 
2518 /* {{{ php_oci_dtrace_check_connection()
2519  *
2520  * DTrace output for connections that may have become invalid and marked for reopening
2521  */
php_oci_dtrace_check_connection(php_oci_connection * connection,sb4 errcode,ub4 serverStatus)2522 void php_oci_dtrace_check_connection(php_oci_connection *connection, sb4 errcode, ub4 serverStatus)
2523 {
2524 #ifdef HAVE_OCI8_DTRACE
2525 	if (DTRACE_OCI8_CHECK_CONNECTION_ENABLED()) {
2526 		DTRACE_OCI8_CHECK_CONNECTION(connection, connection->client_id, connection->is_open ? 1 : 0, (long)errcode, (unsigned long)serverStatus);
2527 	}
2528 #endif /* HAVE_OCI8_DTRACE */
2529 }
2530 /* }}} */
2531 
2532 #endif /* HAVE_OCI8 */
2533