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