xref: /php-src/ext/mysqlnd/mysqlnd_vio.c (revision 43cbeb3b)
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: Andrey Hristov <andrey@php.net>                             |
14   |          Ulf Wendel <uw@php.net>                                     |
15   +----------------------------------------------------------------------+
16 */
17 
18 #include "php.h"
19 #include "mysqlnd.h"
20 #include "mysqlnd_priv.h"
21 #include "mysqlnd_statistics.h"
22 #include "mysqlnd_debug.h"
23 #include "mysqlnd_ext_plugin.h"
24 #include "php_network.h"
25 
26 #ifndef PHP_WIN32
27 #include <netinet/tcp.h>
28 #else
29 #include <winsock.h>
30 #endif
31 
32 
33 /* {{{ mysqlnd_set_sock_no_delay */
34 static int
mysqlnd_set_sock_no_delay(php_stream * stream)35 mysqlnd_set_sock_no_delay(php_stream * stream)
36 {
37 	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
38 	int ret = SUCCESS;
39 	int flag = 1;
40 	int result = setsockopt(socketd, IPPROTO_TCP,  TCP_NODELAY, (char *) &flag, sizeof(int));
41 
42 	DBG_ENTER("mysqlnd_set_sock_no_delay");
43 
44 	if (result == -1) {
45 		ret = FAILURE;
46 	}
47 
48 	DBG_RETURN(ret);
49 }
50 /* }}} */
51 
52 
53 /* {{{ mysqlnd_set_sock_keepalive */
54 static int
mysqlnd_set_sock_keepalive(php_stream * stream)55 mysqlnd_set_sock_keepalive(php_stream * stream)
56 {
57 	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
58 	int ret = SUCCESS;
59 	int flag = 1;
60 	int result = setsockopt(socketd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int));
61 
62 	DBG_ENTER("mysqlnd_set_sock_keepalive");
63 
64 	if (result == -1) {
65 		ret = FAILURE;
66 	}
67 
68 	DBG_RETURN(ret);
69 }
70 /* }}} */
71 
72 
73 /* {{{ mysqlnd_vio::network_read */
74 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,network_read)75 MYSQLND_METHOD(mysqlnd_vio, network_read)(MYSQLND_VIO * const vio, zend_uchar * const buffer, const size_t count,
76 										  MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
77 {
78 	enum_func_status return_value = PASS;
79 	php_stream * net_stream = vio->data->m.get_stream(vio);
80 	size_t to_read = count;
81 	zend_uchar * p = buffer;
82 
83 	DBG_ENTER("mysqlnd_vio::network_read");
84 	DBG_INF_FMT("count=%zu", count);
85 
86 	while (to_read) {
87 		ssize_t ret = php_stream_read(net_stream, (char *) p, to_read);
88 		if (ret <= 0) {
89 			DBG_ERR_FMT("Error while reading header from socket");
90 			return_value = FAIL;
91 			break;
92 		}
93 		p += ret;
94 		to_read -= ret;
95 	}
96 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read);
97 	DBG_RETURN(return_value);
98 }
99 /* }}} */
100 
101 
102 /* {{{ mysqlnd_vio::network_write */
103 static ssize_t
MYSQLND_METHOD(mysqlnd_vio,network_write)104 MYSQLND_METHOD(mysqlnd_vio, network_write)(MYSQLND_VIO * const vio, const zend_uchar * const buffer, const size_t count,
105 										   MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
106 {
107 	ssize_t ret;
108 	DBG_ENTER("mysqlnd_vio::network_write");
109 	DBG_INF_FMT("sending %zu bytes", count);
110 	ret = php_stream_write(vio->data->m.get_stream(vio), (char *)buffer, count);
111 	DBG_RETURN(ret);
112 }
113 /* }}} */
114 
mysqlnd_fixup_regular_list(php_stream * net_stream)115 static void mysqlnd_fixup_regular_list(php_stream *net_stream)
116 {
117 	/*
118 	  Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
119 	  be registered as resource (in EG(regular_list). So far, so good. However, it won't be
120 	  unregistered until the script ends. So, we need to take care of that.
121 	*/
122 	dtor_func_t origin_dtor = EG(regular_list).pDestructor;
123 	EG(regular_list).pDestructor = NULL;
124 	zend_hash_index_del(&EG(regular_list), net_stream->res->handle);
125 	EG(regular_list).pDestructor = origin_dtor;
126 	efree(net_stream->res);
127 	net_stream->res = NULL;
128 }
129 
130 /* {{{ mysqlnd_vio::open_pipe */
131 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,open_pipe)132 MYSQLND_METHOD(mysqlnd_vio, open_pipe)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
133 									   MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
134 {
135 	unsigned int streams_options = 0;
136 	php_stream * net_stream = NULL;
137 
138 	DBG_ENTER("mysqlnd_vio::open_pipe");
139 	if (persistent) {
140 		streams_options |= STREAM_OPEN_PERSISTENT;
141 	}
142 	streams_options |= IGNORE_URL;
143 	net_stream = php_stream_open_wrapper(scheme.s + sizeof("pipe://") - 1, "r+", streams_options, NULL);
144 	if (!net_stream) {
145 		SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown error while connecting");
146 		DBG_RETURN(NULL);
147 	}
148 
149 	if (persistent) {
150 		/* This is a similar hack as for mysqlnd_vio::open_tcp_or_unix.
151 		 * The main difference here is that we have no access to the hashed key.
152 		 * We can however perform a loop over the persistent resource list to find
153 		 * which one corresponds to our newly allocated stream.
154 		 * This loop is pretty cheap because it will normally either be the last entry or second to last entry
155 		 * in the list, depending on whether the socket connection itself is persistent or not.
156 		 * That's why we use a reverse loop. */
157 		Bucket *bucket;
158 		/* Use a bucket loop to make deletion cheap. */
159 		ZEND_HASH_MAP_REVERSE_FOREACH_BUCKET(&EG(persistent_list), bucket) {
160 			zend_resource *current_res = Z_RES(bucket->val);
161 			if (current_res->ptr == net_stream) {
162 				dtor_func_t origin_dtor = EG(persistent_list).pDestructor;
163 				EG(persistent_list).pDestructor = NULL;
164 				zend_hash_del_bucket(&EG(persistent_list), bucket);
165 				EG(persistent_list).pDestructor = origin_dtor;
166 				pefree(current_res, 1);
167 				break;
168 			}
169 		} ZEND_HASH_FOREACH_END();
170 #if ZEND_DEBUG
171 		php_stream_auto_cleanup(net_stream);
172 #endif
173 	}
174 
175 	mysqlnd_fixup_regular_list(net_stream);
176 
177 	DBG_RETURN(net_stream);
178 }
179 /* }}} */
180 
181 
182 /* {{{ mysqlnd_vio::open_tcp_or_unix */
183 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,open_tcp_or_unix)184 MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
185 											  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
186 {
187 	unsigned int streams_options = 0;
188 	unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
189 	char * hashed_details = NULL;
190 	int hashed_details_len = 0;
191 	zend_string *errstr = NULL;
192 	int errcode = 0;
193 	struct timeval tv;
194 	dtor_func_t origin_dtor;
195 	php_stream * net_stream = NULL;
196 
197 	DBG_ENTER("mysqlnd_vio::open_tcp_or_unix");
198 
199 	vio->data->stream = NULL;
200 
201 	if (persistent) {
202 		hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", vio);
203 		DBG_INF_FMT("hashed_details=%s", hashed_details);
204 	}
205 
206 	if (vio->data->options.timeout_connect) {
207 		tv.tv_sec = vio->data->options.timeout_connect;
208 		tv.tv_usec = 0;
209 	}
210 
211 	DBG_INF_FMT("calling php_stream_xport_create");
212 	net_stream = php_stream_xport_create(scheme.s, scheme.l, streams_options, streams_flags,
213 										  hashed_details, (vio->data->options.timeout_connect) ? &tv : NULL,
214 										  NULL /*ctx*/, &errstr, &errcode);
215 	if (errstr || !net_stream) {
216 		DBG_ERR("Error");
217 		if (hashed_details) {
218 			mnd_sprintf_free(hashed_details);
219 		}
220 		errcode = CR_CONNECTION_ERROR;
221 		SET_CLIENT_ERROR(error_info,
222 						 CR_CONNECTION_ERROR,
223 						 UNKNOWN_SQLSTATE,
224 						 errstr? ZSTR_VAL(errstr):"Unknown error while connecting");
225 		if (errstr) {
226 			zend_string_release_ex(errstr, 0);
227 		}
228 		DBG_RETURN(NULL);
229 	}
230 	if (hashed_details) {
231 		/*
232 		  If persistent, the streams register it in EG(persistent_list).
233 		  This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
234 		  whatever they have to.
235 		*/
236 		zend_resource *le;
237 
238 		if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_len))) {
239 			ZEND_ASSERT(le->ptr == net_stream);
240 			origin_dtor = EG(persistent_list).pDestructor;
241 			/*
242 			  in_free will let streams code skip destructing - big HACK,
243 			  but STREAMS suck big time regarding persistent streams.
244 			  Just not compatible for extensions that need persistency.
245 			*/
246 			EG(persistent_list).pDestructor = NULL;
247 			zend_hash_str_del(&EG(persistent_list), hashed_details, hashed_details_len);
248 			EG(persistent_list).pDestructor = origin_dtor;
249 			pefree(le, 1);
250 		}
251 #if ZEND_DEBUG
252 		/* Shut-up the streams, they don't know what they are doing */
253 		php_stream_auto_cleanup(net_stream);
254 #endif
255 		mnd_sprintf_free(hashed_details);
256 	}
257 
258 	mysqlnd_fixup_regular_list(net_stream);
259 	DBG_RETURN(net_stream);
260 }
261 /* }}} */
262 
263 
264 /* {{{ mysqlnd_vio::post_connect_set_opt */
265 static void
MYSQLND_METHOD(mysqlnd_vio,post_connect_set_opt)266 MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
267 												  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
268 {
269 	php_stream * net_stream = vio->data->m.get_stream(vio);
270 	DBG_ENTER("mysqlnd_vio::post_connect_set_opt");
271 	if (net_stream) {
272 		if (vio->data->options.timeout_read) {
273 			struct timeval tv;
274 			DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", vio->data->options.timeout_read);
275 			tv.tv_sec = vio->data->options.timeout_read;
276 			tv.tv_usec = 0;
277 			php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
278 		}
279 
280 		if (!memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1)) {
281 			/* TCP -> Set TCP_NODELAY */
282 			mysqlnd_set_sock_no_delay(net_stream);
283 			/* TCP -> Set SO_KEEPALIVE */
284 			mysqlnd_set_sock_keepalive(net_stream);
285 		}
286 
287 		net_stream->chunk_size = vio->data->options.net_read_buffer_size;
288 		net_stream->flags |= PHP_STREAM_FLAG_SUPPRESS_ERRORS;
289 	}
290 
291 	DBG_VOID_RETURN;
292 }
293 /* }}} */
294 
295 
296 /* {{{ mysqlnd_vio::get_open_stream */
297 static func_mysqlnd_vio__open_stream
MYSQLND_METHOD(mysqlnd_vio,get_open_stream)298 MYSQLND_METHOD(mysqlnd_vio, get_open_stream)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
299 											 MYSQLND_ERROR_INFO * const error_info)
300 {
301 	func_mysqlnd_vio__open_stream ret = NULL;
302 	DBG_ENTER("mysqlnd_vio::get_open_stream");
303 	if (scheme.l > (sizeof("pipe://") - 1) && !memcmp(scheme.s, "pipe://", sizeof("pipe://") - 1)) {
304 		ret = vio->data->m.open_pipe;
305 	} else if ((scheme.l > (sizeof("tcp://") - 1) && !memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1))
306 				||
307 				(scheme.l > (sizeof("unix://") - 1) && !memcmp(scheme.s, "unix://", sizeof("unix://") - 1)))
308 	{
309 		ret = vio->data->m.open_tcp_or_unix;
310 	}
311 
312 	if (!ret) {
313 		SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "No handler for this scheme");
314 	}
315 
316 	DBG_RETURN(ret);
317 }
318 /* }}} */
319 
320 
321 /* {{{ mysqlnd_vio::connect */
322 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,connect)323 MYSQLND_METHOD(mysqlnd_vio, connect)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
324 									 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
325 {
326 	enum_func_status ret = FAIL;
327 	func_mysqlnd_vio__open_stream open_stream = NULL;
328 	DBG_ENTER("mysqlnd_vio::connect");
329 
330 	vio->data->m.close_stream(vio, conn_stats, error_info);
331 
332 	open_stream = vio->data->m.get_open_stream(vio, scheme, error_info);
333 	if (open_stream) {
334 		php_stream * net_stream = open_stream(vio, scheme, persistent, conn_stats, error_info);
335 		if (net_stream && PASS == vio->data->m.set_stream(vio, net_stream)) {
336 			vio->data->m.post_connect_set_opt(vio, scheme, conn_stats, error_info);
337 			ret = PASS;
338 		}
339 	}
340 
341 	DBG_RETURN(ret);
342 }
343 /* }}} */
344 
345 
346 /* {{{ mysqlnd_vio::set_client_option */
347 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,set_client_option)348 MYSQLND_METHOD(mysqlnd_vio, set_client_option)(MYSQLND_VIO * const net, enum_mysqlnd_client_option option, const char * const value)
349 {
350 	DBG_ENTER("mysqlnd_vio::set_client_option");
351 	DBG_INF_FMT("option=%u", option);
352 	switch (option) {
353 		case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
354 			DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
355 			net->data->options.net_read_buffer_size = *(unsigned int*) value;
356 			DBG_INF_FMT("new_length=%zu", net->data->options.net_read_buffer_size);
357 			break;
358 		case MYSQL_OPT_CONNECT_TIMEOUT:
359 			DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
360 			net->data->options.timeout_connect = *(unsigned int*) value;
361 			break;
362 		case MYSQLND_OPT_SSL_KEY:
363 			{
364 				bool pers = net->persistent;
365 				if (net->data->options.ssl_key) {
366 					mnd_pefree(net->data->options.ssl_key, pers);
367 				}
368 				net->data->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
369 				break;
370 			}
371 		case MYSQLND_OPT_SSL_CERT:
372 			{
373 				bool pers = net->persistent;
374 				if (net->data->options.ssl_cert) {
375 					mnd_pefree(net->data->options.ssl_cert, pers);
376 				}
377 				net->data->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
378 				break;
379 			}
380 		case MYSQLND_OPT_SSL_CA:
381 			{
382 				bool pers = net->persistent;
383 				if (net->data->options.ssl_ca) {
384 					mnd_pefree(net->data->options.ssl_ca, pers);
385 				}
386 				net->data->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
387 				break;
388 			}
389 		case MYSQLND_OPT_SSL_CAPATH:
390 			{
391 				bool pers = net->persistent;
392 				if (net->data->options.ssl_capath) {
393 					mnd_pefree(net->data->options.ssl_capath, pers);
394 				}
395 				net->data->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
396 				break;
397 			}
398 		case MYSQLND_OPT_SSL_CIPHER:
399 			{
400 				bool pers = net->persistent;
401 				if (net->data->options.ssl_cipher) {
402 					mnd_pefree(net->data->options.ssl_cipher, pers);
403 				}
404 				net->data->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
405 				break;
406 			}
407 		case MYSQLND_OPT_SSL_PASSPHRASE:
408 			{
409 				bool pers = net->persistent;
410 				if (net->data->options.ssl_passphrase) {
411 					mnd_pefree(net->data->options.ssl_passphrase, pers);
412 				}
413 				net->data->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
414 				break;
415 			}
416 		case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
417 		{
418 			enum mysqlnd_ssl_peer val = *((enum mysqlnd_ssl_peer *)value);
419 			switch (val) {
420 				case MYSQLND_SSL_PEER_VERIFY:
421 					DBG_INF("MYSQLND_SSL_PEER_VERIFY");
422 					break;
423 				case MYSQLND_SSL_PEER_DONT_VERIFY:
424 					DBG_INF("MYSQLND_SSL_PEER_DONT_VERIFY");
425 					break;
426 				case MYSQLND_SSL_PEER_DEFAULT:
427 					DBG_INF("MYSQLND_SSL_PEER_DEFAULT");
428 					val = MYSQLND_SSL_PEER_DEFAULT;
429 					break;
430 				default:
431 					DBG_INF("default = MYSQLND_SSL_PEER_DEFAULT_ACTION");
432 					val = MYSQLND_SSL_PEER_DEFAULT;
433 					break;
434 			}
435 			net->data->options.ssl_verify_peer = val;
436 			break;
437 		}
438 		case MYSQL_OPT_READ_TIMEOUT:
439 			net->data->options.timeout_read = *(unsigned int*) value;
440 			break;
441 #ifdef WHEN_SUPPORTED_BY_MYSQLI
442 		case MYSQL_OPT_WRITE_TIMEOUT:
443 			net->data->options.timeout_write = *(unsigned int*) value;
444 			break;
445 #endif
446 		default:
447 			DBG_RETURN(FAIL);
448 	}
449 	DBG_RETURN(PASS);
450 }
451 /* }}} */
452 
453 
454 /* {{{ mysqlnd_vio::consume_uneaten_data */
455 size_t
MYSQLND_METHOD(mysqlnd_vio,consume_uneaten_data)456 MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data)(MYSQLND_VIO * const net, enum php_mysqlnd_server_command cmd)
457 {
458 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
459 	/*
460 	  Switch to non-blocking mode and try to consume something from
461 	  the line, if possible, then continue. This saves us from looking for
462 	  the actual place where out-of-order packets have been sent.
463 	  If someone is completely sure that everything is fine, he can switch it
464 	  off.
465 	*/
466 	char tmp_buf[256];
467 	size_t skipped_bytes = 0;
468 	int opt = PHP_STREAM_OPTION_BLOCKING;
469 	php_stream * net_stream = net->data->get_stream(net);
470 	int was_blocked = net_stream->ops->set_option(net_stream, opt, 0, NULL);
471 
472 	DBG_ENTER("mysqlnd_vio::consume_uneaten_data");
473 
474 	if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
475 		/* Do a read of 1 byte */
476 		ssize_t bytes_consumed;
477 
478 		do {
479 			bytes_consumed = php_stream_read(net_stream, tmp_buf, sizeof(tmp_buf));
480 			if (bytes_consumed <= 0) {
481 				break;
482 			}
483 			skipped_bytes += bytes_consumed;
484 		} while (bytes_consumed == sizeof(tmp_buf));
485 
486 		if (was_blocked) {
487 			net_stream->ops->set_option(net_stream, opt, 1, NULL);
488 		}
489 
490 		if (bytes_consumed) {
491 			DBG_ERR_FMT("Skipped %u bytes. Last command hasn't consumed all the output from the server",
492 						bytes_consumed, mysqlnd_command_to_text[net->last_command]);
493 			php_error_docref(NULL, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
494 							 "consumed all the output from the server",
495 							 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
496 		}
497 	}
498 	net->last_command = cmd;
499 
500 	DBG_RETURN(skipped_bytes);
501 #else
502 	return 0;
503 #endif
504 }
505 /* }}} */
506 
507 /*
508   in libmyusql, if cert and !key then key=cert
509 */
510 /* {{{ mysqlnd_vio::enable_ssl */
511 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,enable_ssl)512 MYSQLND_METHOD(mysqlnd_vio, enable_ssl)(MYSQLND_VIO * const net)
513 {
514 #ifdef MYSQLND_SSL_SUPPORTED
515 	php_stream_context * context = php_stream_context_alloc();
516 	php_stream * net_stream = net->data->m.get_stream(net);
517 	bool any_flag = FALSE;
518 
519 	DBG_ENTER("mysqlnd_vio::enable_ssl");
520 
521 	if (net->data->options.ssl_key) {
522 		zval key_zval;
523 		ZVAL_STRING(&key_zval, net->data->options.ssl_key);
524 		php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
525 		zval_ptr_dtor(&key_zval);
526 		any_flag = TRUE;
527 	}
528 	if (net->data->options.ssl_cert) {
529 		zval cert_zval;
530 		ZVAL_STRING(&cert_zval, net->data->options.ssl_cert);
531 		php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
532 		if (!net->data->options.ssl_key) {
533 			php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
534 		}
535 		zval_ptr_dtor(&cert_zval);
536 		any_flag = TRUE;
537 	}
538 	if (net->data->options.ssl_ca) {
539 		zval cafile_zval;
540 		ZVAL_STRING(&cafile_zval, net->data->options.ssl_ca);
541 		php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
542 		zval_ptr_dtor(&cafile_zval);
543 		any_flag = TRUE;
544 	}
545 	if (net->data->options.ssl_capath) {
546 		zval capath_zval;
547 		ZVAL_STRING(&capath_zval, net->data->options.ssl_capath);
548 		php_stream_context_set_option(context, "ssl", "capath", &capath_zval);
549 		zval_ptr_dtor(&capath_zval);
550 		any_flag = TRUE;
551 	}
552 	if (net->data->options.ssl_passphrase) {
553 		zval passphrase_zval;
554 		ZVAL_STRING(&passphrase_zval, net->data->options.ssl_passphrase);
555 		php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
556 		zval_ptr_dtor(&passphrase_zval);
557 		any_flag = TRUE;
558 	}
559 	if (net->data->options.ssl_cipher) {
560 		zval cipher_zval;
561 		ZVAL_STRING(&cipher_zval, net->data->options.ssl_cipher);
562 		php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
563 		zval_ptr_dtor(&cipher_zval);
564 		any_flag = TRUE;
565 	}
566 	{
567 		zval verify_peer_zval;
568 		bool verify;
569 
570 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DEFAULT) {
571 			net->data->options.ssl_verify_peer = any_flag? MYSQLND_SSL_PEER_DEFAULT_ACTION:MYSQLND_SSL_PEER_DONT_VERIFY;
572 		}
573 
574 		verify = net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_VERIFY? TRUE:FALSE;
575 
576 		DBG_INF_FMT("VERIFY=%d", verify);
577 		ZVAL_BOOL(&verify_peer_zval, verify);
578 		php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
579 		php_stream_context_set_option(context, "ssl", "verify_peer_name", &verify_peer_zval);
580 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DONT_VERIFY) {
581 			ZVAL_TRUE(&verify_peer_zval);
582 			php_stream_context_set_option(context, "ssl", "allow_self_signed", &verify_peer_zval);
583 		}
584 	}
585 	php_stream_context_set(net_stream, context);
586 	/* php_stream_context_set() increases the refcount of context, but we just want to transfer ownership
587 	 * hence the need to decrease the refcount so the refcount will be equal to 1. */
588 	ZEND_ASSERT(GC_REFCOUNT(context->res) == 2);
589 	GC_DELREF(context->res);
590 	if (php_stream_xport_crypto_setup(net_stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL) < 0 ||
591 	    php_stream_xport_crypto_enable(net_stream, 1) < 0)
592 	{
593 		DBG_ERR("Cannot connect to MySQL by using SSL");
594 		DBG_RETURN(FAIL);
595 	}
596 	net->data->ssl = TRUE;
597 	/*
598 	  get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
599 	  then the context would not survive cleaning of EG(regular_list), where it is registered, as a
600 	  resource. What happens is that after this destruction any use of the network will mean usage
601 	  of the context, which means usage of already freed memory, bad. Actually we don't need this
602 	  context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
603 	*/
604 	php_stream_context_set(net_stream, NULL);
605 
606 	if (net->data->options.timeout_read) {
607 		struct timeval tv;
608 		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
609 		tv.tv_sec = net->data->options.timeout_read;
610 		tv.tv_usec = 0;
611 		php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
612 	}
613 
614 	DBG_RETURN(PASS);
615 #else
616 	DBG_ENTER("mysqlnd_vio::enable_ssl");
617 	DBG_INF("MYSQLND_SSL_SUPPORTED is not defined");
618 	DBG_RETURN(PASS);
619 #endif
620 }
621 /* }}} */
622 
623 
624 /* {{{ mysqlnd_vio::disable_ssl */
625 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,disable_ssl)626 MYSQLND_METHOD(mysqlnd_vio, disable_ssl)(MYSQLND_VIO * const vio)
627 {
628 	DBG_ENTER("mysqlnd_vio::disable_ssl");
629 	DBG_RETURN(PASS);
630 }
631 /* }}} */
632 
633 
634 /* {{{ mysqlnd_vio::free_contents */
635 static void
MYSQLND_METHOD(mysqlnd_vio,free_contents)636 MYSQLND_METHOD(mysqlnd_vio, free_contents)(MYSQLND_VIO * net)
637 {
638 	bool pers = net->persistent;
639 	DBG_ENTER("mysqlnd_vio::free_contents");
640 
641 	if (net->data->options.ssl_key) {
642 		mnd_pefree(net->data->options.ssl_key, pers);
643 		net->data->options.ssl_key = NULL;
644 	}
645 	if (net->data->options.ssl_cert) {
646 		mnd_pefree(net->data->options.ssl_cert, pers);
647 		net->data->options.ssl_cert = NULL;
648 	}
649 	if (net->data->options.ssl_ca) {
650 		mnd_pefree(net->data->options.ssl_ca, pers);
651 		net->data->options.ssl_ca = NULL;
652 	}
653 	if (net->data->options.ssl_capath) {
654 		mnd_pefree(net->data->options.ssl_capath, pers);
655 		net->data->options.ssl_capath = NULL;
656 	}
657 	if (net->data->options.ssl_cipher) {
658 		mnd_pefree(net->data->options.ssl_cipher, pers);
659 		net->data->options.ssl_cipher = NULL;
660 	}
661 
662 	DBG_VOID_RETURN;
663 }
664 /* }}} */
665 
666 
667 /* {{{ mysqlnd_vio::close_stream */
668 static void
MYSQLND_METHOD(mysqlnd_vio,close_stream)669 MYSQLND_METHOD(mysqlnd_vio, close_stream)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
670 {
671 	php_stream * net_stream;
672 	DBG_ENTER("mysqlnd_vio::close_stream");
673 	if (net && (net_stream = net->data->m.get_stream(net))) {
674 		bool pers = net->persistent;
675 		DBG_INF_FMT("Freeing stream. abstract=%p", net_stream->abstract);
676 		/* We removed the resource from the stream, so pass FREE_RSRC_DTOR now to force
677 		 * destruction to occur during shutdown, because it won't happen through the resource
678 		 * because we removed the resource from the EG resource list(s). */
679 		if (pers) {
680 			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
681 		} else {
682 			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
683 		}
684 		net->data->m.set_stream(net, NULL);
685 	}
686 
687 	DBG_VOID_RETURN;
688 }
689 /* }}} */
690 
691 
692 /* {{{ mysqlnd_vio::init */
693 static void
MYSQLND_METHOD(mysqlnd_vio,init)694 MYSQLND_METHOD(mysqlnd_vio, init)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
695 {
696 	unsigned int buf_size;
697 	DBG_ENTER("mysqlnd_vio::init");
698 
699 	buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
700 	net->data->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size);
701 
702 	buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
703 	net->data->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size);
704 
705 	DBG_VOID_RETURN;
706 }
707 /* }}} */
708 
709 
710 /* {{{ mysqlnd_vio::dtor */
711 static void
MYSQLND_METHOD(mysqlnd_vio,dtor)712 MYSQLND_METHOD(mysqlnd_vio, dtor)(MYSQLND_VIO * const vio, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
713 {
714 	DBG_ENTER("mysqlnd_vio::dtor");
715 	if (vio) {
716 		vio->data->m.free_contents(vio);
717 		vio->data->m.close_stream(vio, stats, error_info);
718 
719 		mnd_pefree(vio, vio->persistent);
720 	}
721 	DBG_VOID_RETURN;
722 }
723 /* }}} */
724 
725 
726 /* {{{ mysqlnd_vio::get_stream */
727 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,get_stream)728 MYSQLND_METHOD(mysqlnd_vio, get_stream)(const MYSQLND_VIO * const net)
729 {
730 	DBG_ENTER("mysqlnd_vio::get_stream");
731 	DBG_INF_FMT("%p", net? net->data->stream:NULL);
732 	DBG_RETURN(net? net->data->stream:NULL);
733 }
734 /* }}} */
735 
736 
737 /* {{{ mysqlnd_vio::set_stream */
738 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,set_stream)739 MYSQLND_METHOD(mysqlnd_vio, set_stream)(MYSQLND_VIO * const vio, php_stream * net_stream)
740 {
741 	DBG_ENTER("mysqlnd_vio::set_stream");
742 	if (vio) {
743 		vio->data->stream = net_stream;
744 		DBG_RETURN(PASS);
745 	}
746 	DBG_RETURN(FAIL);
747 }
748 /* }}} */
749 
750 
751 /* {{{ mysqlnd_vio::has_valid_stream */
752 static bool
MYSQLND_METHOD(mysqlnd_vio,has_valid_stream)753 MYSQLND_METHOD(mysqlnd_vio, has_valid_stream)(const MYSQLND_VIO * const vio)
754 {
755 	DBG_ENTER("mysqlnd_vio::has_valid_stream");
756 	DBG_INF_FMT("%p %p", vio, vio? vio->data->stream:NULL);
757 	DBG_RETURN((vio && vio->data->stream)? TRUE: FALSE);
758 }
759 /* }}} */
760 
761 
762 MYSQLND_CLASS_METHODS_START(mysqlnd_vio)
763 	MYSQLND_METHOD(mysqlnd_vio, init),
764 	MYSQLND_METHOD(mysqlnd_vio, dtor),
765 
766 	MYSQLND_METHOD(mysqlnd_vio, connect),
767 
768 	MYSQLND_METHOD(mysqlnd_vio, close_stream),
769 	MYSQLND_METHOD(mysqlnd_vio, open_pipe),
770 	MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix),
771 
772 	MYSQLND_METHOD(mysqlnd_vio, get_stream),
773 	MYSQLND_METHOD(mysqlnd_vio, set_stream),
774 	MYSQLND_METHOD(mysqlnd_vio, has_valid_stream),
775 	MYSQLND_METHOD(mysqlnd_vio, get_open_stream),
776 
777 	MYSQLND_METHOD(mysqlnd_vio, set_client_option),
778 	MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt),
779 
780 	MYSQLND_METHOD(mysqlnd_vio, enable_ssl),
781 	MYSQLND_METHOD(mysqlnd_vio, disable_ssl),
782 
783 	MYSQLND_METHOD(mysqlnd_vio, network_read),
784 	MYSQLND_METHOD(mysqlnd_vio, network_write),
785 
786 	MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data),
787 
788 	MYSQLND_METHOD(mysqlnd_vio, free_contents),
789 MYSQLND_CLASS_METHODS_END;
790 
791 
792 /* {{{ mysqlnd_vio_init */
793 PHPAPI MYSQLND_VIO *
mysqlnd_vio_init(bool persistent,MYSQLND_CLASS_METHODS_TYPE (mysqlnd_object_factory)* object_factory,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)794 mysqlnd_vio_init(bool persistent, MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *object_factory, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
795 {
796 	MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *factory = object_factory? object_factory : &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory);
797 	MYSQLND_VIO * vio;
798 	DBG_ENTER("mysqlnd_vio_init");
799 	vio = factory->get_vio(persistent, stats, error_info);
800 	DBG_RETURN(vio);
801 }
802 /* }}} */
803 
804 
805 /* {{{ mysqlnd_vio_free */
806 PHPAPI void
mysqlnd_vio_free(MYSQLND_VIO * const vio,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)807 mysqlnd_vio_free(MYSQLND_VIO * const vio, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
808 {
809 	DBG_ENTER("mysqlnd_vio_free");
810 	if (vio) {
811 		vio->data->m.dtor(vio, stats, error_info);
812 	}
813 	DBG_VOID_RETURN;
814 }
815 /* }}} */
816