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