xref: /PHP-7.0/ext/mysqlnd/mysqlnd_net.c (revision 478f119a)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2017 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   |          Georg Richter <georg@php.net>                               |
18   +----------------------------------------------------------------------+
19 */
20 
21 #include "php.h"
22 #include "php_globals.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_priv.h"
25 #include "mysqlnd_wireprotocol.h"
26 #include "mysqlnd_statistics.h"
27 #include "mysqlnd_debug.h"
28 #include "mysqlnd_ext_plugin.h"
29 #include "php_network.h"
30 #include "zend_ini.h"
31 #ifdef MYSQLND_COMPRESSION_ENABLED
32 #include <zlib.h>
33 #endif
34 
35 #ifndef PHP_WIN32
36 #include <netinet/tcp.h>
37 #else
38 #include <winsock.h>
39 #endif
40 
41 
42 /* {{{ mysqlnd_set_sock_no_delay */
43 static int
mysqlnd_set_sock_no_delay(php_stream * stream)44 mysqlnd_set_sock_no_delay(php_stream * stream)
45 {
46 
47 	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
48 	int ret = SUCCESS;
49 	int flag = 1;
50 	int result = setsockopt(socketd, IPPROTO_TCP,  TCP_NODELAY, (char *) &flag, sizeof(int));
51 
52 	DBG_ENTER("mysqlnd_set_sock_no_delay");
53 
54 	if (result == -1) {
55 		ret = FAILURE;
56 	}
57 
58 	DBG_RETURN(ret);
59 }
60 /* }}} */
61 
62 
63 /* {{{ mysqlnd_set_sock_keepalive */
64 static int
mysqlnd_set_sock_keepalive(php_stream * stream)65 mysqlnd_set_sock_keepalive(php_stream * stream)
66 {
67 
68 	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
69 	int ret = SUCCESS;
70 	int flag = 1;
71 	int result = setsockopt(socketd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int));
72 
73 	DBG_ENTER("mysqlnd_set_sock_keepalive");
74 
75 	if (result == -1) {
76 		ret = FAILURE;
77 	}
78 
79 	DBG_RETURN(ret);
80 }
81 /* }}} */
82 
83 
84 /* {{{ mysqlnd_net::network_read_ex */
85 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,network_read_ex)86 MYSQLND_METHOD(mysqlnd_net, network_read_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
87 											 MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
88 {
89 	enum_func_status return_value = PASS;
90 	php_stream * net_stream = net->data->m.get_stream(net);
91 	size_t old_chunk_size = net_stream->chunk_size;
92 	size_t to_read = count, ret;
93 	zend_uchar * p = buffer;
94 
95 	DBG_ENTER("mysqlnd_net::network_read_ex");
96 	DBG_INF_FMT("count="MYSQLND_SZ_T_SPEC, count);
97 
98 	net_stream->chunk_size = MIN(to_read, net->data->options.net_read_buffer_size);
99 	while (to_read) {
100 		if (!(ret = php_stream_read(net_stream, (char *) p, to_read))) {
101 			DBG_ERR_FMT("Error while reading header from socket");
102 			return_value = FAIL;
103 			break;
104 		}
105 		p += ret;
106 		to_read -= ret;
107 	}
108 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read);
109 	net_stream->chunk_size = old_chunk_size;
110 	DBG_RETURN(return_value);
111 }
112 /* }}} */
113 
114 
115 /* {{{ mysqlnd_net::network_write_ex */
116 static size_t
MYSQLND_METHOD(mysqlnd_net,network_write_ex)117 MYSQLND_METHOD(mysqlnd_net, network_write_ex)(MYSQLND_NET * const net, const zend_uchar * const buffer, const size_t count,
118 											  MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
119 {
120 	size_t ret;
121 	DBG_ENTER("mysqlnd_net::network_write_ex");
122 	DBG_INF_FMT("sending %u bytes", count);
123 	ret = php_stream_write(net->data->m.get_stream(net), (char *)buffer, count);
124 	DBG_RETURN(ret);
125 }
126 /* }}} */
127 
128 
129 /* {{{ mysqlnd_net::open_pipe */
130 static php_stream *
MYSQLND_METHOD(mysqlnd_net,open_pipe)131 MYSQLND_METHOD(mysqlnd_net, open_pipe)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
132 									   const zend_bool persistent,
133 									   MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
134 {
135 #if PHP_API_VERSION < 20100412
136 	unsigned int streams_options = ENFORCE_SAFE_MODE;
137 #else
138 	unsigned int streams_options = 0;
139 #endif
140 	dtor_func_t origin_dtor;
141 	php_stream * net_stream = NULL;
142 
143 	DBG_ENTER("mysqlnd_net::open_pipe");
144 	if (persistent) {
145 		streams_options |= STREAM_OPEN_PERSISTENT;
146 	}
147 	streams_options |= IGNORE_URL;
148 	net_stream = php_stream_open_wrapper((char*) scheme + sizeof("pipe://") - 1, "r+", streams_options, NULL);
149 	if (!net_stream) {
150 		SET_CLIENT_ERROR(*error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown errror while connecting");
151 		DBG_RETURN(NULL);
152 	}
153 	/*
154 	  Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
155 	  be registered as resource (in EG(regular_list). So far, so good. However, it won't be
156 	  unregistered until the script ends. So, we need to take care of that.
157 	*/
158 	origin_dtor = EG(regular_list).pDestructor;
159 	EG(regular_list).pDestructor = NULL;
160 	zend_hash_index_del(&EG(regular_list), net_stream->res->handle); /* ToDO: should it be res->handle, do streams register with addref ?*/
161 	EG(regular_list).pDestructor = origin_dtor;
162 	net_stream->res = NULL;
163 
164 	DBG_RETURN(net_stream);
165 }
166 /* }}} */
167 
168 
169 /* {{{ mysqlnd_net::open_tcp_or_unix */
170 static php_stream *
MYSQLND_METHOD(mysqlnd_net,open_tcp_or_unix)171 MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
172 											  const zend_bool persistent,
173 											  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
174 {
175 #if PHP_API_VERSION < 20100412
176 	unsigned int streams_options = ENFORCE_SAFE_MODE;
177 #else
178 	unsigned int streams_options = 0;
179 #endif
180 	unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
181 	char * hashed_details = NULL;
182 	int hashed_details_len = 0;
183 	zend_string *errstr = NULL;
184 	int errcode = 0;
185 	struct timeval tv;
186 	dtor_func_t origin_dtor;
187 	php_stream * net_stream = NULL;
188 
189 	DBG_ENTER("mysqlnd_net::open_tcp_or_unix");
190 
191 	net->data->stream = NULL;
192 
193 	if (persistent) {
194 		hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", net);
195 		DBG_INF_FMT("hashed_details=%s", hashed_details);
196 	}
197 
198 	if (net->data->options.timeout_connect) {
199 		tv.tv_sec = net->data->options.timeout_connect;
200 		tv.tv_usec = 0;
201 	}
202 
203 	DBG_INF_FMT("calling php_stream_xport_create");
204 	net_stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags,
205 										  hashed_details, (net->data->options.timeout_connect) ? &tv : NULL,
206 										  NULL /*ctx*/, &errstr, &errcode);
207 	if (errstr || !net_stream) {
208 		DBG_ERR("Error");
209 		if (hashed_details) {
210 			mnd_sprintf_free(hashed_details);
211 		}
212 		errcode = CR_CONNECTION_ERROR;
213 		SET_CLIENT_ERROR(*error_info,
214 						 CR_CONNECTION_ERROR,
215 						 UNKNOWN_SQLSTATE,
216 						 errstr? ZSTR_VAL(errstr):"Unknown error while connecting");
217 		if (errstr) {
218 			zend_string_release(errstr);
219 		}
220 		DBG_RETURN(NULL);
221 	}
222 	if (hashed_details) {
223 		/*
224 		  If persistent, the streams register it in EG(persistent_list).
225 		  This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
226 		  whatever they have to.
227 		*/
228 		zend_resource *le;
229 
230 		if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_len))) {
231 			origin_dtor = EG(persistent_list).pDestructor;
232 			/*
233 			  in_free will let streams code skip destructing - big HACK,
234 			  but STREAMS suck big time regarding persistent streams.
235 			  Just not compatible for extensions that need persistency.
236 			*/
237 			EG(persistent_list).pDestructor = NULL;
238 			zend_hash_str_del(&EG(persistent_list), hashed_details, hashed_details_len);
239 			EG(persistent_list).pDestructor = origin_dtor;
240 			pefree(le, 1);
241 		}
242 #if ZEND_DEBUG
243 		/* Shut-up the streams, they don't know what they are doing */
244 		net_stream->__exposed = 1;
245 #endif
246 		mnd_sprintf_free(hashed_details);
247 	}
248 
249 	/*
250 	  Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
251 	  be registered as resource (in EG(regular_list). So far, so good. However, it won't be
252 	  unregistered until the script ends. So, we need to take care of that.
253 	*/
254 	origin_dtor = EG(regular_list).pDestructor;
255 	EG(regular_list).pDestructor = NULL;
256 	zend_hash_index_del(&EG(regular_list), net_stream->res->handle); /* ToDO: should it be res->handle, do streams register with addref ?*/
257 	efree(net_stream->res);
258 	net_stream->res = NULL;
259 	EG(regular_list).pDestructor = origin_dtor;
260 	DBG_RETURN(net_stream);
261 }
262 /* }}} */
263 
264 
265 /* {{{ mysqlnd_net::post_connect_set_opt */
266 static void
MYSQLND_METHOD(mysqlnd_net,post_connect_set_opt)267 MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt)(MYSQLND_NET * const net,
268 												  const char * const scheme, const size_t scheme_len,
269 												  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
270 {
271 	php_stream * net_stream = net->data->m.get_stream(net);
272 	DBG_ENTER("mysqlnd_net::post_connect_set_opt");
273 	if (net_stream) {
274 		if (net->data->options.timeout_read) {
275 			struct timeval tv;
276 			DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
277 			tv.tv_sec = net->data->options.timeout_read;
278 			tv.tv_usec = 0;
279 			php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
280 		}
281 
282 		if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) {
283 			/* TCP -> Set TCP_NODELAY */
284 			mysqlnd_set_sock_no_delay(net_stream);
285 			/* TCP -> Set SO_KEEPALIVE */
286 			mysqlnd_set_sock_keepalive(net_stream);
287 		}
288 	}
289 
290 	DBG_VOID_RETURN;
291 }
292 /* }}} */
293 
294 
295 /* {{{ mysqlnd_net::get_open_stream */
296 static func_mysqlnd_net__open_stream
MYSQLND_METHOD(mysqlnd_net,get_open_stream)297 MYSQLND_METHOD(mysqlnd_net, get_open_stream)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
298 											 MYSQLND_ERROR_INFO * const error_info)
299 {
300 	func_mysqlnd_net__open_stream ret = NULL;
301 	DBG_ENTER("mysqlnd_net::get_open_stream");
302 	if (scheme_len > (sizeof("pipe://") - 1) && !memcmp(scheme, "pipe://", sizeof("pipe://") - 1)) {
303 		ret = net->data->m.open_pipe;
304 	} else if ((scheme_len > (sizeof("tcp://") - 1) && !memcmp(scheme, "tcp://", sizeof("tcp://") - 1))
305 				||
306 				(scheme_len > (sizeof("unix://") - 1) && !memcmp(scheme, "unix://", sizeof("unix://") - 1)))
307 	{
308 		ret = net->data->m.open_tcp_or_unix;
309 	}
310 
311 	if (!ret) {
312 		SET_CLIENT_ERROR(*error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "No handler for this scheme");
313 	}
314 
315 	DBG_RETURN(ret);
316 }
317 /* }}} */
318 
319 
320 /* {{{ mysqlnd_net::connect_ex */
321 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,connect_ex)322 MYSQLND_METHOD(mysqlnd_net, connect_ex)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
323 										const zend_bool persistent,
324 										MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
325 {
326 	enum_func_status ret = FAIL;
327 	func_mysqlnd_net__open_stream open_stream = NULL;
328 	DBG_ENTER("mysqlnd_net::connect_ex");
329 
330 	net->packet_no = net->compressed_envelope_packet_no = 0;
331 
332 	net->data->m.close_stream(net, conn_stats, error_info);
333 
334 	open_stream = net->data->m.get_open_stream(net, scheme, scheme_len, error_info);
335 	if (open_stream) {
336 		php_stream * net_stream = open_stream(net, scheme, scheme_len, persistent, conn_stats, error_info);
337 		if (net_stream) {
338 			(void) net->data->m.set_stream(net, net_stream);
339 			net->data->m.post_connect_set_opt(net, scheme, scheme_len, conn_stats, error_info);
340 			ret = PASS;
341 		}
342 	}
343 
344 	DBG_RETURN(ret);
345 }
346 /* }}} */
347 
348 
349 /* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
350 #define COPY_HEADER(T,A)  do { \
351 		*(((char *)(T)))   = *(((char *)(A)));\
352 		*(((char *)(T))+1) = *(((char *)(A))+1);\
353 		*(((char *)(T))+2) = *(((char *)(A))+2);\
354 		*(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
355 #define STORE_HEADER_SIZE(safe_storage, buffer)  COPY_HEADER((safe_storage), (buffer))
356 #define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
357 
358 
359 /* {{{ mysqlnd_net::send_ex */
360 /*
361   IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!!
362 			  This is done for performance reasons in the caller of this function.
363 			  Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
364 			  Neither are quick, thus the clients of this function are obligated to do
365 			  what they are asked for.
366 
367   `count` is actually the length of the payload data. Thus :
368   count + MYSQLND_HEADER_SIZE = sizeof(buffer) (not the pointer but the actual buffer)
369 */
370 static size_t
MYSQLND_METHOD(mysqlnd_net,send_ex)371 MYSQLND_METHOD(mysqlnd_net, send_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
372 									 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
373 {
374 	zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
375 	zend_uchar * safe_storage = safe_buf;
376 	size_t bytes_sent, packets_sent = 1;
377 	size_t left = count;
378 	zend_uchar * p = (zend_uchar *) buffer;
379 	zend_uchar * compress_buf = NULL;
380 	size_t to_be_sent;
381 
382 	DBG_ENTER("mysqlnd_net::send_ex");
383 	DBG_INF_FMT("count=" MYSQLND_SZ_T_SPEC " compression=%u", count, net->data->compressed);
384 
385 	if (net->data->compressed == TRUE) {
386 		size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
387 		DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size);
388 		compress_buf = mnd_emalloc(comp_buf_size);
389 	}
390 
391 	do {
392 		to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
393 		DBG_INF_FMT("to_be_sent=%u", to_be_sent);
394 		DBG_INF_FMT("packets_sent=%u", packets_sent);
395 		DBG_INF_FMT("compressed_envelope_packet_no=%u", net->compressed_envelope_packet_no);
396 		DBG_INF_FMT("packet_no=%u", net->packet_no);
397 #ifdef MYSQLND_COMPRESSION_ENABLED
398 		if (net->data->compressed == TRUE) {
399 			/* here we need to compress the data and then write it, first comes the compressed header */
400 			size_t tmp_complen = to_be_sent;
401 			size_t payload_size;
402 			zend_uchar * uncompressed_payload = p; /* should include the header */
403 
404 			STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
405 			int3store(uncompressed_payload, to_be_sent);
406 			int1store(uncompressed_payload + 3, net->packet_no);
407 			if (PASS == net->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
408 									   uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE))
409 			{
410 				int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
411 				payload_size = tmp_complen;
412 			} else {
413 				int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
414 				memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
415 				payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
416 			}
417 			RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
418 
419 			int3store(compress_buf, payload_size);
420 			int1store(compress_buf + 3, net->packet_no);
421 			DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
422 			bytes_sent = net->data->m.network_write_ex(net, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE,
423 												 conn_stats, error_info);
424 			net->compressed_envelope_packet_no++;
425   #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
426 			if (res == Z_OK) {
427 				size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
428 				zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
429 				int error = net->data->m.decode(decompressed_data, decompressed_size,
430 										  compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
431 				if (error == Z_OK) {
432 					int i;
433 					DBG_INF("success decompressing");
434 					for (i = 0 ; i < decompressed_size; i++) {
435 						if (i && (i % 30 == 0)) {
436 							printf("\n\t\t");
437 						}
438 						printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
439 						DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
440 					}
441 				} else {
442 					DBG_INF("error decompressing");
443 				}
444 				mnd_free(decompressed_data);
445 			}
446   #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
447 		} else
448 #endif /* MYSQLND_COMPRESSION_ENABLED */
449 		{
450 			DBG_INF("no compression");
451 			STORE_HEADER_SIZE(safe_storage, p);
452 			int3store(p, to_be_sent);
453 			int1store(p + 3, net->packet_no);
454 			bytes_sent = net->data->m.network_write_ex(net, p, to_be_sent + MYSQLND_HEADER_SIZE, conn_stats, error_info);
455 			RESTORE_HEADER_SIZE(p, safe_storage);
456 			net->compressed_envelope_packet_no++;
457 		}
458 		net->packet_no++;
459 
460 		p += to_be_sent;
461 		left -= to_be_sent;
462 		packets_sent++;
463 		/*
464 		  if left is 0 then there is nothing more to send, but if the last packet was exactly
465 		  with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
466 		  empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
467 		  indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
468 		  packet will be sent and this loop will end.
469 		*/
470 	} while (bytes_sent && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
471 
472 	DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no);
473 
474 	MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats,
475 			STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
476 			STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
477 			STAT_PACKETS_SENT, packets_sent);
478 
479 	if (compress_buf) {
480 		mnd_efree(compress_buf);
481 	}
482 
483 	/* Even for zero size payload we have to send a packet */
484 	if (!bytes_sent) {
485 		DBG_ERR_FMT("Can't %u send bytes", count);
486 		SET_CLIENT_ERROR(*error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
487 	}
488 	DBG_RETURN(bytes_sent);
489 }
490 /* }}} */
491 
492 
493 #ifdef MYSQLND_COMPRESSION_ENABLED
494 /* {{{ php_mysqlnd_read_buffer_is_empty */
495 static zend_bool
php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)496 php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)
497 {
498 	return buffer->len? FALSE:TRUE;
499 }
500 /* }}} */
501 
502 
503 /* {{{ php_mysqlnd_read_buffer_read */
504 static void
php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer,size_t count,zend_uchar * dest)505 php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest)
506 {
507 	if (buffer->len >= count) {
508 		memcpy(dest, buffer->data + buffer->offset, count);
509 		buffer->offset += count;
510 		buffer->len -= count;
511 	}
512 }
513 /* }}} */
514 
515 
516 /* {{{ php_mysqlnd_read_buffer_bytes_left */
517 static size_t
php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)518 php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)
519 {
520 	return buffer->len;
521 }
522 /* }}} */
523 
524 
525 /* {{{ php_mysqlnd_read_buffer_free */
526 static void
php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer)527 php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer)
528 {
529 	DBG_ENTER("php_mysqlnd_read_buffer_free");
530 	if (*buffer) {
531 		mnd_efree((*buffer)->data);
532 		mnd_efree(*buffer);
533 		*buffer = NULL;
534 	}
535 	DBG_VOID_RETURN;
536 }
537 /* }}} */
538 
539 
540 /* {{{ php_mysqlnd_create_read_buffer */
541 static MYSQLND_READ_BUFFER *
mysqlnd_create_read_buffer(size_t count)542 mysqlnd_create_read_buffer(size_t count)
543 {
544 	MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER));
545 	DBG_ENTER("mysqlnd_create_read_buffer");
546 	ret->is_empty = php_mysqlnd_read_buffer_is_empty;
547 	ret->read = php_mysqlnd_read_buffer_read;
548 	ret->bytes_left = php_mysqlnd_read_buffer_bytes_left;
549 	ret->free_buffer = php_mysqlnd_read_buffer_free;
550 	ret->data = mnd_emalloc(count);
551 	ret->size = ret->len = count;
552 	ret->offset = 0;
553 	DBG_RETURN(ret);
554 }
555 /* }}} */
556 
557 
558 /* {{{ mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffer */
559 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,read_compressed_packet_from_stream_and_fill_read_buffer)560 MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer)
561 		(MYSQLND_NET * net, size_t net_payload_size, MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info)
562 {
563 	size_t decompressed_size;
564 	enum_func_status retval = PASS;
565 	zend_uchar * compressed_data = NULL;
566 	zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
567 	DBG_ENTER("mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffer");
568 
569 	/* Read the compressed header */
570 	if (FAIL == net->data->m.network_read_ex(net, comp_header, COMPRESSED_HEADER_SIZE, conn_stats, error_info)) {
571 		DBG_RETURN(FAIL);
572 	}
573 	decompressed_size = uint3korr(comp_header);
574 
575 	/* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
576 	/* we need to decompress the data */
577 
578 	if (decompressed_size) {
579 		compressed_data = mnd_emalloc(net_payload_size);
580 		if (FAIL == net->data->m.network_read_ex(net, compressed_data, net_payload_size, conn_stats, error_info)) {
581 			retval = FAIL;
582 			goto end;
583 		}
584 		net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size);
585 		retval = net->data->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size);
586 		if (FAIL == retval) {
587 			goto end;
588 		}
589 	} else {
590 		DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size);
591 		net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size);
592 		if (FAIL == net->data->m.network_read_ex(net, net->uncompressed_data->data, net_payload_size, conn_stats, error_info)) {
593 			retval = FAIL;
594 			goto end;
595 		}
596 	}
597 end:
598 	if (compressed_data) {
599 		mnd_efree(compressed_data);
600 	}
601 	DBG_RETURN(retval);
602 }
603 /* }}} */
604 #endif /* MYSQLND_COMPRESSION_ENABLED */
605 
606 
607 /* {{{ mysqlnd_net::decode */
608 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,decode)609 MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, const size_t uncompressed_data_len,
610 									const zend_uchar * const compressed_data, const size_t compressed_data_len)
611 {
612 #ifdef MYSQLND_COMPRESSION_ENABLED
613 	int error;
614 	uLongf tmp_complen = uncompressed_data_len;
615 	DBG_ENTER("mysqlnd_net::decode");
616 	error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
617 
618 	DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len);
619 	if (error != Z_OK) {
620 		DBG_INF_FMT("decompression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
621 	}
622 	DBG_RETURN(error == Z_OK? PASS:FAIL);
623 #else
624 	DBG_ENTER("mysqlnd_net::decode");
625 	DBG_RETURN(FAIL);
626 #endif
627 }
628 /* }}} */
629 
630 
631 /* {{{ mysqlnd_net::encode */
632 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,encode)633 MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
634 									const zend_uchar * const uncompressed_data, const size_t uncompressed_data_len)
635 {
636 #ifdef MYSQLND_COMPRESSION_ENABLED
637 	int error;
638 	uLongf tmp_complen = *compress_buffer_len;
639 	DBG_ENTER("mysqlnd_net::encode");
640 	error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
641 
642 	if (error != Z_OK) {
643 		DBG_INF_FMT("compression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
644 	} else {
645 		*compress_buffer_len = tmp_complen;
646 		DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
647 	}
648 
649 	DBG_RETURN(error == Z_OK? PASS:FAIL);
650 #else
651 	DBG_ENTER("mysqlnd_net::encode");
652 	DBG_RETURN(FAIL);
653 #endif
654 }
655 /* }}} */
656 
657 
658 /* {{{ mysqlnd_net::receive_ex */
659 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,receive_ex)660 MYSQLND_METHOD(mysqlnd_net, receive_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
661 										MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
662 {
663 	size_t to_read = count;
664 	zend_uchar * p = buffer;
665 
666 	DBG_ENTER("mysqlnd_net::receive_ex");
667 #ifdef MYSQLND_COMPRESSION_ENABLED
668 	if (net->data->compressed) {
669 		if (net->uncompressed_data) {
670 			size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read);
671 			DBG_INF_FMT("reading "MYSQLND_SZ_T_SPEC" from uncompressed_data buffer", to_read_from_buffer);
672 			if (to_read_from_buffer) {
673 				net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
674 				p += to_read_from_buffer;
675 				to_read -= to_read_from_buffer;
676 			}
677 			DBG_INF_FMT("left "MYSQLND_SZ_T_SPEC" to read", to_read);
678 			if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) {
679 				/* Everything was consumed. This should never happen here, but for security */
680 				net->uncompressed_data->free_buffer(&net->uncompressed_data);
681 			}
682 		}
683 		if (to_read) {
684 			zend_uchar net_header[MYSQLND_HEADER_SIZE];
685 			size_t net_payload_size;
686 			zend_uchar packet_no;
687 
688 			if (FAIL == net->data->m.network_read_ex(net, net_header, MYSQLND_HEADER_SIZE, conn_stats, error_info)) {
689 				DBG_RETURN(FAIL);
690 			}
691 			net_payload_size = uint3korr(net_header);
692 			packet_no = uint1korr(net_header + 3);
693 			if (net->compressed_envelope_packet_no != packet_no) {
694 				DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
695 							net->compressed_envelope_packet_no, packet_no, net_payload_size);
696 
697 				php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
698 						  net->compressed_envelope_packet_no, packet_no, net_payload_size);
699 				DBG_RETURN(FAIL);
700 			}
701 			net->compressed_envelope_packet_no++;
702 #ifdef MYSQLND_DUMP_HEADER_N_BODY
703 			DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (zend_ulong) net_payload_size);
704 #endif
705 			/* Now let's read from the wire, decompress it and fill the read buffer */
706 			net->data->m.read_compressed_packet_from_stream_and_fill_read_buffer(net, net_payload_size, conn_stats, error_info);
707 
708 			/*
709 			  Now a bit of recursion - read from the read buffer,
710 			  if the data which we have just read from the wire
711 			  is not enough, then the recursive call will try to
712 			  satisfy it until it is satisfied.
713 			*/
714 			DBG_RETURN(net->data->m.receive_ex(net, p, to_read, conn_stats, error_info));
715 		}
716 		DBG_RETURN(PASS);
717 	}
718 #endif /* MYSQLND_COMPRESSION_ENABLED */
719 	DBG_RETURN(net->data->m.network_read_ex(net, p, to_read, conn_stats, error_info));
720 }
721 /* }}} */
722 
723 
724 /* {{{ mysqlnd_net::set_client_option */
725 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,set_client_option)726 MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value)
727 {
728 	DBG_ENTER("mysqlnd_net::set_client_option");
729 	DBG_INF_FMT("option=%u", option);
730 	switch (option) {
731 		case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
732 			DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
733 			if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
734 				DBG_RETURN(FAIL);
735 			}
736 			net->cmd_buffer.length = *(unsigned int*) value;
737 			DBG_INF_FMT("new_length="MYSQLND_SZ_T_SPEC, net->cmd_buffer.length);
738 			if (!net->cmd_buffer.buffer) {
739 				net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent);
740 			} else {
741 				net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent);
742 			}
743 			break;
744 		case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
745 			DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
746 			net->data->options.net_read_buffer_size = *(unsigned int*) value;
747 			DBG_INF_FMT("new_length="MYSQLND_SZ_T_SPEC, net->data->options.net_read_buffer_size);
748 			break;
749 		case MYSQL_OPT_CONNECT_TIMEOUT:
750 			DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
751 			net->data->options.timeout_connect = *(unsigned int*) value;
752 			break;
753 		case MYSQLND_OPT_SSL_KEY:
754 			{
755 				zend_bool pers = net->persistent;
756 				if (net->data->options.ssl_key) {
757 					mnd_pefree(net->data->options.ssl_key, pers);
758 				}
759 				net->data->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
760 				break;
761 			}
762 		case MYSQLND_OPT_SSL_CERT:
763 			{
764 				zend_bool pers = net->persistent;
765 				if (net->data->options.ssl_cert) {
766 					mnd_pefree(net->data->options.ssl_cert, pers);
767 				}
768 				net->data->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
769 				break;
770 			}
771 		case MYSQLND_OPT_SSL_CA:
772 			{
773 				zend_bool pers = net->persistent;
774 				if (net->data->options.ssl_ca) {
775 					mnd_pefree(net->data->options.ssl_ca, pers);
776 				}
777 				net->data->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
778 				break;
779 			}
780 		case MYSQLND_OPT_SSL_CAPATH:
781 			{
782 				zend_bool pers = net->persistent;
783 				if (net->data->options.ssl_capath) {
784 					mnd_pefree(net->data->options.ssl_capath, pers);
785 				}
786 				net->data->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
787 				break;
788 			}
789 		case MYSQLND_OPT_SSL_CIPHER:
790 			{
791 				zend_bool pers = net->persistent;
792 				if (net->data->options.ssl_cipher) {
793 					mnd_pefree(net->data->options.ssl_cipher, pers);
794 				}
795 				net->data->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
796 				break;
797 			}
798 		case MYSQLND_OPT_SSL_PASSPHRASE:
799 			{
800 				zend_bool pers = net->persistent;
801 				if (net->data->options.ssl_passphrase) {
802 					mnd_pefree(net->data->options.ssl_passphrase, pers);
803 				}
804 				net->data->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
805 				break;
806 			}
807 		case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
808 		{
809 			enum mysqlnd_ssl_peer val = *((enum mysqlnd_ssl_peer *)value);
810 			switch (val) {
811 				case MYSQLND_SSL_PEER_VERIFY:
812 					DBG_INF("MYSQLND_SSL_PEER_VERIFY");
813 					break;
814 				case MYSQLND_SSL_PEER_DONT_VERIFY:
815 					DBG_INF("MYSQLND_SSL_PEER_DONT_VERIFY");
816 					break;
817 				case MYSQLND_SSL_PEER_DEFAULT:
818 					DBG_INF("MYSQLND_SSL_PEER_DEFAULT");
819 					val = MYSQLND_SSL_PEER_DEFAULT;
820 					break;
821 				default:
822 					DBG_INF("default = MYSQLND_SSL_PEER_DEFAULT_ACTION");
823 					val = MYSQLND_SSL_PEER_DEFAULT;
824 					break;
825 			}
826 			net->data->options.ssl_verify_peer = val;
827 			break;
828 		}
829 		case MYSQL_OPT_READ_TIMEOUT:
830 			net->data->options.timeout_read = *(unsigned int*) value;
831 			break;
832 #ifdef WHEN_SUPPORTED_BY_MYSQLI
833 		case MYSQL_OPT_WRITE_TIMEOUT:
834 			net->data->options.timeout_write = *(unsigned int*) value;
835 			break;
836 #endif
837 		case MYSQL_OPT_COMPRESS:
838 			net->data->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION;
839 			break;
840 		case MYSQL_SERVER_PUBLIC_KEY:
841 			{
842 				zend_bool pers = net->persistent;
843 				if (net->data->options.sha256_server_public_key) {
844 					mnd_pefree(net->data->options.sha256_server_public_key, pers);
845 				}
846 				net->data->options.sha256_server_public_key = value? mnd_pestrdup(value, pers) : NULL;
847 				break;
848 			}
849 		default:
850 			DBG_RETURN(FAIL);
851 	}
852 	DBG_RETURN(PASS);
853 }
854 /* }}} */
855 
856 /* {{{ mysqlnd_net::consume_uneaten_data */
857 size_t
MYSQLND_METHOD(mysqlnd_net,consume_uneaten_data)858 MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd)
859 {
860 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
861 	/*
862 	  Switch to non-blocking mode and try to consume something from
863 	  the line, if possible, then continue. This saves us from looking for
864 	  the actual place where out-of-order packets have been sent.
865 	  If someone is completely sure that everything is fine, he can switch it
866 	  off.
867 	*/
868 	char tmp_buf[256];
869 	size_t skipped_bytes = 0;
870 	int opt = PHP_STREAM_OPTION_BLOCKING;
871 	php_stream * net_stream = net->data->get_stream(net);
872 	int was_blocked = net_stream->ops->set_option(net_stream, opt, 0, NULL);
873 
874 	DBG_ENTER("mysqlnd_net::consume_uneaten_data");
875 
876 	if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
877 		/* Do a read of 1 byte */
878 		int bytes_consumed;
879 
880 		do {
881 			skipped_bytes += (bytes_consumed = php_stream_read(net_stream, tmp_buf, sizeof(tmp_buf)));
882 		} while (bytes_consumed == sizeof(tmp_buf));
883 
884 		if (was_blocked) {
885 			net_stream->ops->set_option(net_stream, opt, 1, NULL);
886 		}
887 
888 		if (bytes_consumed) {
889 			DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server",
890 						bytes_consumed, mysqlnd_command_to_text[net->last_command]);
891 			php_error_docref(NULL, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
892 							 "consumed all the output from the server",
893 							 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
894 		}
895 	}
896 	net->last_command = cmd;
897 
898 	DBG_RETURN(skipped_bytes);
899 #else
900 	return 0;
901 #endif
902 }
903 /* }}} */
904 
905 /*
906   in libmyusql, if cert and !key then key=cert
907 */
908 /* {{{ mysqlnd_net::enable_ssl */
909 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,enable_ssl)910 MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net)
911 {
912 #ifdef MYSQLND_SSL_SUPPORTED
913 	php_stream_context * context = php_stream_context_alloc();
914 	php_stream * net_stream = net->data->m.get_stream(net);
915 	zend_bool any_flag = FALSE;
916 
917 	DBG_ENTER("mysqlnd_net::enable_ssl");
918 	if (!context) {
919 		DBG_RETURN(FAIL);
920 	}
921 
922 	if (net->data->options.ssl_key) {
923 		zval key_zval;
924 		ZVAL_STRING(&key_zval, net->data->options.ssl_key);
925 		php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
926 		zval_ptr_dtor(&key_zval);
927 		any_flag = TRUE;
928 	}
929 	if (net->data->options.ssl_cert) {
930 		zval cert_zval;
931 		ZVAL_STRING(&cert_zval, net->data->options.ssl_cert);
932 		php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
933 		if (!net->data->options.ssl_key) {
934 			php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
935 		}
936 		zval_ptr_dtor(&cert_zval);
937 		any_flag = TRUE;
938 	}
939 	if (net->data->options.ssl_ca) {
940 		zval cafile_zval;
941 		ZVAL_STRING(&cafile_zval, net->data->options.ssl_ca);
942 		php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
943 		any_flag = TRUE;
944 	}
945 	if (net->data->options.ssl_capath) {
946 		zval capath_zval;
947 		ZVAL_STRING(&capath_zval, net->data->options.ssl_capath);
948 		php_stream_context_set_option(context, "ssl", "capath", &capath_zval);
949 		zval_ptr_dtor(&capath_zval);
950 		any_flag = TRUE;
951 	}
952 	if (net->data->options.ssl_passphrase) {
953 		zval passphrase_zval;
954 		ZVAL_STRING(&passphrase_zval, net->data->options.ssl_passphrase);
955 		php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
956 		zval_ptr_dtor(&passphrase_zval);
957 		any_flag = TRUE;
958 	}
959 	if (net->data->options.ssl_cipher) {
960 		zval cipher_zval;
961 		ZVAL_STRING(&cipher_zval, net->data->options.ssl_cipher);
962 		php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
963 		zval_ptr_dtor(&cipher_zval);
964 		any_flag = TRUE;
965 	}
966 	{
967 		zval verify_peer_zval;
968 		zend_bool verify;
969 
970 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DEFAULT) {
971 			net->data->options.ssl_verify_peer = any_flag? MYSQLND_SSL_PEER_DEFAULT_ACTION:MYSQLND_SSL_PEER_DONT_VERIFY;
972 		}
973 
974 		verify = net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_VERIFY? TRUE:FALSE;
975 
976 		DBG_INF_FMT("VERIFY=%d", verify);
977 		ZVAL_BOOL(&verify_peer_zval, verify);
978 		php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
979 		php_stream_context_set_option(context, "ssl", "verify_peer_name", &verify_peer_zval);
980 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DONT_VERIFY) {
981 			ZVAL_TRUE(&verify_peer_zval);
982 			php_stream_context_set_option(context, "ssl", "allow_self_signed", &verify_peer_zval);
983 		}
984 	}
985 #if PHP_API_VERSION >= 20131106
986 	php_stream_context_set(net_stream, context);
987 #else
988 	php_stream_context_set(net_stream, context);
989 #endif
990 	if (php_stream_xport_crypto_setup(net_stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL) < 0 ||
991 	    php_stream_xport_crypto_enable(net_stream, 1) < 0)
992 	{
993 		DBG_ERR("Cannot connect to MySQL by using SSL");
994 		php_error_docref(NULL, E_WARNING, "Cannot connect to MySQL by using SSL");
995 		DBG_RETURN(FAIL);
996 	}
997 	net->data->ssl = TRUE;
998 	/*
999 	  get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
1000 	  then the context would not survive cleaning of EG(regular_list), where it is registered, as a
1001 	  resource. What happens is that after this destruction any use of the network will mean usage
1002 	  of the context, which means usage of already freed memory, bad. Actually we don't need this
1003 	  context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
1004 	*/
1005 #if PHP_API_VERSION >= 20131106
1006 	php_stream_context_set(net_stream, NULL);
1007 #else
1008 	php_stream_context_set(net_stream, NULL);
1009 #endif
1010 
1011 	if (net->data->options.timeout_read) {
1012 		struct timeval tv;
1013 		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
1014 		tv.tv_sec = net->data->options.timeout_read;
1015 		tv.tv_usec = 0;
1016 		php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
1017 	}
1018 
1019 	DBG_RETURN(PASS);
1020 #else
1021 	DBG_ENTER("mysqlnd_net::enable_ssl");
1022 	DBG_INF("MYSQLND_SSL_SUPPORTED is not defined");
1023 	DBG_RETURN(PASS);
1024 #endif
1025 }
1026 /* }}} */
1027 
1028 
1029 /* {{{ mysqlnd_net::disable_ssl */
1030 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,disable_ssl)1031 MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net)
1032 {
1033 	DBG_ENTER("mysqlnd_net::disable_ssl");
1034 	DBG_RETURN(PASS);
1035 }
1036 /* }}} */
1037 
1038 
1039 /* {{{ mysqlnd_net::free_contents */
1040 static void
MYSQLND_METHOD(mysqlnd_net,free_contents)1041 MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net)
1042 {
1043 	zend_bool pers = net->persistent;
1044 	DBG_ENTER("mysqlnd_net::free_contents");
1045 
1046 #ifdef MYSQLND_COMPRESSION_ENABLED
1047 	if (net->uncompressed_data) {
1048 		net->uncompressed_data->free_buffer(&net->uncompressed_data);
1049 	}
1050 #endif
1051 	if (net->data->options.ssl_key) {
1052 		mnd_pefree(net->data->options.ssl_key, pers);
1053 		net->data->options.ssl_key = NULL;
1054 	}
1055 	if (net->data->options.ssl_cert) {
1056 		mnd_pefree(net->data->options.ssl_cert, pers);
1057 		net->data->options.ssl_cert = NULL;
1058 	}
1059 	if (net->data->options.ssl_ca) {
1060 		mnd_pefree(net->data->options.ssl_ca, pers);
1061 		net->data->options.ssl_ca = NULL;
1062 	}
1063 	if (net->data->options.ssl_capath) {
1064 		mnd_pefree(net->data->options.ssl_capath, pers);
1065 		net->data->options.ssl_capath = NULL;
1066 	}
1067 	if (net->data->options.ssl_cipher) {
1068 		mnd_pefree(net->data->options.ssl_cipher, pers);
1069 		net->data->options.ssl_cipher = NULL;
1070 	}
1071 	if (net->data->options.sha256_server_public_key) {
1072 		mnd_pefree(net->data->options.sha256_server_public_key, pers);
1073 		net->data->options.sha256_server_public_key = NULL;
1074 	}
1075 
1076 	DBG_VOID_RETURN;
1077 }
1078 /* }}} */
1079 
1080 
1081 /* {{{ mysqlnd_net::close_stream */
1082 static void
MYSQLND_METHOD(mysqlnd_net,close_stream)1083 MYSQLND_METHOD(mysqlnd_net, close_stream)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1084 {
1085 	php_stream * net_stream;
1086 	DBG_ENTER("mysqlnd_net::close_stream");
1087 	if (net && (net_stream = net->data->m.get_stream(net))) {
1088 		zend_bool pers = net->persistent;
1089 		DBG_INF_FMT("Freeing stream. abstract=%p", net_stream->abstract);
1090 		if (pers) {
1091 			if (EG(active)) {
1092 				php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
1093 			} else {
1094 				/*
1095 				  otherwise we will crash because the EG(persistent_list) has been freed already,
1096 				  before the modules are shut down
1097 				*/
1098 				php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1099 			}
1100 		} else {
1101 			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE);
1102 		}
1103 		(void) net->data->m.set_stream(net, NULL);
1104 	}
1105 
1106 	DBG_VOID_RETURN;
1107 }
1108 /* }}} */
1109 
1110 
1111 /* {{{ mysqlnd_net::init */
1112 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,init)1113 MYSQLND_METHOD(mysqlnd_net, init)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1114 {
1115 	unsigned int buf_size;
1116 	DBG_ENTER("mysqlnd_net::init");
1117 
1118 	buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
1119 	net->data->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size);
1120 
1121 	buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
1122 	net->data->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size);
1123 
1124 	buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
1125 	net->data->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size);
1126 
1127 	DBG_RETURN(PASS);
1128 }
1129 /* }}} */
1130 
1131 
1132 /* {{{ mysqlnd_net::dtor */
1133 static void
MYSQLND_METHOD(mysqlnd_net,dtor)1134 MYSQLND_METHOD(mysqlnd_net, dtor)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1135 {
1136 	DBG_ENTER("mysqlnd_net::dtor");
1137 	if (net) {
1138 		net->data->m.free_contents(net);
1139 		net->data->m.close_stream(net, stats, error_info);
1140 
1141 		if (net->cmd_buffer.buffer) {
1142 			DBG_INF("Freeing cmd buffer");
1143 			mnd_pefree(net->cmd_buffer.buffer, net->persistent);
1144 			net->cmd_buffer.buffer = NULL;
1145 		}
1146 
1147 		mnd_pefree(net->data, net->data->persistent);
1148 		mnd_pefree(net, net->persistent);
1149 	}
1150 	DBG_VOID_RETURN;
1151 }
1152 /* }}} */
1153 
1154 
1155 /* {{{ mysqlnd_net::get_stream */
1156 static php_stream *
MYSQLND_METHOD(mysqlnd_net,get_stream)1157 MYSQLND_METHOD(mysqlnd_net, get_stream)(const MYSQLND_NET * const net)
1158 {
1159 	DBG_ENTER("mysqlnd_net::get_stream");
1160 	DBG_INF_FMT("%p", net? net->data->stream:NULL);
1161 	DBG_RETURN(net? net->data->stream:NULL);
1162 }
1163 /* }}} */
1164 
1165 
1166 /* {{{ mysqlnd_net::set_stream */
1167 static php_stream *
MYSQLND_METHOD(mysqlnd_net,set_stream)1168 MYSQLND_METHOD(mysqlnd_net, set_stream)(MYSQLND_NET * const net, php_stream * net_stream)
1169 {
1170 	php_stream * ret = NULL;
1171 	DBG_ENTER("mysqlnd_net::set_stream");
1172 	if (net) {
1173 		net->data->stream = net_stream;
1174 		ret = net->data->stream;
1175 	}
1176 	DBG_RETURN(ret);
1177 }
1178 /* }}} */
1179 
1180 
1181 MYSQLND_CLASS_METHODS_START(mysqlnd_net)
1182 	MYSQLND_METHOD(mysqlnd_net, init),
1183 	MYSQLND_METHOD(mysqlnd_net, dtor),
1184 	MYSQLND_METHOD(mysqlnd_net, connect_ex),
1185 	MYSQLND_METHOD(mysqlnd_net, close_stream),
1186 	MYSQLND_METHOD(mysqlnd_net, open_pipe),
1187 	MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix),
1188 	MYSQLND_METHOD(mysqlnd_net, get_stream),
1189 	MYSQLND_METHOD(mysqlnd_net, set_stream),
1190 	MYSQLND_METHOD(mysqlnd_net, get_open_stream),
1191 	MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt),
1192 	MYSQLND_METHOD(mysqlnd_net, set_client_option),
1193 	MYSQLND_METHOD(mysqlnd_net, decode),
1194 	MYSQLND_METHOD(mysqlnd_net, encode),
1195 	MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
1196 	MYSQLND_METHOD(mysqlnd_net, free_contents),
1197 	MYSQLND_METHOD(mysqlnd_net, enable_ssl),
1198 	MYSQLND_METHOD(mysqlnd_net, disable_ssl),
1199 	MYSQLND_METHOD(mysqlnd_net, network_read_ex),
1200 	MYSQLND_METHOD(mysqlnd_net, network_write_ex),
1201 	MYSQLND_METHOD(mysqlnd_net, send_ex),
1202 	MYSQLND_METHOD(mysqlnd_net, receive_ex),
1203 #ifdef MYSQLND_COMPRESSION_ENABLED
1204 	MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer),
1205 #else
1206 	NULL,
1207 #endif
1208 	NULL, /* unused 1 */
1209 	NULL, /* unused 2 */
1210 	NULL, /* unused 3 */
1211 	NULL, /* unused 4 */
1212 	NULL  /* unused 5 */
1213 MYSQLND_CLASS_METHODS_END;
1214 
1215 
1216 /* {{{ mysqlnd_net_init */
1217 PHPAPI MYSQLND_NET *
mysqlnd_net_init(zend_bool persistent,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)1218 mysqlnd_net_init(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
1219 {
1220 	MYSQLND_NET * net;
1221 	DBG_ENTER("mysqlnd_net_init");
1222 	net = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_io_channel(persistent, stats, error_info);
1223 	DBG_RETURN(net);
1224 }
1225 /* }}} */
1226 
1227 
1228 /* {{{ mysqlnd_net_free */
1229 PHPAPI void
mysqlnd_net_free(MYSQLND_NET * const net,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)1230 mysqlnd_net_free(MYSQLND_NET * const net, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
1231 {
1232 	DBG_ENTER("mysqlnd_net_free");
1233 	if (net) {
1234 		net->data->m.dtor(net, stats, error_info);
1235 	}
1236 	DBG_VOID_RETURN;
1237 }
1238 /* }}} */
1239 
1240 
1241 
1242 /*
1243  * Local variables:
1244  * tab-width: 4
1245  * c-basic-offset: 4
1246  * End:
1247  * vim600: noet sw=4 ts=4 fdm=marker
1248  * vim<600: noet sw=4 ts=4
1249  */
1250