xref: /PHP-7.1/ext/mysqlnd/mysqlnd_net.c (revision ccd4716e)
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 			net->data->options.timeout_read = *(unsigned int*) value;
822 			break;
823 #ifdef WHEN_SUPPORTED_BY_MYSQLI
824 		case MYSQL_OPT_WRITE_TIMEOUT:
825 			net->data->options.timeout_write = *(unsigned int*) value;
826 			break;
827 #endif
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 	if (!context) {
910 		DBG_RETURN(FAIL);
911 	}
912 
913 	if (net->data->options.ssl_key) {
914 		zval key_zval;
915 		ZVAL_STRING(&key_zval, net->data->options.ssl_key);
916 		php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
917 		zval_ptr_dtor(&key_zval);
918 		any_flag = TRUE;
919 	}
920 	if (net->data->options.ssl_cert) {
921 		zval cert_zval;
922 		ZVAL_STRING(&cert_zval, net->data->options.ssl_cert);
923 		php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
924 		if (!net->data->options.ssl_key) {
925 			php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
926 		}
927 		zval_ptr_dtor(&cert_zval);
928 		any_flag = TRUE;
929 	}
930 	if (net->data->options.ssl_ca) {
931 		zval cafile_zval;
932 		ZVAL_STRING(&cafile_zval, net->data->options.ssl_ca);
933 		php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
934 		any_flag = TRUE;
935 	}
936 	if (net->data->options.ssl_capath) {
937 		zval capath_zval;
938 		ZVAL_STRING(&capath_zval, net->data->options.ssl_capath);
939 		php_stream_context_set_option(context, "ssl", "capath", &capath_zval);
940 		zval_ptr_dtor(&capath_zval);
941 		any_flag = TRUE;
942 	}
943 	if (net->data->options.ssl_passphrase) {
944 		zval passphrase_zval;
945 		ZVAL_STRING(&passphrase_zval, net->data->options.ssl_passphrase);
946 		php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
947 		zval_ptr_dtor(&passphrase_zval);
948 		any_flag = TRUE;
949 	}
950 	if (net->data->options.ssl_cipher) {
951 		zval cipher_zval;
952 		ZVAL_STRING(&cipher_zval, net->data->options.ssl_cipher);
953 		php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
954 		zval_ptr_dtor(&cipher_zval);
955 		any_flag = TRUE;
956 	}
957 	{
958 		zval verify_peer_zval;
959 		zend_bool verify;
960 
961 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DEFAULT) {
962 			net->data->options.ssl_verify_peer = any_flag? MYSQLND_SSL_PEER_DEFAULT_ACTION:MYSQLND_SSL_PEER_DONT_VERIFY;
963 		}
964 
965 		verify = net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_VERIFY? TRUE:FALSE;
966 
967 		DBG_INF_FMT("VERIFY=%d", verify);
968 		ZVAL_BOOL(&verify_peer_zval, verify);
969 		php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
970 		php_stream_context_set_option(context, "ssl", "verify_peer_name", &verify_peer_zval);
971 		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DONT_VERIFY) {
972 			ZVAL_TRUE(&verify_peer_zval);
973 			php_stream_context_set_option(context, "ssl", "allow_self_signed", &verify_peer_zval);
974 		}
975 	}
976 	php_stream_context_set(net_stream, context);
977 	if (php_stream_xport_crypto_setup(net_stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL) < 0 ||
978 	    php_stream_xport_crypto_enable(net_stream, 1) < 0)
979 	{
980 		DBG_ERR("Cannot connect to MySQL by using SSL");
981 		php_error_docref(NULL, E_WARNING, "Cannot connect to MySQL by using SSL");
982 		DBG_RETURN(FAIL);
983 	}
984 	net->data->ssl = TRUE;
985 	/*
986 	  get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
987 	  then the context would not survive cleaning of EG(regular_list), where it is registered, as a
988 	  resource. What happens is that after this destruction any use of the network will mean usage
989 	  of the context, which means usage of already freed memory, bad. Actually we don't need this
990 	  context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
991 	*/
992 	php_stream_context_set(net_stream, NULL);
993 
994 	if (net->data->options.timeout_read) {
995 		struct timeval tv;
996 		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
997 		tv.tv_sec = net->data->options.timeout_read;
998 		tv.tv_usec = 0;
999 		php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
1000 	}
1001 
1002 	DBG_RETURN(PASS);
1003 #else
1004 	DBG_ENTER("mysqlnd_net::enable_ssl");
1005 	DBG_INF("MYSQLND_SSL_SUPPORTED is not defined");
1006 	DBG_RETURN(PASS);
1007 #endif
1008 }
1009 /* }}} */
1010 
1011 
1012 /* {{{ mysqlnd_net::disable_ssl */
1013 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,disable_ssl)1014 MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net)
1015 {
1016 	DBG_ENTER("mysqlnd_net::disable_ssl");
1017 	DBG_RETURN(PASS);
1018 }
1019 /* }}} */
1020 
1021 
1022 /* {{{ mysqlnd_net::free_contents */
1023 static void
MYSQLND_METHOD(mysqlnd_net,free_contents)1024 MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net)
1025 {
1026 	zend_bool pers = net->persistent;
1027 	DBG_ENTER("mysqlnd_net::free_contents");
1028 
1029 #ifdef MYSQLND_COMPRESSION_ENABLED
1030 	if (net->uncompressed_data) {
1031 		net->uncompressed_data->free_buffer(&net->uncompressed_data);
1032 	}
1033 #endif
1034 	if (net->data->options.ssl_key) {
1035 		mnd_pefree(net->data->options.ssl_key, pers);
1036 		net->data->options.ssl_key = NULL;
1037 	}
1038 	if (net->data->options.ssl_cert) {
1039 		mnd_pefree(net->data->options.ssl_cert, pers);
1040 		net->data->options.ssl_cert = NULL;
1041 	}
1042 	if (net->data->options.ssl_ca) {
1043 		mnd_pefree(net->data->options.ssl_ca, pers);
1044 		net->data->options.ssl_ca = NULL;
1045 	}
1046 	if (net->data->options.ssl_capath) {
1047 		mnd_pefree(net->data->options.ssl_capath, pers);
1048 		net->data->options.ssl_capath = NULL;
1049 	}
1050 	if (net->data->options.ssl_cipher) {
1051 		mnd_pefree(net->data->options.ssl_cipher, pers);
1052 		net->data->options.ssl_cipher = NULL;
1053 	}
1054 	if (net->data->options.sha256_server_public_key) {
1055 		mnd_pefree(net->data->options.sha256_server_public_key, pers);
1056 		net->data->options.sha256_server_public_key = NULL;
1057 	}
1058 
1059 	DBG_VOID_RETURN;
1060 }
1061 /* }}} */
1062 
1063 
1064 /* {{{ mysqlnd_net::close_stream */
1065 static void
MYSQLND_METHOD(mysqlnd_net,close_stream)1066 MYSQLND_METHOD(mysqlnd_net, close_stream)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1067 {
1068 	php_stream * net_stream;
1069 	DBG_ENTER("mysqlnd_net::close_stream");
1070 	if (net && (net_stream = net->data->m.get_stream(net))) {
1071 		zend_bool pers = net->persistent;
1072 		DBG_INF_FMT("Freeing stream. abstract=%p", net_stream->abstract);
1073 		if (pers) {
1074 			if (EG(active)) {
1075 				php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
1076 			} else {
1077 				/*
1078 				  otherwise we will crash because the EG(persistent_list) has been freed already,
1079 				  before the modules are shut down
1080 				*/
1081 				php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1082 			}
1083 		} else {
1084 			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE);
1085 		}
1086 		(void) net->data->m.set_stream(net, NULL);
1087 	}
1088 
1089 	DBG_VOID_RETURN;
1090 }
1091 /* }}} */
1092 
1093 
1094 /* {{{ mysqlnd_net::init */
1095 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,init)1096 MYSQLND_METHOD(mysqlnd_net, init)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1097 {
1098 	unsigned int buf_size;
1099 	DBG_ENTER("mysqlnd_net::init");
1100 
1101 	buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
1102 	net->data->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size);
1103 
1104 	buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
1105 	net->data->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size);
1106 
1107 	buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
1108 	net->data->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size);
1109 
1110 	DBG_RETURN(PASS);
1111 }
1112 /* }}} */
1113 
1114 
1115 /* {{{ mysqlnd_net::dtor */
1116 static void
MYSQLND_METHOD(mysqlnd_net,dtor)1117 MYSQLND_METHOD(mysqlnd_net, dtor)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
1118 {
1119 	DBG_ENTER("mysqlnd_net::dtor");
1120 	if (net) {
1121 		net->data->m.free_contents(net);
1122 		net->data->m.close_stream(net, stats, error_info);
1123 
1124 		if (net->cmd_buffer.buffer) {
1125 			DBG_INF("Freeing cmd buffer");
1126 			mnd_pefree(net->cmd_buffer.buffer, net->persistent);
1127 			net->cmd_buffer.buffer = NULL;
1128 		}
1129 
1130 		mnd_pefree(net->data, net->data->persistent);
1131 		mnd_pefree(net, net->persistent);
1132 	}
1133 	DBG_VOID_RETURN;
1134 }
1135 /* }}} */
1136 
1137 
1138 /* {{{ mysqlnd_net::get_stream */
1139 static php_stream *
MYSQLND_METHOD(mysqlnd_net,get_stream)1140 MYSQLND_METHOD(mysqlnd_net, get_stream)(const MYSQLND_NET * const net)
1141 {
1142 	DBG_ENTER("mysqlnd_net::get_stream");
1143 	DBG_INF_FMT("%p", net? net->data->stream:NULL);
1144 	DBG_RETURN(net? net->data->stream:NULL);
1145 }
1146 /* }}} */
1147 
1148 
1149 /* {{{ mysqlnd_net::set_stream */
1150 static php_stream *
MYSQLND_METHOD(mysqlnd_net,set_stream)1151 MYSQLND_METHOD(mysqlnd_net, set_stream)(MYSQLND_NET * const net, php_stream * net_stream)
1152 {
1153 	php_stream * ret = NULL;
1154 	DBG_ENTER("mysqlnd_net::set_stream");
1155 	if (net) {
1156 		net->data->stream = net_stream;
1157 		ret = net->data->stream;
1158 	}
1159 	DBG_RETURN(ret);
1160 }
1161 /* }}} */
1162 
1163 
1164 MYSQLND_CLASS_METHODS_START(mysqlnd_net)
1165 	MYSQLND_METHOD(mysqlnd_net, init),
1166 	MYSQLND_METHOD(mysqlnd_net, dtor),
1167 	MYSQLND_METHOD(mysqlnd_net, connect_ex),
1168 	MYSQLND_METHOD(mysqlnd_net, close_stream),
1169 	MYSQLND_METHOD(mysqlnd_net, open_pipe),
1170 	MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix),
1171 	MYSQLND_METHOD(mysqlnd_net, get_stream),
1172 	MYSQLND_METHOD(mysqlnd_net, set_stream),
1173 	MYSQLND_METHOD(mysqlnd_net, get_open_stream),
1174 	MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt),
1175 	MYSQLND_METHOD(mysqlnd_net, set_client_option),
1176 	MYSQLND_METHOD(mysqlnd_net, decode),
1177 	MYSQLND_METHOD(mysqlnd_net, encode),
1178 	MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
1179 	MYSQLND_METHOD(mysqlnd_net, free_contents),
1180 	MYSQLND_METHOD(mysqlnd_net, enable_ssl),
1181 	MYSQLND_METHOD(mysqlnd_net, disable_ssl),
1182 	MYSQLND_METHOD(mysqlnd_net, network_read_ex),
1183 	MYSQLND_METHOD(mysqlnd_net, network_write_ex),
1184 	MYSQLND_METHOD(mysqlnd_net, send_ex),
1185 	MYSQLND_METHOD(mysqlnd_net, receive_ex),
1186 #ifdef MYSQLND_COMPRESSION_ENABLED
1187 	MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer),
1188 #else
1189 	NULL,
1190 #endif
1191 	NULL, /* unused 1 */
1192 	NULL, /* unused 2 */
1193 	NULL, /* unused 3 */
1194 	NULL, /* unused 4 */
1195 	NULL  /* unused 5 */
1196 MYSQLND_CLASS_METHODS_END;
1197 
1198 
1199 /* {{{ mysqlnd_net_init */
1200 PHPAPI MYSQLND_NET *
mysqlnd_net_init(zend_bool persistent,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)1201 mysqlnd_net_init(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
1202 {
1203 	MYSQLND_NET * net;
1204 	DBG_ENTER("mysqlnd_net_init");
1205 	net = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_io_channel(persistent, stats, error_info);
1206 	DBG_RETURN(net);
1207 }
1208 /* }}} */
1209 
1210 
1211 /* {{{ mysqlnd_net_free */
1212 PHPAPI void
mysqlnd_net_free(MYSQLND_NET * const net,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)1213 mysqlnd_net_free(MYSQLND_NET * const net, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
1214 {
1215 	DBG_ENTER("mysqlnd_net_free");
1216 	if (net) {
1217 		net->data->m.dtor(net, stats, error_info);
1218 	}
1219 	DBG_VOID_RETURN;
1220 }
1221 /* }}} */
1222 
1223 
1224 
1225 /*
1226  * Local variables:
1227  * tab-width: 4
1228  * c-basic-offset: 4
1229  * End:
1230  * vim600: noet sw=4 ts=4 fdm=marker
1231  * vim<600: noet sw=4 ts=4
1232  */
1233