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