xref: /PHP-5.3/ext/mysqlnd/mysqlnd_net.c (revision bc11e6fd)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2013 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: Georg Richter <georg@mysql.com>                             |
16   |          Andrey Hristov <andrey@mysql.com>                           |
17   |          Ulf Wendel <uwendel@mysql.com>                              |
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 "ext/standard/sha1.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 TSRMLS_DC)43 mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC)
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_net::network_read */
63 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,network_read)64 MYSQLND_METHOD(mysqlnd_net, network_read)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
65 {
66 	enum_func_status return_value = PASS;
67 	size_t to_read = count, ret;
68 	size_t old_chunk_size = conn->net->stream->chunk_size;
69 	DBG_ENTER("mysqlnd_net::network_read");
70 	DBG_INF_FMT("count=%u", count);
71 	conn->net->stream->chunk_size = MIN(to_read, conn->net->options.net_read_buffer_size);
72 	while (to_read) {
73 		if (!(ret = php_stream_read(conn->net->stream, (char *) buffer, to_read))) {
74 			DBG_ERR_FMT("Error while reading header from socket");
75 			return_value = FAIL;
76 			break;
77 		}
78 		buffer += ret;
79 		to_read -= ret;
80 	}
81 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_BYTES_RECEIVED, count - to_read);
82 	conn->net->stream->chunk_size = old_chunk_size;
83 	DBG_RETURN(return_value);
84 }
85 /* }}} */
86 
87 
88 /* {{{ mysqlnd_net::network_write */
89 static size_t
MYSQLND_METHOD(mysqlnd_net,network_write)90 MYSQLND_METHOD(mysqlnd_net, network_write)(MYSQLND * const conn, const zend_uchar * const buf, size_t count TSRMLS_DC)
91 {
92 	size_t ret;
93 	DBG_ENTER("mysqlnd_net::network_write");
94 	ret = php_stream_write(conn->net->stream, (char *)buf, count);
95 	DBG_RETURN(ret);
96 }
97 /* }}} */
98 
99 
100 
101 /* {{{ mysqlnd_net::connect */
102 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,connect)103 MYSQLND_METHOD(mysqlnd_net, connect)(MYSQLND_NET * net, const char * const scheme, size_t scheme_len, zend_bool persistent, char **errstr, int * errcode TSRMLS_DC)
104 {
105 #if PHP_API_VERSION < 20100412
106 	unsigned int streams_options = ENFORCE_SAFE_MODE;
107 #else
108 	unsigned int streams_options = 0;
109 #endif
110 	unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
111 	char * hashed_details = NULL;
112 	int hashed_details_len = 0;
113 	struct timeval tv;
114 	DBG_ENTER("mysqlnd_net::connect");
115 
116 	if (persistent) {
117 		hashed_details_len = spprintf(&hashed_details, 0, "%p", net);
118 		DBG_INF_FMT("hashed_details=%s", hashed_details);
119 	}
120 
121 	net->packet_no = net->compressed_envelope_packet_no = 0;
122 
123 	if (net->stream) {
124 		/* close before opening a new one */
125 		DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
126 		if (net->persistent) {
127 			php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
128 		} else {
129 			php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
130 		}
131 		net->stream = NULL;
132 	}
133 
134 	if (net->options.timeout_connect) {
135 		tv.tv_sec = net->options.timeout_connect;
136 		tv.tv_usec = 0;
137 	}
138 
139 	DBG_INF_FMT("calling php_stream_xport_create");
140 	net->stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags,
141 										  hashed_details, (net->options.timeout_connect) ? &tv : NULL,
142 										  NULL /*ctx*/, errstr, errcode);
143 
144 	if (*errstr || !net->stream) {
145 		if (hashed_details) {
146 			efree(hashed_details); /* allocated by spprintf */
147 		}
148 		*errcode = CR_CONNECTION_ERROR;
149 		DBG_RETURN(FAIL);
150 	}
151 
152 	if (hashed_details) {
153 		/*
154 		  If persistent, the streams register it in EG(persistent_list).
155 		  This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
156 		  whatever they have to.
157 		*/
158 		zend_rsrc_list_entry *le;
159 
160 		if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1, (void*) &le) == SUCCESS) {
161 			/*
162 			  in_free will let streams code skip destructing - big HACK,
163 			  but STREAMS suck big time regarding persistent streams.
164 			  Just not compatible for extensions that need persistency.
165 			*/
166 			net->stream->in_free = 1;
167 			zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1);
168 			net->stream->in_free = 0;
169 		}
170 #if ZEND_DEBUG
171 		/* Shut-up the streams, they don't know what they are doing */
172 		net->stream->__exposed = 1;
173 #endif
174 		efree(hashed_details);
175 	}
176 	/*
177 	  Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
178 	  be registered as resource (in EG(regular_list). So far, so good. However, it won't be
179 	  unregistered till the script ends. So, we need to take care of that.
180 	*/
181 	net->stream->in_free = 1;
182 	zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id);
183 	net->stream->in_free = 0;
184 
185 	if (!net->options.timeout_read) {
186 		/* should always happen because read_timeout cannot be set via API */
187 		net->options.timeout_read = (unsigned int) MYSQLND_G(net_read_timeout);
188 	}
189 	if (net->options.timeout_read) {
190 		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
191 		tv.tv_sec = net->options.timeout_read;
192 		tv.tv_usec = 0;
193 		php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
194 	}
195 
196 	if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) {
197 		/* TCP -> Set TCP_NODELAY */
198 		mysqlnd_set_sock_no_delay(net->stream TSRMLS_CC);
199 	}
200 
201 	{
202 		unsigned int buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
203 		net->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size TSRMLS_CC);
204 	}
205 
206 
207 	DBG_RETURN(PASS);
208 }
209 /* }}} */
210 
211 
212 /* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
213 #define COPY_HEADER(T,A)  do { \
214 		*(((char *)(T)))   = *(((char *)(A)));\
215 		*(((char *)(T))+1) = *(((char *)(A))+1);\
216 		*(((char *)(T))+2) = *(((char *)(A))+2);\
217 		*(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
218 #define STORE_HEADER_SIZE(safe_storage, buffer)  COPY_HEADER((safe_storage), (buffer))
219 #define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
220 
221 /* {{{ mysqlnd_net::send */
222 /*
223   IMPORTANT : It's expected that buf has place in the beginning for MYSQLND_HEADER_SIZE !!!!
224 			  This is done for performance reasons in the caller of this function.
225 			  Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
226 			  Neither are quick, thus the clients of this function are obligated to do
227 			  what they are asked for.
228 
229   `count` is actually the length of the payload data. Thus :
230   count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer)
231 */
232 size_t
MYSQLND_METHOD(mysqlnd_net,send)233 MYSQLND_METHOD(mysqlnd_net, send)(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC)
234 {
235 	zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
236 	zend_uchar *safe_storage = safe_buf;
237 	MYSQLND_NET *net = conn->net;
238 	size_t old_chunk_size = net->stream->chunk_size;
239 	size_t ret, packets_sent = 1;
240 	size_t left = count;
241 	zend_uchar *p = (zend_uchar *) buf;
242 	zend_uchar * compress_buf = NULL;
243 	size_t to_be_sent;
244 
245 	DBG_ENTER("mysqlnd_net::send");
246 	DBG_INF_FMT("conn=%llu count=%lu compression=%u", conn->thread_id, count, net->compressed);
247 
248 	net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE;
249 
250 	if (net->compressed == TRUE) {
251 		size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
252 		DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size);
253 		compress_buf = mnd_emalloc(comp_buf_size);
254 	}
255 
256 	do {
257 		to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
258 #ifdef MYSQLND_COMPRESSION_ENABLED
259 		if (net->compressed == TRUE) {
260 			/* here we need to compress the data and then write it, first comes the compressed header */
261 			size_t tmp_complen = to_be_sent;
262 			size_t payload_size;
263 			zend_uchar * uncompressed_payload = p; /* should include the header */
264 
265 			STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
266 			int3store(uncompressed_payload, to_be_sent);
267 			int1store(uncompressed_payload + 3, net->packet_no);
268 			if (PASS == net->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
269 									   uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC))
270 			{
271 				int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
272 				payload_size = tmp_complen;
273 			} else {
274 				int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
275 				memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
276 				payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
277 			}
278 			RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
279 
280 			int3store(compress_buf, payload_size);
281 			int1store(compress_buf + 3, net->packet_no);
282 			DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
283 			ret = conn->net->m.network_write(conn, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE TSRMLS_CC);
284 			net->compressed_envelope_packet_no++;
285   #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
286 			if (res == Z_OK) {
287 				size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
288 				zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
289 				int error = net->m.decode(decompressed_data, decompressed_size,
290 										  compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
291 				if (error == Z_OK) {
292 					int i;
293 					DBG_INF("success decompressing");
294 					for (i = 0 ; i < decompressed_size; i++) {
295 						if (i && (i % 30 == 0)) {
296 							printf("\n\t\t");
297 						}
298 						printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
299 						DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
300 					}
301 				} else {
302 					DBG_INF("error decompressing");
303 				}
304 				mnd_free(decompressed_data);
305 			}
306   #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
307 		} else
308 #endif /* MYSQLND_COMPRESSION_ENABLED */
309 		{
310 			DBG_INF("no compression");
311 			STORE_HEADER_SIZE(safe_storage, p);
312 			int3store(p, to_be_sent);
313 			int1store(p + 3, net->packet_no);
314 			ret = conn->net->m.network_write(conn, p, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC);
315 			RESTORE_HEADER_SIZE(p, safe_storage);
316 			net->compressed_envelope_packet_no++;
317 		}
318 		net->packet_no++;
319 
320 		p += to_be_sent;
321 		left -= to_be_sent;
322 		packets_sent++;
323 		/*
324 		  if left is 0 then there is nothing more to send, but if the last packet was exactly
325 		  with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
326 		  empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
327 		  indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
328 		  packet will be sent and this loop will end.
329 		*/
330 	} while (ret && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
331 
332 	DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no);
333 	/* Even for zero size payload we have to send a packet */
334 	if (!ret) {
335 		DBG_ERR_FMT("Can't %u send bytes", count);
336 		conn->state = CONN_QUIT_SENT;
337 		SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
338 	}
339 
340 	MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn->stats,
341 			STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
342 			STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
343 			STAT_PACKETS_SENT, packets_sent);
344 
345 	net->stream->chunk_size = old_chunk_size;
346 	if (compress_buf) {
347 		mnd_efree(compress_buf);
348 	}
349 	DBG_RETURN(ret);
350 }
351 /* }}} */
352 
353 
354 #ifdef MYSQLND_COMPRESSION_ENABLED
355 /* {{{ php_mysqlnd_read_buffer_is_empty */
356 static zend_bool
php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)357 php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)
358 {
359 	return buffer->len? FALSE:TRUE;
360 }
361 /* }}} */
362 
363 
364 /* {{{ php_mysqlnd_read_buffer_read */
365 static void
php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer,size_t count,zend_uchar * dest)366 php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest)
367 {
368 	if (buffer->len >= count) {
369 		memcpy(dest, buffer->data + buffer->offset, count);
370 		buffer->offset += count;
371 		buffer->len -= count;
372 	}
373 }
374 /* }}} */
375 
376 
377 /* {{{ php_mysqlnd_read_buffer_bytes_left */
378 static size_t
php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)379 php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)
380 {
381 	return buffer->len;
382 }
383 /* }}} */
384 
385 
386 /* {{{ php_mysqlnd_read_buffer_free */
387 static void
php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)388 php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)
389 {
390 	DBG_ENTER("php_mysqlnd_read_buffer_free");
391 	if (*buffer) {
392 		mnd_efree((*buffer)->data);
393 		mnd_efree(*buffer);
394 		*buffer = NULL;
395 	}
396 	DBG_VOID_RETURN;
397 }
398 /* }}} */
399 
400 
401 /* {{{ php_mysqlnd_create_read_buffer */
402 static MYSQLND_READ_BUFFER *
mysqlnd_create_read_buffer(size_t count TSRMLS_DC)403 mysqlnd_create_read_buffer(size_t count TSRMLS_DC)
404 {
405 	MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER));
406 	DBG_ENTER("mysqlnd_create_read_buffer");
407 	ret->is_empty = php_mysqlnd_read_buffer_is_empty;
408 	ret->read = php_mysqlnd_read_buffer_read;
409 	ret->bytes_left = php_mysqlnd_read_buffer_bytes_left;
410 	ret->free_buffer = php_mysqlnd_read_buffer_free;
411 	ret->data = mnd_emalloc(count);
412 	ret->size = ret->len = count;
413 	ret->offset = 0;
414 	DBG_RETURN(ret);
415 }
416 /* }}} */
417 
418 
419 /* {{{ mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer */
420 static enum_func_status
mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(MYSQLND * conn,size_t net_payload_size TSRMLS_DC)421 mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(MYSQLND * conn, size_t net_payload_size TSRMLS_DC)
422 {
423 	MYSQLND_NET * net = conn->net;
424 	size_t decompressed_size;
425 	enum_func_status ret = PASS;
426 	zend_uchar * compressed_data = NULL;
427 	zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
428 	DBG_ENTER("mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer");
429 
430 	/* Read the compressed header */
431 	if (FAIL == conn->net->m.network_read(conn, comp_header, COMPRESSED_HEADER_SIZE TSRMLS_CC)) {
432 		DBG_RETURN(FAIL);
433 	}
434 	decompressed_size = uint3korr(comp_header);
435 
436 	/* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
437 	/* we need to decompress the data */
438 
439 	if (decompressed_size) {
440 		compressed_data = mnd_emalloc(net_payload_size);
441 		if (FAIL == conn->net->m.network_read(conn, compressed_data, net_payload_size TSRMLS_CC)) {
442 			ret = FAIL;
443 			goto end;
444 		}
445 		net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size TSRMLS_CC);
446 		ret = net->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size TSRMLS_CC);
447 		if (ret == FAIL) {
448 			goto end;
449 		}
450 	} else {
451 		DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size);
452 		net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size TSRMLS_CC);
453 		if (FAIL == conn->net->m.network_read(conn, net->uncompressed_data->data, net_payload_size TSRMLS_CC)) {
454 			ret = FAIL;
455 			goto end;
456 		}
457 	}
458 end:
459 	if (compressed_data) {
460 		mnd_efree(compressed_data);
461 	}
462 	DBG_RETURN(ret);
463 }
464 /* }}} */
465 #endif /* MYSQLND_COMPRESSION_ENABLED */
466 
467 
468 /* {{{ mysqlnd_net::decode */
469 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,decode)470 MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, size_t uncompressed_data_len,
471 									const zend_uchar * const compressed_data, size_t compressed_data_len TSRMLS_DC)
472 {
473 #ifdef MYSQLND_COMPRESSION_ENABLED
474 	int error;
475 	uLongf tmp_complen = uncompressed_data_len;
476 	DBG_ENTER("mysqlnd_net::decode");
477 	error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
478 
479 	DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len);
480 	if (error != Z_OK) {
481 		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);
482 	}
483 	DBG_RETURN(error == Z_OK? PASS:FAIL);
484 #else
485 	DBG_ENTER("mysqlnd_net::decode");
486 	DBG_RETURN(FAIL);
487 #endif
488 }
489 /* }}} */
490 
491 
492 /* {{{ mysqlnd_net::encode */
493 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,encode)494 MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
495 									const zend_uchar * const uncompressed_data, size_t uncompressed_data_len TSRMLS_DC)
496 {
497 #ifdef MYSQLND_COMPRESSION_ENABLED
498 	int error;
499 	uLongf tmp_complen = *compress_buffer_len;
500 	DBG_ENTER("mysqlnd_net::encode");
501 	error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
502 
503 	if (error != Z_OK) {
504 		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);
505 	} else {
506 		*compress_buffer_len = tmp_complen;
507 		DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
508 	}
509 
510 	DBG_RETURN(error == Z_OK? PASS:FAIL);
511 #else
512 	DBG_ENTER("mysqlnd_net::encode");
513 	DBG_RETURN(FAIL);
514 #endif
515 }
516 /* }}} */
517 
518 
519 /* {{{ mysqlnd_net::receive */
520 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,receive)521 MYSQLND_METHOD(mysqlnd_net, receive)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
522 {
523 	size_t to_read = count;
524 	zend_uchar * p = buffer;
525 	MYSQLND_NET * net = conn->net;
526 
527 	DBG_ENTER("mysqlnd_net::receive");
528 #ifdef MYSQLND_COMPRESSION_ENABLED
529 	if (net->compressed) {
530 		if (net->uncompressed_data) {
531 			size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read);
532 			DBG_INF_FMT("reading %u from uncompressed_data buffer", to_read_from_buffer);
533 			if (to_read_from_buffer) {
534 				net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
535 				p += to_read_from_buffer;
536 				to_read -= to_read_from_buffer;
537 			}
538 			DBG_INF_FMT("left %u to read", to_read);
539 			if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) {
540 				/* Everything was consumed. This should never happen here, but for security */
541 				net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
542 			}
543 		}
544 		if (to_read) {
545 			zend_uchar net_header[MYSQLND_HEADER_SIZE];
546 			size_t net_payload_size;
547 			zend_uchar packet_no;
548 
549 			if (FAIL == net->m.network_read(conn, net_header, MYSQLND_HEADER_SIZE TSRMLS_CC)) {
550 				DBG_RETURN(FAIL);
551 			}
552 			net_payload_size = uint3korr(net_header);
553 			packet_no = uint1korr(net_header + 3);
554 			if (net->compressed_envelope_packet_no != packet_no) {
555 				DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
556 							net->compressed_envelope_packet_no, packet_no, net_payload_size);
557 
558 				php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
559 						  net->compressed_envelope_packet_no, packet_no, net_payload_size);
560 				DBG_RETURN(FAIL);
561 			}
562 			net->compressed_envelope_packet_no++;
563 #ifdef MYSQLND_DUMP_HEADER_N_BODY
564 			DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (unsigned long) net_payload_size);
565 #endif
566 			/* Now let's read from the wire, decompress it and fill the read buffer */
567 			mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(conn, net_payload_size TSRMLS_CC);
568 
569 			/*
570 			  Now a bit of recursion - read from the read buffer,
571 			  if the data which we have just read from the wire
572 			  is not enough, then the recursive call will try to
573 			  satisfy it until it is satisfied.
574 			*/
575 			DBG_RETURN(net->m.receive(conn, p, to_read TSRMLS_CC));
576 		}
577 		DBG_RETURN(PASS);
578 	}
579 #endif /* MYSQLND_COMPRESSION_ENABLED */
580 	DBG_RETURN(net->m.network_read(conn, p, to_read TSRMLS_CC));
581 }
582 /* }}} */
583 
584 
585 /* {{{ mysqlnd_net::set_client_option */
586 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,set_client_option)587 MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value TSRMLS_DC)
588 {
589 	DBG_ENTER("mysqlnd_net::set_client_option");
590 	DBG_INF_FMT("option=%u", option);
591 	switch (option) {
592 		case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
593 			DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
594 			if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
595 				DBG_RETURN(FAIL);
596 			}
597 			net->cmd_buffer.length = *(unsigned int*) value;
598 			DBG_INF_FMT("new_length=%u", net->cmd_buffer.length);
599 			if (!net->cmd_buffer.buffer) {
600 				net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent);
601 			} else {
602 				net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent);
603 			}
604 			break;
605 		case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
606 			DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
607 			net->options.net_read_buffer_size = *(unsigned int*) value;
608 			DBG_INF_FMT("new_length=%u", net->options.net_read_buffer_size);
609 			break;
610 		case MYSQL_OPT_CONNECT_TIMEOUT:
611 			DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
612 			net->options.timeout_connect = *(unsigned int*) value;
613 			break;
614 		case MYSQLND_OPT_SSL_KEY:
615 			{
616 				zend_bool pers = net->persistent;
617 				if (net->options.ssl_key) {
618 					mnd_pefree(net->options.ssl_key, pers);
619 				}
620 				net->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
621 				break;
622 			}
623 		case MYSQLND_OPT_SSL_CERT:
624 			{
625 				zend_bool pers = net->persistent;
626 				if (net->options.ssl_cert) {
627 					mnd_pefree(net->options.ssl_cert, pers);
628 				}
629 				net->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
630 				break;
631 			}
632 		case MYSQLND_OPT_SSL_CA:
633 			{
634 				zend_bool pers = net->persistent;
635 				if (net->options.ssl_ca) {
636 					mnd_pefree(net->options.ssl_ca, pers);
637 				}
638 				net->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
639 				break;
640 			}
641 		case MYSQLND_OPT_SSL_CAPATH:
642 			{
643 				zend_bool pers = net->persistent;
644 				if (net->options.ssl_capath) {
645 					mnd_pefree(net->options.ssl_capath, pers);
646 				}
647 				net->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
648 				break;
649 			}
650 		case MYSQLND_OPT_SSL_CIPHER:
651 			{
652 				zend_bool pers = net->persistent;
653 				if (net->options.ssl_cipher) {
654 					mnd_pefree(net->options.ssl_cipher, pers);
655 				}
656 				net->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
657 				break;
658 			}
659 		case MYSQLND_OPT_SSL_PASSPHRASE:
660 			{
661 				zend_bool pers = net->persistent;
662 				if (net->options.ssl_passphrase) {
663 					mnd_pefree(net->options.ssl_passphrase, pers);
664 				}
665 				net->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
666 				break;
667 			}
668 		case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
669 			net->options.ssl_verify_peer = value? ((*(zend_bool *)value)? TRUE:FALSE): FALSE;
670 			break;
671 #ifdef WHEN_SUPPORTED_BY_MYSQLI
672 		case MYSQL_OPT_READ_TIMEOUT:
673 			DBG_INF("MYSQL_OPT_READ_TIMEOUT");
674 			net->options.timeout_read = *(unsigned int*) value;
675 			break;
676 		case MYSQL_OPT_WRITE_TIMEOUT:
677 			DBG_INF("MYSQL_OPT_WRITE_TIMEOUT");
678 			net->options.timeout_write = *(unsigned int*) value;
679 			break;
680 #endif
681 		case MYSQL_OPT_COMPRESS:
682 			net->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION;
683 			break;
684 		default:
685 			DBG_RETURN(FAIL);
686 	}
687 	DBG_RETURN(PASS);
688 }
689 /* }}} */
690 
691 /* {{{ mysqlnd_net::consume_uneaten_data */
692 size_t
MYSQLND_METHOD(mysqlnd_net,consume_uneaten_data)693 MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC)
694 {
695 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
696 	/*
697 	  Switch to non-blocking mode and try to consume something from
698 	  the line, if possible, then continue. This saves us from looking for
699 	  the actuall place where out-of-order packets have been sent.
700 	  If someone is completely sure that everything is fine, he can switch it
701 	  off.
702 	*/
703 	char tmp_buf[256];
704 	size_t skipped_bytes = 0;
705 	int opt = PHP_STREAM_OPTION_BLOCKING;
706 	int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC);
707 
708 	DBG_ENTER("mysqlnd_net::consume_uneaten_data");
709 
710 	if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
711 		/* Do a read of 1 byte */
712 		int bytes_consumed;
713 
714 		do {
715 			skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf)));
716 		} while (bytes_consumed == sizeof(tmp_buf));
717 
718 		if (was_blocked) {
719 			net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC);
720 		}
721 
722 		if (bytes_consumed) {
723 			DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server",
724 						bytes_consumed, mysqlnd_command_to_text[net->last_command]);
725 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
726 							 "consumed all the output from the server",
727 							 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
728 		}
729 	}
730 	net->last_command = cmd;
731 
732 	DBG_RETURN(skipped_bytes);
733 #else
734 	return 0;
735 #endif
736 }
737 /* }}} */
738 
739 /*
740   in libmyusql, if cert and !key then key=cert
741 */
742 /* {{{ mysqlnd_net::enable_ssl */
743 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,enable_ssl)744 MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
745 {
746 #ifdef MYSQLND_SSL_SUPPORTED
747 	php_stream_context *context = php_stream_context_alloc();
748 	DBG_ENTER("mysqlnd_net::enable_ssl");
749 	if (!context) {
750 		DBG_RETURN(FAIL);
751 	}
752 
753 	if (net->options.ssl_key) {
754 		zval key_zval;
755 		ZVAL_STRING(&key_zval, net->options.ssl_key, 0);
756 		DBG_INF("key");
757 		php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
758 	}
759 	if (net->options.ssl_verify_peer) {
760 		zval verify_peer_zval;
761 		ZVAL_TRUE(&verify_peer_zval);
762 		DBG_INF("verify peer");
763 		php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
764 	}
765 	if (net->options.ssl_cert) {
766 		zval cert_zval;
767 		ZVAL_STRING(&cert_zval, net->options.ssl_cert, 0);
768 		DBG_INF_FMT("local_cert=%s", net->options.ssl_cert);
769 		php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
770 		if (!net->options.ssl_key) {
771 			php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
772 		}
773 	}
774 	if (net->options.ssl_ca) {
775 		zval cafile_zval;
776 		ZVAL_STRING(&cafile_zval, net->options.ssl_ca, 0);
777 		DBG_INF_FMT("cafile=%s", net->options.ssl_ca);
778 		php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
779 	}
780 	if (net->options.ssl_capath) {
781 		zval capath_zval;
782 		ZVAL_STRING(&capath_zval, net->options.ssl_capath, 0);
783 		DBG_INF_FMT("capath=%s", net->options.ssl_capath);
784 		php_stream_context_set_option(context, "ssl", "cafile", &capath_zval);
785 	}
786 	if (net->options.ssl_passphrase) {
787 		zval passphrase_zval;
788 		ZVAL_STRING(&passphrase_zval, net->options.ssl_passphrase, 0);
789 		php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
790 	}
791 	if (net->options.ssl_cipher) {
792 		zval cipher_zval;
793 		ZVAL_STRING(&cipher_zval, net->options.ssl_cipher, 0);
794 		DBG_INF_FMT("ciphers=%s", net->options.ssl_cipher);
795 		php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
796 	}
797 	php_stream_context_set(net->stream, context);
798 	if (php_stream_xport_crypto_setup(net->stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL TSRMLS_CC) < 0 ||
799 	    php_stream_xport_crypto_enable(net->stream, 1 TSRMLS_CC) < 0)
800 	{
801 		DBG_ERR("Cannot connect to MySQL by using SSL");
802 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot connect to MySQL by using SSL");
803 		DBG_RETURN(FAIL);
804 	}
805 	/*
806 	  get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
807 	  then the context would not survive cleaning of EG(regular_list), where it is registered, as a
808 	  resource. What happens is that after this destruction any use of the network will mean usage
809 	  of the context, which means usage of already freed memory, bad. Actually we don't need this
810 	  context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
811 	*/
812 	php_stream_context_set(net->stream, NULL);
813 
814 	if (net->options.timeout_read) {
815 		struct timeval tv;
816 		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
817 		tv.tv_sec = net->options.timeout_read;
818 		tv.tv_usec = 0;
819 		php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
820 	}
821 
822 	DBG_RETURN(PASS);
823 #else
824 	DBG_ENTER("mysqlnd_net::enable_ssl");
825 	DBG_RETURN(PASS);
826 #endif
827 }
828 /* }}} */
829 
830 
831 /* {{{ mysqlnd_net::disable_ssl */
832 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,disable_ssl)833 MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
834 {
835 	DBG_ENTER("mysqlnd_net::disable_ssl");
836 	DBG_RETURN(PASS);
837 }
838 /* }}} */
839 
840 
841 /* {{{ mysqlnd_net::set_client_option */
842 static void
MYSQLND_METHOD(mysqlnd_net,free_contents)843 MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net TSRMLS_DC)
844 {
845 	zend_bool pers = net->persistent;
846 	DBG_ENTER("mysqlnd_net::free_contents");
847 
848 #ifdef MYSQLND_COMPRESSION_ENABLED
849 	if (net->uncompressed_data) {
850 		net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
851 	}
852 #endif
853 	if (net->options.ssl_key) {
854 		mnd_pefree(net->options.ssl_key, pers);
855 		net->options.ssl_key = NULL;
856 	}
857 	if (net->options.ssl_cert) {
858 		mnd_pefree(net->options.ssl_cert, pers);
859 		net->options.ssl_cert = NULL;
860 	}
861 	if (net->options.ssl_ca) {
862 		mnd_pefree(net->options.ssl_ca, pers);
863 		net->options.ssl_ca = NULL;
864 	}
865 	if (net->options.ssl_capath) {
866 		mnd_pefree(net->options.ssl_capath, pers);
867 		net->options.ssl_capath = NULL;
868 	}
869 	if (net->options.ssl_cipher) {
870 		mnd_pefree(net->options.ssl_cipher, pers);
871 		net->options.ssl_cipher = NULL;
872 	}
873 
874 	DBG_VOID_RETURN;
875 }
876 /* }}} */
877 
878 static
879 MYSQLND_CLASS_METHODS_START(mysqlnd_net)
880 	MYSQLND_METHOD(mysqlnd_net, connect),
881 	MYSQLND_METHOD(mysqlnd_net, send),
882 	MYSQLND_METHOD(mysqlnd_net, receive),
883 	MYSQLND_METHOD(mysqlnd_net, set_client_option),
884 	MYSQLND_METHOD(mysqlnd_net, network_read),
885 	MYSQLND_METHOD(mysqlnd_net, network_write),
886 	MYSQLND_METHOD(mysqlnd_net, decode),
887 	MYSQLND_METHOD(mysqlnd_net, encode),
888 	MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
889 	MYSQLND_METHOD(mysqlnd_net, free_contents),
890 	MYSQLND_METHOD(mysqlnd_net, enable_ssl),
891 	MYSQLND_METHOD(mysqlnd_net, disable_ssl)
892 MYSQLND_CLASS_METHODS_END;
893 
894 
895 /* {{{ mysqlnd_net_init */
896 PHPAPI MYSQLND_NET *
mysqlnd_net_init(zend_bool persistent TSRMLS_DC)897 mysqlnd_net_init(zend_bool persistent TSRMLS_DC)
898 {
899 	size_t alloc_size = sizeof(MYSQLND_NET) + mysqlnd_plugin_count() * sizeof(void *);
900 	MYSQLND_NET * net = mnd_pecalloc(1, alloc_size, persistent);
901 
902 	DBG_ENTER("mysqlnd_net_init");
903 	DBG_INF_FMT("persistent=%u", persistent);
904 	if (net) {
905 		net->persistent = persistent;
906 		net->m = mysqlnd_mysqlnd_net_methods;
907 
908 		{
909 			unsigned int buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
910 			net->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size TSRMLS_CC);
911 		}
912 	}
913 	DBG_RETURN(net);
914 }
915 /* }}} */
916 
917 
918 /* {{{ mysqlnd_net_free */
919 PHPAPI void
mysqlnd_net_free(MYSQLND_NET * const net TSRMLS_DC)920 mysqlnd_net_free(MYSQLND_NET * const net TSRMLS_DC)
921 {
922 	DBG_ENTER("mysqlnd_net_free");
923 
924 	if (net) {
925 		zend_bool pers = net->persistent;
926 
927 		net->m.free_contents(net TSRMLS_CC);
928 		if (net->cmd_buffer.buffer) {
929 			DBG_INF("Freeing cmd buffer");
930 			mnd_pefree(net->cmd_buffer.buffer, pers);
931 			net->cmd_buffer.buffer = NULL;
932 		}
933 		if (net->stream) {
934 			DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
935 			if (pers) {
936 				php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
937 			} else {
938 				php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
939 			}
940 			net->stream = NULL;
941 		}
942 		mnd_pefree(net, pers);
943 	}
944 	DBG_VOID_RETURN;
945 }
946 /* }}} */
947 
948 
949 /* {{{ _mysqlnd_plugin_get_plugin_net_data */
_mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net,unsigned int plugin_id TSRMLS_DC)950 PHPAPI void ** _mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net, unsigned int plugin_id TSRMLS_DC)
951 {
952 	DBG_ENTER("_mysqlnd_plugin_get_plugin_net_data");
953 	DBG_INF_FMT("plugin_id=%u", plugin_id);
954 	if (!net || plugin_id >= mysqlnd_plugin_count()) {
955 		return NULL;
956 	}
957 	DBG_RETURN((void *)((char *)net + sizeof(MYSQLND_NET) + plugin_id * sizeof(void *)));
958 }
959 /* }}} */
960 
961 
962 /* {{{ mysqlnd_net_get_methods */
963 PHPAPI struct st_mysqlnd_net_methods *
mysqlnd_net_get_methods()964 mysqlnd_net_get_methods()
965 {
966 	return &mysqlnd_mysqlnd_net_methods;
967 }
968 /* }}} */
969 
970 
971 /*
972  * Local variables:
973  * tab-width: 4
974  * c-basic-offset: 4
975  * End:
976  * vim600: noet sw=4 ts=4 fdm=marker
977  * vim<600: noet sw=4 ts=4
978  */
979