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