1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2014 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$ */
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 "zend_ini.h"
30 
31 #define MYSQLND_SILENT 1
32 
33 #define MYSQLND_DUMP_HEADER_N_BODY
34 
35 #define	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_size, packet_type_as_text, packet_type) \
36 	{ \
37 		DBG_INF_FMT("buf=%p size=%u", (buf), (buf_size)); \
38 		if (FAIL == mysqlnd_read_header((conn)->net, &((packet)->header), (conn)->stats, ((conn)->error_info) TSRMLS_CC)) {\
39 			CONN_SET_STATE(conn, CONN_QUIT_SENT); \
40 			SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\
41 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \
42 			DBG_ERR_FMT("Can't read %s's header", (packet_type_as_text)); \
43 			DBG_RETURN(FAIL);\
44 		}\
45 		if ((buf_size) < (packet)->header.size) { \
46 			DBG_ERR_FMT("Packet buffer %u wasn't big enough %u, %u bytes will be unread", \
47 						(buf_size), (packet)->header.size, (packet)->header.size - (buf_size)); \
48 						DBG_RETURN(FAIL); \
49 		}\
50 		if (FAIL == conn->net->m.receive_ex((conn)->net, (buf), (packet)->header.size, (conn)->stats, ((conn)->error_info) TSRMLS_CC)) { \
51 			CONN_SET_STATE(conn, CONN_QUIT_SENT); \
52 			SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\
53 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \
54 			DBG_ERR_FMT("Empty '%s' packet body", (packet_type_as_text)); \
55 			DBG_RETURN(FAIL);\
56 		} \
57 		MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, packet_type_to_statistic_byte_count[packet_type], \
58 											MYSQLND_HEADER_SIZE + (packet)->header.size, \
59 											packet_type_to_statistic_packet_count[packet_type], \
60 											1); \
61 	}
62 
63 
64 #define BAIL_IF_NO_MORE_DATA \
65 	if ((size_t)(p - begin) > packet->header.size) { \
66 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Premature end of data (mysqlnd_wireprotocol.c:%u)", __LINE__); \
67 		goto premature_end; \
68 	} \
69 
70 
71 static const char *unknown_sqlstate= "HY000";
72 
73 const char * const mysqlnd_empty_string = "";
74 
75 /* Used in mysqlnd_debug.c */
76 const char mysqlnd_read_header_name[]	= "mysqlnd_read_header";
77 const char mysqlnd_read_body_name[]		= "mysqlnd_read_body";
78 
79 
80 #define ERROR_MARKER 0xFF
81 #define EODATA_MARKER 0xFE
82 
83 /* {{{ mysqlnd_command_to_text
84  */
85 const char * const mysqlnd_command_to_text[COM_END] =
86 {
87   "SLEEP", "QUIT", "INIT_DB", "QUERY", "FIELD_LIST",
88   "CREATE_DB", "DROP_DB", "REFRESH", "SHUTDOWN", "STATISTICS",
89   "PROCESS_INFO", "CONNECT", "PROCESS_KILL", "DEBUG", "PING",
90   "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP",
91   "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE",
92   "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE",
93   "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON", "BINLOG_DUMP_GTID",
94   "RESET_CONNECTION"
95 };
96 /* }}} */
97 
98 
99 
100 static enum_mysqlnd_collected_stats packet_type_to_statistic_byte_count[PROT_LAST] =
101 {
102 	STAT_LAST,
103 	STAT_LAST,
104 	STAT_BYTES_RECEIVED_OK,
105 	STAT_BYTES_RECEIVED_EOF,
106 	STAT_LAST,
107 	STAT_BYTES_RECEIVED_RSET_HEADER,
108 	STAT_BYTES_RECEIVED_RSET_FIELD_META,
109 	STAT_BYTES_RECEIVED_RSET_ROW,
110 	STAT_BYTES_RECEIVED_PREPARE_RESPONSE,
111 	STAT_BYTES_RECEIVED_CHANGE_USER,
112 };
113 
114 static enum_mysqlnd_collected_stats packet_type_to_statistic_packet_count[PROT_LAST] =
115 {
116 	STAT_LAST,
117 	STAT_LAST,
118 	STAT_PACKETS_RECEIVED_OK,
119 	STAT_PACKETS_RECEIVED_EOF,
120 	STAT_LAST,
121 	STAT_PACKETS_RECEIVED_RSET_HEADER,
122 	STAT_PACKETS_RECEIVED_RSET_FIELD_META,
123 	STAT_PACKETS_RECEIVED_RSET_ROW,
124 	STAT_PACKETS_RECEIVED_PREPARE_RESPONSE,
125 	STAT_PACKETS_RECEIVED_CHANGE_USER,
126 };
127 
128 
129 /* {{{ php_mysqlnd_net_field_length
130    Get next field's length */
131 unsigned long
php_mysqlnd_net_field_length(zend_uchar ** packet)132 php_mysqlnd_net_field_length(zend_uchar **packet)
133 {
134 	register zend_uchar *p= (zend_uchar *)*packet;
135 
136 	if (*p < 251) {
137 		(*packet)++;
138 		return (unsigned long) *p;
139 	}
140 
141 	switch (*p) {
142 		case 251:
143 			(*packet)++;
144 			return MYSQLND_NULL_LENGTH;
145 		case 252:
146 			(*packet) += 3;
147 			return (unsigned long) uint2korr(p+1);
148 		case 253:
149 			(*packet) += 4;
150 			return (unsigned long) uint3korr(p+1);
151 		default:
152 			(*packet) += 9;
153 			return (unsigned long) uint4korr(p+1);
154 	}
155 }
156 /* }}} */
157 
158 
159 /* {{{ php_mysqlnd_net_field_length_ll
160    Get next field's length */
161 uint64_t
php_mysqlnd_net_field_length_ll(zend_uchar ** packet)162 php_mysqlnd_net_field_length_ll(zend_uchar **packet)
163 {
164 	register zend_uchar *p= (zend_uchar *)*packet;
165 
166 	if (*p < 251) {
167 		(*packet)++;
168 		return (uint64_t) *p;
169 	}
170 
171 	switch (*p) {
172 		case 251:
173 			(*packet)++;
174 			return (uint64_t) MYSQLND_NULL_LENGTH;
175 		case 252:
176 			(*packet) += 3;
177 			return (uint64_t) uint2korr(p + 1);
178 		case 253:
179 			(*packet) += 4;
180 			return (uint64_t) uint3korr(p + 1);
181 		default:
182 			(*packet) += 9;
183 			return (uint64_t) uint8korr(p + 1);
184 	}
185 }
186 /* }}} */
187 
188 
189 /* {{{ php_mysqlnd_net_store_length */
190 zend_uchar *
php_mysqlnd_net_store_length(zend_uchar * packet,uint64_t length)191 php_mysqlnd_net_store_length(zend_uchar *packet, uint64_t length)
192 {
193 	if (length < (uint64_t) L64(251)) {
194 		*packet = (zend_uchar) length;
195 		return packet + 1;
196 	}
197 
198 	if (length < (uint64_t) L64(65536)) {
199 		*packet++ = 252;
200 		int2store(packet,(unsigned int) length);
201 		return packet + 2;
202 	}
203 
204 	if (length < (uint64_t) L64(16777216)) {
205 		*packet++ = 253;
206 		int3store(packet,(ulong) length);
207 		return packet + 3;
208 	}
209 	*packet++ = 254;
210 	int8store(packet, length);
211 	return packet + 8;
212 }
213 /* }}} */
214 
215 
216 /* {{{ php_mysqlnd_read_error_from_line */
217 static enum_func_status
php_mysqlnd_read_error_from_line(zend_uchar * buf,size_t buf_len,char * error,int error_buf_len,unsigned int * error_no,char * sqlstate TSRMLS_DC)218 php_mysqlnd_read_error_from_line(zend_uchar *buf, size_t buf_len,
219 								char *error, int error_buf_len,
220 								unsigned int *error_no, char *sqlstate TSRMLS_DC)
221 {
222 	zend_uchar *p = buf;
223 	int error_msg_len= 0;
224 
225 	DBG_ENTER("php_mysqlnd_read_error_from_line");
226 
227 	*error_no = CR_UNKNOWN_ERROR;
228 	memcpy(sqlstate, unknown_sqlstate, MYSQLND_SQLSTATE_LENGTH);
229 
230 	if (buf_len > 2) {
231 		*error_no = uint2korr(p);
232 		p+= 2;
233 		/*
234 		  sqlstate is following. No need to check for buf_left_len as we checked > 2 above,
235 		  if it was >=2 then we would need a check
236 		*/
237 		if (*p == '#') {
238 			++p;
239 			if ((buf_len - (p - buf)) >= MYSQLND_SQLSTATE_LENGTH) {
240 				memcpy(sqlstate, p, MYSQLND_SQLSTATE_LENGTH);
241 				p+= MYSQLND_SQLSTATE_LENGTH;
242 			} else {
243 				goto end;
244 			}
245 		}
246 		if ((buf_len - (p - buf)) > 0) {
247 			error_msg_len = MIN((int)((buf_len - (p - buf))), (int) (error_buf_len - 1));
248 			memcpy(error, p, error_msg_len);
249 		}
250 	}
251 end:
252 	sqlstate[MYSQLND_SQLSTATE_LENGTH] = '\0';
253 	error[error_msg_len]= '\0';
254 
255 	DBG_RETURN(FAIL);
256 }
257 /* }}} */
258 
259 
260 /* {{{ mysqlnd_read_header */
261 static enum_func_status
mysqlnd_read_header(MYSQLND_NET * net,MYSQLND_PACKET_HEADER * header,MYSQLND_STATS * conn_stats,MYSQLND_ERROR_INFO * error_info TSRMLS_DC)262 mysqlnd_read_header(MYSQLND_NET * net, MYSQLND_PACKET_HEADER * header,
263 					MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC)
264 {
265 	zend_uchar buffer[MYSQLND_HEADER_SIZE];
266 
267 	DBG_ENTER(mysqlnd_read_header_name);
268 	DBG_INF_FMT("compressed=%u", net->compressed);
269 	if (FAIL == net->m.receive_ex(net, buffer, MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) {
270 		DBG_RETURN(FAIL);
271 	}
272 
273 	header->size = uint3korr(buffer);
274 	header->packet_no = uint1korr(buffer + 3);
275 
276 #ifdef MYSQLND_DUMP_HEADER_N_BODY
277 	DBG_INF_FMT("HEADER: prot_packet_no=%u size=%3u", header->packet_no, header->size);
278 #endif
279 	MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn_stats,
280 							STAT_PROTOCOL_OVERHEAD_IN, MYSQLND_HEADER_SIZE,
281 							STAT_PACKETS_RECEIVED, 1);
282 
283 	if (net->compressed || net->packet_no == header->packet_no) {
284 		/*
285 		  Have to increase the number, so we can send correct number back. It will
286 		  round at 255 as this is unsigned char. The server needs this for simple
287 		  flow control checking.
288 		*/
289 		net->packet_no++;
290 		DBG_RETURN(PASS);
291 	}
292 
293 	DBG_ERR_FMT("Logical link: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
294 				net->packet_no, header->packet_no, header->size);
295 
296 	php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
297 			  net->packet_no, header->packet_no, header->size);
298 	DBG_RETURN(FAIL);
299 }
300 /* }}} */
301 
302 
303 /* {{{ php_mysqlnd_greet_read */
304 static enum_func_status
php_mysqlnd_greet_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)305 php_mysqlnd_greet_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
306 {
307 	zend_uchar buf[2048];
308 	zend_uchar *p = buf;
309 	zend_uchar *begin = buf;
310 	zend_uchar *pad_start = NULL;
311 	MYSQLND_PACKET_GREET *packet= (MYSQLND_PACKET_GREET *) _packet;
312 
313 	DBG_ENTER("php_mysqlnd_greet_read");
314 
315 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "greeting", PROT_GREET_PACKET);
316 	BAIL_IF_NO_MORE_DATA;
317 
318 	packet->auth_plugin_data = packet->intern_auth_plugin_data;
319 	packet->auth_plugin_data_len = sizeof(packet->intern_auth_plugin_data);
320 
321 	if (packet->header.size < sizeof(buf)) {
322 		/*
323 		  Null-terminate the string, so strdup can work even if the packets have a string at the end,
324 		  which is not ASCIIZ
325 		*/
326 		buf[packet->header.size] = '\0';
327 	}
328 
329 	packet->protocol_version = uint1korr(p);
330 	p++;
331 	BAIL_IF_NO_MORE_DATA;
332 
333 	if (ERROR_MARKER == packet->protocol_version) {
334 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
335 										 packet->error, sizeof(packet->error),
336 										 &packet->error_no, packet->sqlstate
337 										 TSRMLS_CC);
338 		/*
339 		  The server doesn't send sqlstate in the greet packet.
340 		  It's a bug#26426 , so we have to set it correctly ourselves.
341 		  It's probably "Too many connections, which has SQL state 08004".
342 		*/
343 		if (packet->error_no == 1040) {
344 			memcpy(packet->sqlstate, "08004", MYSQLND_SQLSTATE_LENGTH);
345 		}
346 		DBG_RETURN(PASS);
347 	}
348 
349 	packet->server_version = estrdup((char *)p);
350 	p+= strlen(packet->server_version) + 1; /* eat the '\0' */
351 	BAIL_IF_NO_MORE_DATA;
352 
353 	packet->thread_id = uint4korr(p);
354 	p+=4;
355 	BAIL_IF_NO_MORE_DATA;
356 
357 	memcpy(packet->auth_plugin_data, p, SCRAMBLE_LENGTH_323);
358 	p+= SCRAMBLE_LENGTH_323;
359 	BAIL_IF_NO_MORE_DATA;
360 
361 	/* pad1 */
362 	p++;
363 	BAIL_IF_NO_MORE_DATA;
364 
365 	packet->server_capabilities = uint2korr(p);
366 	p+= 2;
367 	BAIL_IF_NO_MORE_DATA;
368 
369 	packet->charset_no = uint1korr(p);
370 	p++;
371 	BAIL_IF_NO_MORE_DATA;
372 
373 	packet->server_status = uint2korr(p);
374 	p+= 2;
375 	BAIL_IF_NO_MORE_DATA;
376 
377 	/* pad2 */
378 	pad_start = p;
379 	p+= 13;
380 	BAIL_IF_NO_MORE_DATA;
381 
382 	if ((size_t) (p - buf) < packet->header.size) {
383 		/* auth_plugin_data is split into two parts */
384 		memcpy(packet->auth_plugin_data + SCRAMBLE_LENGTH_323, p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323);
385 		p+= SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323;
386 		p++; /* 0x0 at the end of the scramble and thus last byte in the packet in 5.1 and previous */
387 	} else {
388 		packet->pre41 = TRUE;
389 	}
390 
391 	/* Is this a 5.5+ server ? */
392 	if ((size_t) (p - buf) < packet->header.size) {
393 		 /* backtrack one byte, the 0x0 at the end of the scramble in 5.1 and previous */
394 		p--;
395 
396     	/* Additional 16 bits for server capabilities */
397 		packet->server_capabilities |= uint2korr(pad_start) << 16;
398 		/* And a length of the server scramble in one byte */
399 		packet->auth_plugin_data_len = uint1korr(pad_start + 2);
400 		if (packet->auth_plugin_data_len > SCRAMBLE_LENGTH) {
401 			/* more data*/
402 			zend_uchar * new_auth_plugin_data = emalloc(packet->auth_plugin_data_len);
403 			if (!new_auth_plugin_data) {
404 				goto premature_end;
405 			}
406 			/* copy what we already have */
407 			memcpy(new_auth_plugin_data, packet->auth_plugin_data, SCRAMBLE_LENGTH);
408 			/* add additional scramble data 5.5+ sent us */
409 			memcpy(new_auth_plugin_data + SCRAMBLE_LENGTH, p, packet->auth_plugin_data_len - SCRAMBLE_LENGTH);
410 			p+= (packet->auth_plugin_data_len - SCRAMBLE_LENGTH);
411 			packet->auth_plugin_data = new_auth_plugin_data;
412 		}
413 	}
414 
415 	if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) {
416 		BAIL_IF_NO_MORE_DATA;
417 		/* The server is 5.5.x and supports authentication plugins */
418 		packet->auth_protocol = estrdup((char *)p);
419 		p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */
420 	}
421 
422 	DBG_INF_FMT("proto=%u server=%s thread_id=%u",
423 				packet->protocol_version, packet->server_version, packet->thread_id);
424 
425 	DBG_INF_FMT("server_capabilities=%u charset_no=%u server_status=%i auth_protocol=%s scramble_length=%u",
426 				packet->server_capabilities, packet->charset_no, packet->server_status,
427 				packet->auth_protocol? packet->auth_protocol:"n/a", packet->auth_plugin_data_len);
428 
429 	DBG_RETURN(PASS);
430 premature_end:
431 	DBG_ERR_FMT("GREET packet %d bytes shorter than expected", p - begin - packet->header.size);
432 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "GREET packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
433 					 p - begin - packet->header.size);
434 	DBG_RETURN(FAIL);
435 }
436 /* }}} */
437 
438 
439 /* {{{ php_mysqlnd_greet_free_mem */
440 static
php_mysqlnd_greet_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)441 void php_mysqlnd_greet_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
442 {
443 	MYSQLND_PACKET_GREET *p= (MYSQLND_PACKET_GREET *) _packet;
444 	if (p->server_version) {
445 		efree(p->server_version);
446 		p->server_version = NULL;
447 	}
448 	if (p->auth_plugin_data && p->auth_plugin_data != p->intern_auth_plugin_data) {
449 		efree(p->auth_plugin_data);
450 		p->auth_plugin_data = NULL;
451 	}
452 	if (p->auth_protocol) {
453 		efree(p->auth_protocol);
454 		p->auth_protocol = NULL;
455 	}
456 	if (!stack_allocation) {
457 		mnd_pefree(p, p->header.persistent);
458 	}
459 }
460 /* }}} */
461 
462 
463 #define AUTH_WRITE_BUFFER_LEN (MYSQLND_HEADER_SIZE + MYSQLND_MAX_ALLOWED_USER_LEN + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 1024)
464 
465 /* {{{ php_mysqlnd_auth_write */
466 static
php_mysqlnd_auth_write(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)467 size_t php_mysqlnd_auth_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
468 {
469 	zend_uchar buffer[AUTH_WRITE_BUFFER_LEN];
470 	zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */
471 	int len;
472 	MYSQLND_PACKET_AUTH * packet= (MYSQLND_PACKET_AUTH *) _packet;
473 
474 	DBG_ENTER("php_mysqlnd_auth_write");
475 
476 	if (!packet->is_change_user_packet) {
477 		int4store(p, packet->client_flags);
478 		p+= 4;
479 
480 		int4store(p, packet->max_packet_size);
481 		p+= 4;
482 
483 		int1store(p, packet->charset_no);
484 		p++;
485 
486 		memset(p, 0, 23); /* filler */
487 		p+= 23;
488 	}
489 
490 	if (packet->send_auth_data || packet->is_change_user_packet) {
491 		len = MIN(strlen(packet->user), MYSQLND_MAX_ALLOWED_USER_LEN);
492 		memcpy(p, packet->user, len);
493 		p+= len;
494 		*p++ = '\0';
495 
496 		/* defensive coding */
497 		if (packet->auth_data == NULL) {
498 			packet->auth_data_len = 0;
499 		}
500 		if (packet->auth_data_len > 0xFF) {
501 			const char * const msg = "Authentication data too long. "
502 				"Won't fit into the buffer and will be truncated. Authentication will thus fail";
503 			SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, msg);
504 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", msg);
505 			DBG_RETURN(0);
506 		}
507 
508 		int1store(p, packet->auth_data_len);
509 		++p;
510 /*!!!!! is the buffer big enough ??? */
511 		if ((sizeof(buffer) - (p - buffer)) < packet->auth_data_len) {
512 			DBG_ERR("the stack buffer was not enough!!");
513 			DBG_RETURN(0);
514 		}
515 		if (packet->auth_data_len) {
516 			memcpy(p, packet->auth_data, packet->auth_data_len);
517 			p+= packet->auth_data_len;
518 		}
519 
520 		if (packet->db) {
521 			/* CLIENT_CONNECT_WITH_DB should have been set */
522 			size_t real_db_len = MIN(MYSQLND_MAX_ALLOWED_DB_LEN, packet->db_len);
523 			memcpy(p, packet->db, real_db_len);
524 			p+= real_db_len;
525 			*p++= '\0';
526 		} else if (packet->is_change_user_packet) {
527 			*p++= '\0';
528 		}
529 		/* no \0 for no DB */
530 
531 		if (packet->is_change_user_packet) {
532 			if (packet->charset_no) {
533 				int2store(p, packet->charset_no);
534 				p+= 2;
535 			}
536 		}
537 
538 		if (packet->auth_plugin_name) {
539 			size_t len = MIN(strlen(packet->auth_plugin_name), sizeof(buffer) - (p - buffer) - 1);
540 			memcpy(p, packet->auth_plugin_name, len);
541 			p+= len;
542 			*p++= '\0';
543 		}
544 	}
545 	if (packet->is_change_user_packet) {
546 		if (PASS != conn->m->simple_command(conn, COM_CHANGE_USER, buffer + MYSQLND_HEADER_SIZE, p - buffer - MYSQLND_HEADER_SIZE,
547 										   PROT_LAST /* the caller will handle the OK packet */,
548 										   packet->silent, TRUE TSRMLS_CC)) {
549 			DBG_RETURN(0);
550 		}
551 		DBG_RETURN(p - buffer - MYSQLND_HEADER_SIZE);
552 	} else {
553 		size_t sent = conn->net->m.send_ex(conn->net, buffer, p - buffer - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC);
554 		if (!sent) {
555 			CONN_SET_STATE(conn, CONN_QUIT_SENT);
556 		}
557 		DBG_RETURN(sent);
558 	}
559 }
560 /* }}} */
561 
562 
563 /* {{{ php_mysqlnd_auth_free_mem */
564 static
php_mysqlnd_auth_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)565 void php_mysqlnd_auth_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
566 {
567 	if (!stack_allocation) {
568 		MYSQLND_PACKET_AUTH * p = (MYSQLND_PACKET_AUTH *) _packet;
569 		mnd_pefree(p, p->header.persistent);
570 	}
571 }
572 /* }}} */
573 
574 
575 #define AUTH_RESP_BUFFER_SIZE 2048
576 
577 /* {{{ php_mysqlnd_auth_response_read */
578 static enum_func_status
php_mysqlnd_auth_response_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)579 php_mysqlnd_auth_response_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
580 {
581 	zend_uchar local_buf[AUTH_RESP_BUFFER_SIZE];
582 	size_t buf_len = conn->net->cmd_buffer.buffer? conn->net->cmd_buffer.length: AUTH_RESP_BUFFER_SIZE;
583 	zend_uchar *buf = conn->net->cmd_buffer.buffer? (zend_uchar *) conn->net->cmd_buffer.buffer : local_buf;
584 	zend_uchar *p = buf;
585 	zend_uchar *begin = buf;
586 	unsigned long i;
587 	register MYSQLND_PACKET_AUTH_RESPONSE * packet= (MYSQLND_PACKET_AUTH_RESPONSE *) _packet;
588 
589 	DBG_ENTER("php_mysqlnd_auth_response_read");
590 
591 	/* leave space for terminating safety \0 */
592 	buf_len--;
593 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "OK", PROT_OK_PACKET);
594 	BAIL_IF_NO_MORE_DATA;
595 
596 	/*
597 	  zero-terminate the buffer for safety. We are sure there is place for the \0
598 	  because buf_len is -1 the size of the buffer pointed
599 	*/
600 	buf[packet->header.size] = '\0';
601 
602 	/* Should be always 0x0 or ERROR_MARKER for error */
603 	packet->response_code = uint1korr(p);
604 	p++;
605 	BAIL_IF_NO_MORE_DATA;
606 
607 	if (ERROR_MARKER == packet->response_code) {
608 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
609 										 packet->error, sizeof(packet->error),
610 										 &packet->error_no, packet->sqlstate
611 										 TSRMLS_CC);
612 		DBG_RETURN(PASS);
613 	}
614 	if (0xFE == packet->response_code) {
615 		/* Authentication Switch Response */
616 		if (packet->header.size > (size_t) (p - buf)) {
617 			packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE);
618 			packet->new_auth_protocol_len = strlen(packet->new_auth_protocol);
619 			p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */
620 
621 			packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf);
622 			if (packet->new_auth_protocol_data_len) {
623 				packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len);
624 				memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len);
625 			}
626 			DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol);
627 			DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data);
628 		}
629 	} else {
630 		/* Everything was fine! */
631 		packet->affected_rows  = php_mysqlnd_net_field_length_ll(&p);
632 		BAIL_IF_NO_MORE_DATA;
633 
634 		packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p);
635 		BAIL_IF_NO_MORE_DATA;
636 
637 		packet->server_status = uint2korr(p);
638 		p+= 2;
639 		BAIL_IF_NO_MORE_DATA;
640 
641 		packet->warning_count = uint2korr(p);
642 		p+= 2;
643 		BAIL_IF_NO_MORE_DATA;
644 
645 		/* There is a message */
646 		if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) {
647 			packet->message_len = MIN(i, buf_len - (p - begin));
648 			packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE);
649 		} else {
650 			packet->message = NULL;
651 			packet->message_len = 0;
652 		}
653 
654 		DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%u warnings=%u",
655 					packet->affected_rows, packet->last_insert_id, packet->server_status,
656 					packet->warning_count);
657 	}
658 
659 	DBG_RETURN(PASS);
660 premature_end:
661 	DBG_ERR_FMT("OK packet %d bytes shorter than expected", p - begin - packet->header.size);
662 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "AUTH_RESPONSE packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
663 					 p - begin - packet->header.size);
664 	DBG_RETURN(FAIL);
665 }
666 /* }}} */
667 
668 
669 /* {{{ php_mysqlnd_auth_response_free_mem */
670 static void
php_mysqlnd_auth_response_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)671 php_mysqlnd_auth_response_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
672 {
673 	MYSQLND_PACKET_AUTH_RESPONSE * p = (MYSQLND_PACKET_AUTH_RESPONSE *) _packet;
674 	if (p->message) {
675 		mnd_efree(p->message);
676 		p->message = NULL;
677 	}
678 	if (p->new_auth_protocol) {
679 		mnd_efree(p->new_auth_protocol);
680 		p->new_auth_protocol = NULL;
681 	}
682 	p->new_auth_protocol_len = 0;
683 
684 	if (p->new_auth_protocol_data) {
685 		mnd_efree(p->new_auth_protocol_data);
686 		p->new_auth_protocol_data = NULL;
687 	}
688 	p->new_auth_protocol_data_len = 0;
689 
690 	if (!stack_allocation) {
691 		mnd_pefree(p, p->header.persistent);
692 	}
693 }
694 /* }}} */
695 
696 
697 /* {{{ php_mysqlnd_change_auth_response_write */
698 static size_t
php_mysqlnd_change_auth_response_write(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)699 php_mysqlnd_change_auth_response_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
700 {
701 	MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *packet= (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet;
702 	zend_uchar * buffer = conn->net->cmd_buffer.length >= packet->auth_data_len? conn->net->cmd_buffer.buffer : mnd_emalloc(packet->auth_data_len);
703 	zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */
704 
705 	DBG_ENTER("php_mysqlnd_change_auth_response_write");
706 
707 	if (packet->auth_data_len) {
708 		memcpy(p, packet->auth_data, packet->auth_data_len);
709 		p+= packet->auth_data_len;
710 	}
711 
712 	{
713 		size_t sent = conn->net->m.send_ex(conn->net, buffer, p - buffer - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC);
714 		if (buffer != conn->net->cmd_buffer.buffer) {
715 			mnd_efree(buffer);
716 		}
717 		if (!sent) {
718 			CONN_SET_STATE(conn, CONN_QUIT_SENT);
719 		}
720 		DBG_RETURN(sent);
721 	}
722 }
723 /* }}} */
724 
725 
726 /* {{{ php_mysqlnd_change_auth_response_free_mem */
727 static void
php_mysqlnd_change_auth_response_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)728 php_mysqlnd_change_auth_response_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
729 {
730 	if (!stack_allocation) {
731 		MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * p = (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet;
732 		mnd_pefree(p, p->header.persistent);
733 	}
734 }
735 /* }}} */
736 
737 
738 #define OK_BUFFER_SIZE 2048
739 
740 /* {{{ php_mysqlnd_ok_read */
741 static enum_func_status
php_mysqlnd_ok_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)742 php_mysqlnd_ok_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
743 {
744 	zend_uchar local_buf[OK_BUFFER_SIZE];
745 	size_t buf_len = conn->net->cmd_buffer.buffer? conn->net->cmd_buffer.length : OK_BUFFER_SIZE;
746 	zend_uchar *buf = conn->net->cmd_buffer.buffer? (zend_uchar *) conn->net->cmd_buffer.buffer : local_buf;
747 	zend_uchar *p = buf;
748 	zend_uchar *begin = buf;
749 	unsigned long i;
750 	register MYSQLND_PACKET_OK *packet= (MYSQLND_PACKET_OK *) _packet;
751 
752 	DBG_ENTER("php_mysqlnd_ok_read");
753 
754 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "OK", PROT_OK_PACKET);
755 	BAIL_IF_NO_MORE_DATA;
756 
757 	/* Should be always 0x0 or ERROR_MARKER for error */
758 	packet->field_count = uint1korr(p);
759 	p++;
760 	BAIL_IF_NO_MORE_DATA;
761 
762 	if (ERROR_MARKER == packet->field_count) {
763 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
764 										 packet->error, sizeof(packet->error),
765 										 &packet->error_no, packet->sqlstate
766 										 TSRMLS_CC);
767 		DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
768 		DBG_RETURN(PASS);
769 	}
770 	/* Everything was fine! */
771 	packet->affected_rows  = php_mysqlnd_net_field_length_ll(&p);
772 	BAIL_IF_NO_MORE_DATA;
773 
774 	packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p);
775 	BAIL_IF_NO_MORE_DATA;
776 
777 	packet->server_status = uint2korr(p);
778 	p+= 2;
779 	BAIL_IF_NO_MORE_DATA;
780 
781 	packet->warning_count = uint2korr(p);
782 	p+= 2;
783 	BAIL_IF_NO_MORE_DATA;
784 
785 	/* There is a message */
786 	if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) {
787 		packet->message_len = MIN(i, buf_len - (p - begin));
788 		packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE);
789 	} else {
790 		packet->message = NULL;
791 		packet->message_len = 0;
792 	}
793 
794 	DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%u warnings=%u",
795 				packet->affected_rows, packet->last_insert_id, packet->server_status,
796 				packet->warning_count);
797 
798 	BAIL_IF_NO_MORE_DATA;
799 
800 	DBG_RETURN(PASS);
801 premature_end:
802 	DBG_ERR_FMT("OK packet %d bytes shorter than expected", p - begin - packet->header.size);
803 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "OK packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
804 					 p - begin - packet->header.size);
805 	DBG_RETURN(FAIL);
806 }
807 /* }}} */
808 
809 
810 /* {{{ php_mysqlnd_ok_free_mem */
811 static void
php_mysqlnd_ok_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)812 php_mysqlnd_ok_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
813 {
814 	MYSQLND_PACKET_OK *p= (MYSQLND_PACKET_OK *) _packet;
815 	if (p->message) {
816 		mnd_efree(p->message);
817 		p->message = NULL;
818 	}
819 	if (!stack_allocation) {
820 		mnd_pefree(p, p->header.persistent);
821 	}
822 }
823 /* }}} */
824 
825 
826 /* {{{ php_mysqlnd_eof_read */
827 static enum_func_status
php_mysqlnd_eof_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)828 php_mysqlnd_eof_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
829 {
830 	/*
831 	  EOF packet is since 4.1 five bytes long,
832 	  but we can get also an error, make it bigger.
833 
834 	  Error : error_code + '#' + sqlstate + MYSQLND_ERRMSG_SIZE
835 	*/
836 	MYSQLND_PACKET_EOF *packet= (MYSQLND_PACKET_EOF *) _packet;
837 	size_t buf_len = conn->net->cmd_buffer.length;
838 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
839 	zend_uchar *p = buf;
840 	zend_uchar *begin = buf;
841 
842 	DBG_ENTER("php_mysqlnd_eof_read");
843 
844 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "EOF", PROT_EOF_PACKET);
845 	BAIL_IF_NO_MORE_DATA;
846 
847 	/* Should be always EODATA_MARKER */
848 	packet->field_count = uint1korr(p);
849 	p++;
850 	BAIL_IF_NO_MORE_DATA;
851 
852 	if (ERROR_MARKER == packet->field_count) {
853 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
854 										 packet->error, sizeof(packet->error),
855 										 &packet->error_no, packet->sqlstate
856 										 TSRMLS_CC);
857 		DBG_RETURN(PASS);
858 	}
859 
860 	/*
861 		4.1 sends 1 byte EOF packet after metadata of
862 		PREPARE/EXECUTE but 5 bytes after the result. This is not
863 		according to the Docs@Forge!!!
864 	*/
865 	if (packet->header.size > 1) {
866 		packet->warning_count = uint2korr(p);
867 		p+= 2;
868 		BAIL_IF_NO_MORE_DATA;
869 
870 		packet->server_status = uint2korr(p);
871 		p+= 2;
872 		BAIL_IF_NO_MORE_DATA;
873 	} else {
874 		packet->warning_count = 0;
875 		packet->server_status = 0;
876 	}
877 
878 	BAIL_IF_NO_MORE_DATA;
879 
880 	DBG_INF_FMT("EOF packet: fields=%u status=%u warnings=%u",
881 				packet->field_count, packet->server_status, packet->warning_count);
882 
883 	DBG_RETURN(PASS);
884 premature_end:
885 	DBG_ERR_FMT("EOF packet %d bytes shorter than expected", p - begin - packet->header.size);
886 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "EOF packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
887 					 p - begin - packet->header.size);
888 	DBG_RETURN(FAIL);
889 }
890 /* }}} */
891 
892 
893 /* {{{ php_mysqlnd_eof_free_mem */
894 static
php_mysqlnd_eof_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)895 void php_mysqlnd_eof_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
896 {
897 	if (!stack_allocation) {
898 		mnd_pefree(_packet, ((MYSQLND_PACKET_EOF *)_packet)->header.persistent);
899 	}
900 }
901 /* }}} */
902 
903 
904 /* {{{ php_mysqlnd_cmd_write */
php_mysqlnd_cmd_write(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)905 size_t php_mysqlnd_cmd_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
906 {
907 	/* Let's have some space, which we can use, if not enough, we will allocate new buffer */
908 	MYSQLND_PACKET_COMMAND * packet= (MYSQLND_PACKET_COMMAND *) _packet;
909 	MYSQLND_NET * net = conn->net;
910 	unsigned int error_reporting = EG(error_reporting);
911 	size_t sent = 0;
912 
913 	DBG_ENTER("php_mysqlnd_cmd_write");
914 	/*
915 	  Reset packet_no, or we will get bad handshake!
916 	  Every command starts a new TX and packet numbers are reset to 0.
917 	*/
918 	net->packet_no = 0;
919 	net->compressed_envelope_packet_no = 0; /* this is for the response */
920 
921 	if (error_reporting) {
922 		EG(error_reporting) = 0;
923 	}
924 
925 	MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PACKETS_SENT_CMD);
926 
927 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
928 	net->m.consume_uneaten_data(net, packet->command TSRMLS_CC);
929 #endif
930 
931 	if (!packet->argument || !packet->arg_len) {
932 		zend_uchar buffer[MYSQLND_HEADER_SIZE + 1];
933 
934 		int1store(buffer + MYSQLND_HEADER_SIZE, packet->command);
935 		sent = net->m.send_ex(net, buffer, 1, conn->stats, conn->error_info TSRMLS_CC);
936 	} else {
937 		size_t tmp_len = packet->arg_len + 1 + MYSQLND_HEADER_SIZE;
938 		zend_uchar *tmp, *p;
939 		tmp = (tmp_len > net->cmd_buffer.length)? mnd_emalloc(tmp_len):net->cmd_buffer.buffer;
940 		if (!tmp) {
941 			goto end;
942 		}
943 		p = tmp + MYSQLND_HEADER_SIZE; /* skip the header */
944 
945 		int1store(p, packet->command);
946 		p++;
947 
948 		memcpy(p, packet->argument, packet->arg_len);
949 
950 		sent = net->m.send_ex(net, tmp, tmp_len - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC);
951 		if (tmp != net->cmd_buffer.buffer) {
952 			MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CMD_BUFFER_TOO_SMALL);
953 			mnd_efree(tmp);
954 		}
955 	}
956 end:
957 	if (error_reporting) {
958 		/* restore error reporting */
959 		EG(error_reporting) = error_reporting;
960 	}
961 	if (!sent) {
962 		CONN_SET_STATE(conn, CONN_QUIT_SENT);
963 	}
964 	DBG_RETURN(sent);
965 }
966 /* }}} */
967 
968 
969 /* {{{ php_mysqlnd_cmd_free_mem */
970 static
php_mysqlnd_cmd_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)971 void php_mysqlnd_cmd_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
972 {
973 	if (!stack_allocation) {
974 		MYSQLND_PACKET_COMMAND * p = (MYSQLND_PACKET_COMMAND *) _packet;
975 		mnd_pefree(p, p->header.persistent);
976 	}
977 }
978 /* }}} */
979 
980 
981 /* {{{ php_mysqlnd_rset_header_read */
982 static enum_func_status
php_mysqlnd_rset_header_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)983 php_mysqlnd_rset_header_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
984 {
985 	enum_func_status ret = PASS;
986 	size_t buf_len = conn->net->cmd_buffer.length;
987 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
988 	zend_uchar *p = buf;
989 	zend_uchar *begin = buf;
990 	size_t len;
991 	MYSQLND_PACKET_RSET_HEADER *packet= (MYSQLND_PACKET_RSET_HEADER *) _packet;
992 
993 	DBG_ENTER("php_mysqlnd_rset_header_read");
994 
995 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "resultset header", PROT_RSET_HEADER_PACKET);
996 	BAIL_IF_NO_MORE_DATA;
997 
998 	/*
999 	  Don't increment. First byte is ERROR_MARKER on error, but otherwise is starting byte
1000 	  of encoded sequence for length.
1001 	*/
1002 	if (ERROR_MARKER == *p) {
1003 		/* Error */
1004 		p++;
1005 		BAIL_IF_NO_MORE_DATA;
1006 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
1007 										 packet->error_info.error, sizeof(packet->error_info.error),
1008 										 &packet->error_info.error_no, packet->error_info.sqlstate
1009 										 TSRMLS_CC);
1010 		DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
1011 		DBG_RETURN(PASS);
1012 	}
1013 
1014 	packet->field_count = php_mysqlnd_net_field_length(&p);
1015 	BAIL_IF_NO_MORE_DATA;
1016 
1017 	switch (packet->field_count) {
1018 		case MYSQLND_NULL_LENGTH:
1019 			DBG_INF("LOAD LOCAL");
1020 			/*
1021 			  First byte in the packet is the field count.
1022 			  Thus, the name is size - 1. And we add 1 for a trailing \0.
1023 			  Because we have BAIL_IF_NO_MORE_DATA before the switch, we are guaranteed
1024 			  that packet->header.size is > 0. Which means that len can't underflow, that
1025 			  would lead to 0 byte allocation but 2^32 or 2^64 bytes copied.
1026 			*/
1027 			len = packet->header.size - 1;
1028 			packet->info_or_local_file = mnd_emalloc(len + 1);
1029 			if (packet->info_or_local_file) {
1030 				memcpy(packet->info_or_local_file, p, len);
1031 				packet->info_or_local_file[len] = '\0';
1032 				packet->info_or_local_file_len = len;
1033 			} else {
1034 				SET_OOM_ERROR(*conn->error_info);
1035 				ret = FAIL;
1036 			}
1037 			break;
1038 		case 0x00:
1039 			DBG_INF("UPSERT");
1040 			packet->affected_rows = php_mysqlnd_net_field_length_ll(&p);
1041 			BAIL_IF_NO_MORE_DATA;
1042 
1043 			packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p);
1044 			BAIL_IF_NO_MORE_DATA;
1045 
1046 			packet->server_status = uint2korr(p);
1047 			p+=2;
1048 			BAIL_IF_NO_MORE_DATA;
1049 
1050 			packet->warning_count = uint2korr(p);
1051 			p+=2;
1052 			BAIL_IF_NO_MORE_DATA;
1053 			/* Check for additional textual data */
1054 			if (packet->header.size  > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) {
1055 				packet->info_or_local_file = mnd_emalloc(len + 1);
1056 				if (packet->info_or_local_file) {
1057 					memcpy(packet->info_or_local_file, p, len);
1058 					packet->info_or_local_file[len] = '\0';
1059 					packet->info_or_local_file_len = len;
1060 				} else {
1061 					SET_OOM_ERROR(*conn->error_info);
1062 					ret = FAIL;
1063 				}
1064 			}
1065 			DBG_INF_FMT("affected_rows=%llu last_insert_id=%llu server_status=%u warning_count=%u",
1066 						packet->affected_rows, packet->last_insert_id,
1067 						packet->server_status, packet->warning_count);
1068 			break;
1069 		default:
1070 			DBG_INF("SELECT");
1071 			/* Result set */
1072 			break;
1073 	}
1074 	BAIL_IF_NO_MORE_DATA;
1075 
1076 	DBG_RETURN(ret);
1077 premature_end:
1078 	DBG_ERR_FMT("RSET_HEADER packet %d bytes shorter than expected", p - begin - packet->header.size);
1079 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "RSET_HEADER packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
1080 					 p - begin - packet->header.size);
1081 	DBG_RETURN(FAIL);
1082 }
1083 /* }}} */
1084 
1085 
1086 /* {{{ php_mysqlnd_rset_header_free_mem */
1087 static
php_mysqlnd_rset_header_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)1088 void php_mysqlnd_rset_header_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
1089 {
1090 	MYSQLND_PACKET_RSET_HEADER *p= (MYSQLND_PACKET_RSET_HEADER *) _packet;
1091 	DBG_ENTER("php_mysqlnd_rset_header_free_mem");
1092 	if (p->info_or_local_file) {
1093 		mnd_efree(p->info_or_local_file);
1094 		p->info_or_local_file = NULL;
1095 	}
1096 	if (!stack_allocation) {
1097 		mnd_pefree(p, p->header.persistent);
1098 	}
1099 	DBG_VOID_RETURN;
1100 }
1101 /* }}} */
1102 
1103 static size_t rset_field_offsets[] =
1104 {
1105 	STRUCT_OFFSET(MYSQLND_FIELD, catalog),
1106 	STRUCT_OFFSET(MYSQLND_FIELD, catalog_length),
1107 	STRUCT_OFFSET(MYSQLND_FIELD, db),
1108 	STRUCT_OFFSET(MYSQLND_FIELD, db_length),
1109 	STRUCT_OFFSET(MYSQLND_FIELD, table),
1110 	STRUCT_OFFSET(MYSQLND_FIELD, table_length),
1111 	STRUCT_OFFSET(MYSQLND_FIELD, org_table),
1112 	STRUCT_OFFSET(MYSQLND_FIELD, org_table_length),
1113 	STRUCT_OFFSET(MYSQLND_FIELD, name),
1114 	STRUCT_OFFSET(MYSQLND_FIELD, name_length),
1115 	STRUCT_OFFSET(MYSQLND_FIELD, org_name),
1116 	STRUCT_OFFSET(MYSQLND_FIELD, org_name_length)
1117 };
1118 
1119 
1120 /* {{{ php_mysqlnd_rset_field_read */
1121 static enum_func_status
php_mysqlnd_rset_field_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)1122 php_mysqlnd_rset_field_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
1123 {
1124 	/* Should be enough for the metadata of a single row */
1125 	MYSQLND_PACKET_RES_FIELD *packet= (MYSQLND_PACKET_RES_FIELD *) _packet;
1126 	size_t buf_len = conn->net->cmd_buffer.length, total_len = 0;
1127 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
1128 	zend_uchar *p = buf;
1129 	zend_uchar *begin = buf;
1130 	char *root_ptr;
1131 	unsigned long len;
1132 	MYSQLND_FIELD *meta;
1133 	unsigned int i, field_count = sizeof(rset_field_offsets)/sizeof(size_t);
1134 
1135 	DBG_ENTER("php_mysqlnd_rset_field_read");
1136 
1137 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "field", PROT_RSET_FLD_PACKET);
1138 
1139 	if (packet->skip_parsing) {
1140 		DBG_RETURN(PASS);
1141 	}
1142 
1143 	BAIL_IF_NO_MORE_DATA;
1144 	if (ERROR_MARKER == *p) {
1145 		/* Error */
1146 		p++;
1147 		BAIL_IF_NO_MORE_DATA;
1148 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
1149 										 packet->error_info.error, sizeof(packet->error_info.error),
1150 										 &packet->error_info.error_no, packet->error_info.sqlstate
1151 										 TSRMLS_CC);
1152 		DBG_ERR_FMT("Server error : (%u) %s", packet->error_info.error_no, packet->error_info.error);
1153 		DBG_RETURN(PASS);
1154 	} else if (EODATA_MARKER == *p && packet->header.size < 8) {
1155 		/* Premature EOF. That should be COM_FIELD_LIST */
1156 		DBG_INF("Premature EOF. That should be COM_FIELD_LIST");
1157 		packet->stupid_list_fields_eof = TRUE;
1158 		DBG_RETURN(PASS);
1159 	}
1160 
1161 	meta = packet->metadata;
1162 
1163 	for (i = 0; i < field_count; i += 2) {
1164 		len = php_mysqlnd_net_field_length(&p);
1165 		BAIL_IF_NO_MORE_DATA;
1166 		switch ((len)) {
1167 			case 0:
1168 				*(const char **)(((char*)meta) + rset_field_offsets[i]) = mysqlnd_empty_string;
1169 				*(unsigned int *)(((char*)meta) + rset_field_offsets[i+1]) = 0;
1170 				break;
1171 			case MYSQLND_NULL_LENGTH:
1172 				goto faulty_or_fake;
1173 			default:
1174 				*(const char **)(((char *)meta) + rset_field_offsets[i]) = (const char *)p;
1175 				*(unsigned int *)(((char*)meta) + rset_field_offsets[i+1]) = len;
1176 				p += len;
1177 				total_len += len + 1;
1178 				break;
1179 		}
1180 		BAIL_IF_NO_MORE_DATA;
1181 	}
1182 
1183 	/* 1 byte length */
1184 	if (12 != *p) {
1185 		DBG_ERR_FMT("Protocol error. Server sent false length. Expected 12 got %d", (int) *p);
1186 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol error. Server sent false length. Expected 12");
1187 	}
1188 	p++;
1189 	BAIL_IF_NO_MORE_DATA;
1190 
1191 	meta->charsetnr = uint2korr(p);
1192 	p += 2;
1193 	BAIL_IF_NO_MORE_DATA;
1194 
1195 	meta->length = uint4korr(p);
1196 	p += 4;
1197 	BAIL_IF_NO_MORE_DATA;
1198 
1199 	meta->type = uint1korr(p);
1200 	p += 1;
1201 	BAIL_IF_NO_MORE_DATA;
1202 
1203 	meta->flags = uint2korr(p);
1204 	p += 2;
1205 	BAIL_IF_NO_MORE_DATA;
1206 
1207 	meta->decimals = uint1korr(p);
1208 	p += 1;
1209 	BAIL_IF_NO_MORE_DATA;
1210 
1211 	/* 2 byte filler */
1212 	p +=2;
1213 	BAIL_IF_NO_MORE_DATA;
1214 
1215 	/* Should we set NUM_FLAG (libmysql does it) ? */
1216 	if (
1217 		(meta->type <= MYSQL_TYPE_INT24 &&
1218 			(meta->type != MYSQL_TYPE_TIMESTAMP || meta->length == 14 || meta->length == 8)
1219 		) || meta->type == MYSQL_TYPE_YEAR)
1220 	{
1221 		meta->flags |= NUM_FLAG;
1222 	}
1223 
1224 
1225 	/*
1226 	  def could be empty, thus don't allocate on the root.
1227 	  NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL.
1228 	  Otherwise the string is length encoded.
1229 	*/
1230 	if (packet->header.size > (size_t) (p - buf) &&
1231 		(len = php_mysqlnd_net_field_length(&p)) &&
1232 		len != MYSQLND_NULL_LENGTH)
1233 	{
1234 		BAIL_IF_NO_MORE_DATA;
1235 		DBG_INF_FMT("Def found, length %lu, persistent=%u", len, packet->persistent_alloc);
1236 		meta->def = mnd_pemalloc(len + 1, packet->persistent_alloc);
1237 		if (!meta->def) {
1238 			SET_OOM_ERROR(*conn->error_info);
1239 			DBG_RETURN(FAIL);
1240 		}
1241 		memcpy(meta->def, p, len);
1242 		meta->def[len] = '\0';
1243 		meta->def_length = len;
1244 		p += len;
1245 	}
1246 
1247 	DBG_INF_FMT("allocing root. persistent=%u", packet->persistent_alloc);
1248 	root_ptr = meta->root = mnd_pemalloc(total_len, packet->persistent_alloc);
1249 	if (!root_ptr) {
1250 		SET_OOM_ERROR(*conn->error_info);
1251 		DBG_RETURN(FAIL);
1252 	}
1253 
1254 	meta->root_len = total_len;
1255 	/* Now do allocs */
1256 	if (meta->catalog && meta->catalog != mysqlnd_empty_string) {
1257 		len = meta->catalog_length;
1258 		meta->catalog = memcpy(root_ptr, meta->catalog, len);
1259 		*(root_ptr +=len) = '\0';
1260 		root_ptr++;
1261 	}
1262 
1263 	if (meta->db && meta->db != mysqlnd_empty_string) {
1264 		len = meta->db_length;
1265 		meta->db = memcpy(root_ptr, meta->db, len);
1266 		*(root_ptr +=len) = '\0';
1267 		root_ptr++;
1268 	}
1269 
1270 	if (meta->table && meta->table != mysqlnd_empty_string) {
1271 		len = meta->table_length;
1272 		meta->table = memcpy(root_ptr, meta->table, len);
1273 		*(root_ptr +=len) = '\0';
1274 		root_ptr++;
1275 	}
1276 
1277 	if (meta->org_table && meta->org_table != mysqlnd_empty_string) {
1278 		len = meta->org_table_length;
1279 		meta->org_table = memcpy(root_ptr, meta->org_table, len);
1280 		*(root_ptr +=len) = '\0';
1281 		root_ptr++;
1282 	}
1283 
1284 	if (meta->name && meta->name != mysqlnd_empty_string) {
1285 		len = meta->name_length;
1286 		meta->name = memcpy(root_ptr, meta->name, len);
1287 		*(root_ptr +=len) = '\0';
1288 		root_ptr++;
1289 	}
1290 
1291 	if (meta->org_name && meta->org_name != mysqlnd_empty_string) {
1292 		len = meta->org_name_length;
1293 		meta->org_name = memcpy(root_ptr, meta->org_name, len);
1294 		*(root_ptr +=len) = '\0';
1295 		root_ptr++;
1296 	}
1297 
1298 	DBG_INF_FMT("FIELD=[%s.%s.%s]", meta->db? meta->db:"*NA*", meta->table? meta->table:"*NA*",
1299 				meta->name? meta->name:"*NA*");
1300 
1301 	DBG_RETURN(PASS);
1302 
1303 faulty_or_fake:
1304 	DBG_ERR_FMT("Protocol error. Server sent NULL_LENGTH. The server is faulty");
1305 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol error. Server sent NULL_LENGTH."
1306 					 " The server is faulty");
1307 	DBG_RETURN(FAIL);
1308 premature_end:
1309 	DBG_ERR_FMT("RSET field packet %d bytes shorter than expected", p - begin - packet->header.size);
1310 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Result set field packet "MYSQLND_SZ_T_SPEC" bytes "
1311 			 		"shorter than expected", p - begin - packet->header.size);
1312 	DBG_RETURN(FAIL);
1313 }
1314 /* }}} */
1315 
1316 
1317 /* {{{ php_mysqlnd_rset_field_free_mem */
1318 static
php_mysqlnd_rset_field_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)1319 void php_mysqlnd_rset_field_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
1320 {
1321 	MYSQLND_PACKET_RES_FIELD *p= (MYSQLND_PACKET_RES_FIELD *) _packet;
1322 	/* p->metadata was passed to us as temporal buffer */
1323 	if (!stack_allocation) {
1324 		mnd_pefree(p, p->header.persistent);
1325 	}
1326 }
1327 /* }}} */
1328 
1329 
1330 /* {{{ php_mysqlnd_read_row_ex */
1331 static enum_func_status
php_mysqlnd_read_row_ex(MYSQLND_CONN_DATA * conn,MYSQLND_MEMORY_POOL * result_set_memory_pool,MYSQLND_MEMORY_POOL_CHUNK ** buffer,size_t * data_size,zend_bool persistent_alloc,unsigned int prealloc_more_bytes TSRMLS_DC)1332 php_mysqlnd_read_row_ex(MYSQLND_CONN_DATA * conn, MYSQLND_MEMORY_POOL * result_set_memory_pool,
1333 						MYSQLND_MEMORY_POOL_CHUNK ** buffer,
1334 						size_t * data_size, zend_bool persistent_alloc,
1335 						unsigned int prealloc_more_bytes TSRMLS_DC)
1336 {
1337 	enum_func_status ret = PASS;
1338 	MYSQLND_PACKET_HEADER header;
1339 	zend_uchar * p = NULL;
1340 	zend_bool first_iteration = TRUE;
1341 
1342 	DBG_ENTER("php_mysqlnd_read_row_ex");
1343 
1344 	/*
1345 	  To ease the process the server splits everything in packets up to 2^24 - 1.
1346 	  Even in the case the payload is evenly divisible by this value, the last
1347 	  packet will be empty, namely 0 bytes. Thus, we can read every packet and ask
1348 	  for next one if they have 2^24 - 1 sizes. But just read the header of a
1349 	  zero-length byte, don't read the body, there is no such.
1350 	*/
1351 
1352 	*data_size = prealloc_more_bytes;
1353 	while (1) {
1354 		if (FAIL == mysqlnd_read_header(conn->net, &header, conn->stats, conn->error_info TSRMLS_CC)) {
1355 			ret = FAIL;
1356 			break;
1357 		}
1358 
1359 		*data_size += header.size;
1360 
1361 		if (first_iteration) {
1362 			first_iteration = FALSE;
1363 			/*
1364 			  We need a trailing \0 for the last string, in case of text-mode,
1365 			  to be able to implement read-only variables. Thus, we add + 1.
1366 			*/
1367 			*buffer = result_set_memory_pool->get_chunk(result_set_memory_pool, *data_size + 1 TSRMLS_CC);
1368 			if (!*buffer) {
1369 				ret = FAIL;
1370 				break;
1371 			}
1372 			p = (*buffer)->ptr;
1373 		} else if (!first_iteration) {
1374 			/* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */
1375 			if (!header.size) {
1376 				break;
1377 			}
1378 
1379 			/*
1380 			  We have to realloc the buffer.
1381 
1382 			  We need a trailing \0 for the last string, in case of text-mode,
1383 			  to be able to implement read-only variables.
1384 			*/
1385 			if (FAIL == (*buffer)->resize_chunk((*buffer), *data_size + 1 TSRMLS_CC)) {
1386 				SET_OOM_ERROR(*conn->error_info);
1387 				ret = FAIL;
1388 				break;
1389 			}
1390 			/* The position could have changed, recalculate */
1391 			p = (*buffer)->ptr + (*data_size - header.size);
1392 		}
1393 
1394 		if (PASS != (ret = conn->net->m.receive_ex(conn->net, p, header.size, conn->stats, conn->error_info TSRMLS_CC))) {
1395 			DBG_ERR("Empty row packet body");
1396 			php_error(E_WARNING, "Empty row packet body");
1397 			break;
1398 		}
1399 
1400 		if (header.size < MYSQLND_MAX_PACKET_SIZE) {
1401 			break;
1402 		}
1403 	}
1404 	if (ret == FAIL && *buffer) {
1405 		(*buffer)->free_chunk((*buffer) TSRMLS_CC);
1406 		*buffer = NULL;
1407 	}
1408 	*data_size -= prealloc_more_bytes;
1409 	DBG_RETURN(ret);
1410 }
1411 /* }}} */
1412 
1413 
1414 /* {{{ php_mysqlnd_rowp_read_binary_protocol */
1415 enum_func_status
php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,zval ** fields,unsigned int field_count,MYSQLND_FIELD * fields_metadata,zend_bool as_unicode,zend_bool as_int_or_float,MYSQLND_STATS * stats TSRMLS_DC)1416 php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
1417 									  unsigned int field_count, MYSQLND_FIELD *fields_metadata,
1418 									  zend_bool as_unicode, zend_bool as_int_or_float,
1419 									  MYSQLND_STATS * stats TSRMLS_DC)
1420 {
1421 	unsigned int i;
1422 	zend_uchar * p = row_buffer->ptr;
1423 	zend_uchar * null_ptr, bit;
1424 	zval **current_field, **end_field, **start_field;
1425 
1426 	DBG_ENTER("php_mysqlnd_rowp_read_binary_protocol");
1427 
1428 	if (!fields) {
1429 		DBG_RETURN(FAIL);
1430 	}
1431 
1432 	end_field = (start_field = fields) + field_count;
1433 
1434 	/* skip the first byte, not EODATA_MARKER -> 0x0, status */
1435 	p++;
1436 	null_ptr= p;
1437 	p += (field_count + 9)/8;	/* skip null bits */
1438 	bit	= 4;					/* first 2 bits are reserved */
1439 
1440 	for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) {
1441 		DBG_INF("Directly creating zval");
1442 		MAKE_STD_ZVAL(*current_field);
1443 		if (!*current_field) {
1444 			DBG_RETURN(FAIL);
1445 		}
1446 	}
1447 
1448 	for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) {
1449 		enum_mysqlnd_collected_stats statistic;
1450 		zend_uchar * orig_p = p;
1451 
1452 		DBG_INF_FMT("Into zval=%p decoding column %u [%s.%s.%s] type=%u field->flags&unsigned=%u flags=%u is_bit=%u as_unicode=%u",
1453 			*current_field, i,
1454 			fields_metadata[i].db, fields_metadata[i].table, fields_metadata[i].name, fields_metadata[i].type,
1455 			fields_metadata[i].flags & UNSIGNED_FLAG, fields_metadata[i].flags, fields_metadata[i].type == MYSQL_TYPE_BIT, as_unicode);
1456 		if (*null_ptr & bit) {
1457 			DBG_INF("It's null");
1458 			ZVAL_NULL(*current_field);
1459 			statistic = STAT_BINARY_TYPE_FETCHED_NULL;
1460 		} else {
1461 			enum_mysqlnd_field_types type = fields_metadata[i].type;
1462 			mysqlnd_ps_fetch_functions[type].func(*current_field, &fields_metadata[i], 0, &p, as_unicode TSRMLS_CC);
1463 
1464 			if (MYSQLND_G(collect_statistics)) {
1465 				switch (fields_metadata[i].type) {
1466 					case MYSQL_TYPE_DECIMAL:	statistic = STAT_BINARY_TYPE_FETCHED_DECIMAL; break;
1467 					case MYSQL_TYPE_TINY:		statistic = STAT_BINARY_TYPE_FETCHED_INT8; break;
1468 					case MYSQL_TYPE_SHORT:		statistic = STAT_BINARY_TYPE_FETCHED_INT16; break;
1469 					case MYSQL_TYPE_LONG:		statistic = STAT_BINARY_TYPE_FETCHED_INT32; break;
1470 					case MYSQL_TYPE_FLOAT:		statistic = STAT_BINARY_TYPE_FETCHED_FLOAT; break;
1471 					case MYSQL_TYPE_DOUBLE:		statistic = STAT_BINARY_TYPE_FETCHED_DOUBLE; break;
1472 					case MYSQL_TYPE_NULL:		statistic = STAT_BINARY_TYPE_FETCHED_NULL; break;
1473 					case MYSQL_TYPE_TIMESTAMP:	statistic = STAT_BINARY_TYPE_FETCHED_TIMESTAMP; break;
1474 					case MYSQL_TYPE_LONGLONG:	statistic = STAT_BINARY_TYPE_FETCHED_INT64; break;
1475 					case MYSQL_TYPE_INT24:		statistic = STAT_BINARY_TYPE_FETCHED_INT24; break;
1476 					case MYSQL_TYPE_DATE:		statistic = STAT_BINARY_TYPE_FETCHED_DATE; break;
1477 					case MYSQL_TYPE_TIME:		statistic = STAT_BINARY_TYPE_FETCHED_TIME; break;
1478 					case MYSQL_TYPE_DATETIME:	statistic = STAT_BINARY_TYPE_FETCHED_DATETIME; break;
1479 					case MYSQL_TYPE_YEAR:		statistic = STAT_BINARY_TYPE_FETCHED_YEAR; break;
1480 					case MYSQL_TYPE_NEWDATE:	statistic = STAT_BINARY_TYPE_FETCHED_DATE; break;
1481 					case MYSQL_TYPE_VARCHAR:	statistic = STAT_BINARY_TYPE_FETCHED_STRING; break;
1482 					case MYSQL_TYPE_BIT:		statistic = STAT_BINARY_TYPE_FETCHED_BIT; break;
1483 					case MYSQL_TYPE_NEWDECIMAL:	statistic = STAT_BINARY_TYPE_FETCHED_DECIMAL; break;
1484 					case MYSQL_TYPE_ENUM:		statistic = STAT_BINARY_TYPE_FETCHED_ENUM; break;
1485 					case MYSQL_TYPE_SET:		statistic = STAT_BINARY_TYPE_FETCHED_SET; break;
1486 					case MYSQL_TYPE_TINY_BLOB:	statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break;
1487 					case MYSQL_TYPE_MEDIUM_BLOB:statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break;
1488 					case MYSQL_TYPE_LONG_BLOB:	statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break;
1489 					case MYSQL_TYPE_BLOB:		statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break;
1490 					case MYSQL_TYPE_VAR_STRING:	statistic = STAT_BINARY_TYPE_FETCHED_STRING; break;
1491 					case MYSQL_TYPE_STRING:		statistic = STAT_BINARY_TYPE_FETCHED_STRING; break;
1492 					case MYSQL_TYPE_GEOMETRY:	statistic = STAT_BINARY_TYPE_FETCHED_GEOMETRY; break;
1493 					default: statistic = STAT_BINARY_TYPE_FETCHED_OTHER; break;
1494 				}
1495 			}
1496 		}
1497 		MYSQLND_INC_CONN_STATISTIC_W_VALUE2(stats, statistic, 1,
1498 										STAT_BYTES_RECEIVED_PURE_DATA_PS,
1499 										(Z_TYPE_PP(current_field) == IS_STRING)?
1500 											Z_STRLEN_PP(current_field) : (p - orig_p));
1501 
1502 		if (!((bit<<=1) & 255)) {
1503 			bit = 1;	/* to the following byte */
1504 			null_ptr++;
1505 		}
1506 	}
1507 
1508 	DBG_RETURN(PASS);
1509 }
1510 /* }}} */
1511 
1512 
1513 /* {{{ php_mysqlnd_rowp_read_text_protocol */
1514 enum_func_status
php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,zval ** fields,unsigned int field_count,MYSQLND_FIELD * fields_metadata,zend_bool as_unicode,zend_bool as_int_or_float,MYSQLND_STATS * stats TSRMLS_DC)1515 php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
1516 									unsigned int field_count, MYSQLND_FIELD *fields_metadata,
1517 									zend_bool as_unicode, zend_bool as_int_or_float,
1518 									MYSQLND_STATS * stats TSRMLS_DC)
1519 {
1520 	unsigned int i;
1521 	zend_bool last_field_was_string = FALSE;
1522 	zval **current_field, **end_field, **start_field;
1523 	zend_uchar * p = row_buffer->ptr;
1524 	size_t data_size = row_buffer->app;
1525 	zend_uchar * bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */
1526 
1527 	DBG_ENTER("php_mysqlnd_rowp_read_text_protocol");
1528 
1529 	if (!fields) {
1530 		DBG_RETURN(FAIL);
1531 	}
1532 
1533 	end_field = (start_field = fields) + field_count;
1534 
1535 	for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) {
1536 		DBG_INF("Directly creating zval");
1537 		MAKE_STD_ZVAL(*current_field);
1538 		if (!*current_field) {
1539 			DBG_RETURN(FAIL);
1540 		}
1541 	}
1542 
1543 	for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) {
1544 		/* Don't reverse the order. It is significant!*/
1545 		zend_uchar *this_field_len_pos = p;
1546 		/* php_mysqlnd_net_field_length() call should be after *this_field_len_pos = p; */
1547 		unsigned long len = php_mysqlnd_net_field_length(&p);
1548 
1549 		if (current_field > start_field && last_field_was_string) {
1550 			/*
1551 			  Normal queries:
1552 			  We have to put \0 now to the end of the previous field, if it was
1553 			  a string. IS_NULL doesn't matter. Because we have already read our
1554 			  length, then we can overwrite it in the row buffer.
1555 			  This statement terminates the previous field, not the current one.
1556 
1557 			  NULL_LENGTH is encoded in one byte, so we can stick a \0 there.
1558 			  Any string's length is encoded in at least one byte, so we can stick
1559 			  a \0 there.
1560 			*/
1561 
1562 			*this_field_len_pos = '\0';
1563 		}
1564 
1565 		/* NULL or NOT NULL, this is the question! */
1566 		if (len == MYSQLND_NULL_LENGTH) {
1567 			ZVAL_NULL(*current_field);
1568 			last_field_was_string = FALSE;
1569 		} else {
1570 #if MYSQLND_UNICODE || defined(MYSQLND_STRING_TO_INT_CONVERSION)
1571 			struct st_mysqlnd_perm_bind perm_bind =
1572 					mysqlnd_ps_fetch_functions[fields_metadata[i].type];
1573 #endif
1574 			if (MYSQLND_G(collect_statistics)) {
1575 				enum_mysqlnd_collected_stats statistic;
1576 				switch (fields_metadata[i].type) {
1577 					case MYSQL_TYPE_DECIMAL:	statistic = STAT_TEXT_TYPE_FETCHED_DECIMAL; break;
1578 					case MYSQL_TYPE_TINY:		statistic = STAT_TEXT_TYPE_FETCHED_INT8; break;
1579 					case MYSQL_TYPE_SHORT:		statistic = STAT_TEXT_TYPE_FETCHED_INT16; break;
1580 					case MYSQL_TYPE_LONG:		statistic = STAT_TEXT_TYPE_FETCHED_INT32; break;
1581 					case MYSQL_TYPE_FLOAT:		statistic = STAT_TEXT_TYPE_FETCHED_FLOAT; break;
1582 					case MYSQL_TYPE_DOUBLE:		statistic = STAT_TEXT_TYPE_FETCHED_DOUBLE; break;
1583 					case MYSQL_TYPE_NULL:		statistic = STAT_TEXT_TYPE_FETCHED_NULL; break;
1584 					case MYSQL_TYPE_TIMESTAMP:	statistic = STAT_TEXT_TYPE_FETCHED_TIMESTAMP; break;
1585 					case MYSQL_TYPE_LONGLONG:	statistic = STAT_TEXT_TYPE_FETCHED_INT64; break;
1586 					case MYSQL_TYPE_INT24:		statistic = STAT_TEXT_TYPE_FETCHED_INT24; break;
1587 					case MYSQL_TYPE_DATE:		statistic = STAT_TEXT_TYPE_FETCHED_DATE; break;
1588 					case MYSQL_TYPE_TIME:		statistic = STAT_TEXT_TYPE_FETCHED_TIME; break;
1589 					case MYSQL_TYPE_DATETIME:	statistic = STAT_TEXT_TYPE_FETCHED_DATETIME; break;
1590 					case MYSQL_TYPE_YEAR:		statistic = STAT_TEXT_TYPE_FETCHED_YEAR; break;
1591 					case MYSQL_TYPE_NEWDATE:	statistic = STAT_TEXT_TYPE_FETCHED_DATE; break;
1592 					case MYSQL_TYPE_VARCHAR:	statistic = STAT_TEXT_TYPE_FETCHED_STRING; break;
1593 					case MYSQL_TYPE_BIT:		statistic = STAT_TEXT_TYPE_FETCHED_BIT; break;
1594 					case MYSQL_TYPE_NEWDECIMAL:	statistic = STAT_TEXT_TYPE_FETCHED_DECIMAL; break;
1595 					case MYSQL_TYPE_ENUM:		statistic = STAT_TEXT_TYPE_FETCHED_ENUM; break;
1596 					case MYSQL_TYPE_SET:		statistic = STAT_TEXT_TYPE_FETCHED_SET; break;
1597 					case MYSQL_TYPE_TINY_BLOB:	statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break;
1598 					case MYSQL_TYPE_MEDIUM_BLOB:statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break;
1599 					case MYSQL_TYPE_LONG_BLOB:	statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break;
1600 					case MYSQL_TYPE_BLOB:		statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break;
1601 					case MYSQL_TYPE_VAR_STRING:	statistic = STAT_TEXT_TYPE_FETCHED_STRING; break;
1602 					case MYSQL_TYPE_STRING:		statistic = STAT_TEXT_TYPE_FETCHED_STRING; break;
1603 					case MYSQL_TYPE_GEOMETRY:	statistic = STAT_TEXT_TYPE_FETCHED_GEOMETRY; break;
1604 					default: statistic = STAT_TEXT_TYPE_FETCHED_OTHER; break;
1605 				}
1606 				MYSQLND_INC_CONN_STATISTIC_W_VALUE2(stats, statistic, 1, STAT_BYTES_RECEIVED_PURE_DATA_TEXT, len);
1607 			}
1608 #ifdef MYSQLND_STRING_TO_INT_CONVERSION
1609 			if (as_int_or_float && perm_bind.php_type == IS_LONG) {
1610 				zend_uchar save = *(p + len);
1611 				/* We have to make it ASCIIZ temporarily */
1612 				*(p + len) = '\0';
1613 				if (perm_bind.pack_len < SIZEOF_LONG) {
1614 					/* direct conversion */
1615 					int64_t v =
1616 #ifndef PHP_WIN32
1617 						atoll((char *) p);
1618 #else
1619 						_atoi64((char *) p);
1620 #endif
1621 					ZVAL_LONG(*current_field, (long) v); /* the cast is safe */
1622 				} else {
1623 					uint64_t v =
1624 #ifndef PHP_WIN32
1625 						(uint64_t) atoll((char *) p);
1626 #else
1627 						(uint64_t) _atoi64((char *) p);
1628 #endif
1629 					zend_bool uns = fields_metadata[i].flags & UNSIGNED_FLAG? TRUE:FALSE;
1630 					/* We have to make it ASCIIZ temporarily */
1631 #if SIZEOF_LONG==8
1632 					if (uns == TRUE && v > 9223372036854775807L)
1633 #elif SIZEOF_LONG==4
1634 					if ((uns == TRUE && v > L64(2147483647)) ||
1635 						(uns == FALSE && (( L64(2147483647) < (int64_t) v) ||
1636 						(L64(-2147483648) > (int64_t) v))))
1637 #else
1638 #error Need fix for this architecture
1639 #endif /* SIZEOF */
1640 					{
1641 						ZVAL_STRINGL(*current_field, (char *)p, len, 0);
1642 					} else {
1643 						ZVAL_LONG(*current_field, (long) v); /* the cast is safe */
1644 					}
1645 				}
1646 				*(p + len) = save;
1647 			} else if (as_int_or_float && perm_bind.php_type == IS_DOUBLE) {
1648 				zend_uchar save = *(p + len);
1649 				/* We have to make it ASCIIZ temporarily */
1650 				*(p + len) = '\0';
1651 				ZVAL_DOUBLE(*current_field, atof((char *) p));
1652 				*(p + len) = save;
1653 			} else
1654 #endif /* MYSQLND_STRING_TO_INT_CONVERSION */
1655 			if (fields_metadata[i].type == MYSQL_TYPE_BIT) {
1656 				/*
1657 				  BIT fields are specially handled. As they come as bit mask, we have
1658 				  to convert it to human-readable representation. As the bits take
1659 				  less space in the protocol than the numbers they represent, we don't
1660 				  have enough space in the packet buffer to overwrite inside.
1661 				  Thus, a bit more space is pre-allocated at the end of the buffer,
1662 				  see php_mysqlnd_rowp_read(). And we add the strings at the end.
1663 				  Definitely not nice, _hackish_ :(, but works.
1664 				*/
1665 				zend_uchar *start = bit_area;
1666 				ps_fetch_from_1_to_8_bytes(*current_field, &(fields_metadata[i]), 0, &p, as_unicode, len TSRMLS_CC);
1667 				/*
1668 				  We have advanced in ps_fetch_from_1_to_8_bytes. We should go back because
1669 				  later in this function there will be an advancement.
1670 				*/
1671 				p -= len;
1672 				if (Z_TYPE_PP(current_field) == IS_LONG) {
1673 					bit_area += 1 + sprintf((char *)start, "%ld", Z_LVAL_PP(current_field));
1674 #if MYSQLND_UNICODE
1675 					if (as_unicode) {
1676 						ZVAL_UTF8_STRINGL(*current_field, start, bit_area - start - 1, 0);
1677 					} else
1678 #endif
1679 					{
1680 						ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0);
1681 					}
1682 				} else if (Z_TYPE_PP(current_field) == IS_STRING){
1683 					memcpy(bit_area, Z_STRVAL_PP(current_field), Z_STRLEN_PP(current_field));
1684 					bit_area += Z_STRLEN_PP(current_field);
1685 					*bit_area++ = '\0';
1686 					zval_dtor(*current_field);
1687 #if MYSQLND_UNICODE
1688 					if (as_unicode) {
1689 						ZVAL_UTF8_STRINGL(*current_field, start, bit_area - start - 1, 0);
1690 					} else
1691 #endif
1692 					{
1693 						ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0);
1694 					}
1695 				}
1696 				/*
1697 				  IS_UNICODE should not be specially handled. In unicode mode
1698 				  the buffers are not referenced - everything is copied.
1699 				*/
1700 			} else
1701 #if MYSQLND_UNICODE == 0
1702 			{
1703 				ZVAL_STRINGL(*current_field, (char *)p, len, 0);
1704 			}
1705 #else
1706 			/*
1707 			  Here we have to convert to UTF16, which means not reusing the buffer.
1708 			  Which in turn means that we can free the buffers once we have
1709 			  stored the result set, if we use store_result().
1710 
1711 			  Also the destruction of the zvals should not call zval_copy_ctor()
1712 			  because then we will leak.
1713 
1714 			  XXX: Keep in mind that up there there is an open `else` in
1715 			  #ifdef MYSQLND_STRING_TO_INT_CONVERSION
1716 			  which will make with this `if` an `else if`.
1717 			*/
1718 			if ((perm_bind.is_possibly_blob == TRUE &&
1719 				 fields_metadata[i].charsetnr == MYSQLND_BINARY_CHARSET_NR) ||
1720 				(!as_unicode && perm_bind.can_ret_as_str_in_uni == TRUE))
1721 			{
1722 				/* BLOB - no conversion please */
1723 				ZVAL_STRINGL(*current_field, (char *)p, len, 0);
1724 			} else {
1725 				ZVAL_UTF8_STRINGL(*current_field, (char *)p, len, 0);
1726 			}
1727 #endif
1728 			p += len;
1729 			last_field_was_string = TRUE;
1730 		}
1731 	}
1732 	if (last_field_was_string) {
1733 		/* Normal queries: The buffer has one more byte at the end, because we need it */
1734 		row_buffer->ptr[data_size] = '\0';
1735 	}
1736 
1737 	DBG_RETURN(PASS);
1738 }
1739 /* }}} */
1740 
1741 
1742 /* {{{ php_mysqlnd_rowp_read */
1743 /*
1744   if normal statements => packet->fields is created by this function,
1745   if PS => packet->fields is passed from outside
1746 */
1747 static enum_func_status
php_mysqlnd_rowp_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)1748 php_mysqlnd_rowp_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
1749 {
1750 	MYSQLND_NET * net = conn->net;
1751 	zend_uchar *p;
1752 	enum_func_status ret = PASS;
1753 	size_t old_chunk_size = net->stream->chunk_size;
1754 	MYSQLND_PACKET_ROW *packet= (MYSQLND_PACKET_ROW *) _packet;
1755 	size_t post_alloc_for_bit_fields = 0;
1756 	size_t data_size = 0;
1757 
1758 	DBG_ENTER("php_mysqlnd_rowp_read");
1759 
1760 	if (!packet->binary_protocol && packet->bit_fields_count) {
1761 		/* For every field we need terminating \0 */
1762 		post_alloc_for_bit_fields = packet->bit_fields_total_len + packet->bit_fields_count;
1763 	}
1764 
1765 	ret = php_mysqlnd_read_row_ex(conn, packet->result_set_memory_pool, &packet->row_buffer, &data_size,
1766 								  packet->persistent_alloc, post_alloc_for_bit_fields
1767 								  TSRMLS_CC);
1768 	if (FAIL == ret) {
1769 		goto end;
1770 	}
1771 	MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, packet_type_to_statistic_byte_count[PROT_ROW_PACKET],
1772 										MYSQLND_HEADER_SIZE + packet->header.size,
1773 										packet_type_to_statistic_packet_count[PROT_ROW_PACKET],
1774 										1);
1775 
1776 	/* packet->row_buffer->ptr is of size 'data_size + 1' */
1777 	packet->header.size = data_size;
1778 	packet->row_buffer->app = data_size;
1779 
1780 	if (ERROR_MARKER == (*(p = packet->row_buffer->ptr))) {
1781 		/*
1782 		   Error message as part of the result set,
1783 		   not good but we should not hang. See:
1784 		   Bug #27876 : SF with cyrillic variable name fails during execution
1785 		*/
1786 		ret = FAIL;
1787 		php_mysqlnd_read_error_from_line(p + 1, data_size - 1,
1788 										 packet->error_info.error,
1789 										 sizeof(packet->error_info.error),
1790 										 &packet->error_info.error_no,
1791 										 packet->error_info.sqlstate
1792 										 TSRMLS_CC);
1793 	} else if (EODATA_MARKER == *p && data_size < 8) { /* EOF */
1794 		packet->eof = TRUE;
1795 		p++;
1796 		if (data_size > 1) {
1797 			packet->warning_count = uint2korr(p);
1798 			p += 2;
1799 			packet->server_status = uint2korr(p);
1800 			/* Seems we have 3 bytes reserved for future use */
1801 			DBG_INF_FMT("server_status=%u warning_count=%u", packet->server_status, packet->warning_count);
1802 		}
1803 	} else {
1804 		MYSQLND_INC_CONN_STATISTIC(conn->stats,
1805 									packet->binary_protocol? STAT_ROWS_FETCHED_FROM_SERVER_PS:
1806 															 STAT_ROWS_FETCHED_FROM_SERVER_NORMAL);
1807 
1808 		packet->eof = FALSE;
1809 		/* packet->field_count is set by the user of the packet */
1810 
1811 		if (!packet->skip_extraction) {
1812 			if (!packet->fields) {
1813 				DBG_INF("Allocating packet->fields");
1814 				/*
1815 				  old-API will probably set packet->fields to NULL every time, though for
1816 				  unbuffered sets it makes not much sense as the zvals in this buffer matter,
1817 				  not the buffer. Constantly allocating and deallocating brings nothing.
1818 
1819 				  For PS - if stmt_store() is performed, thus we don't have a cursor, it will
1820 				  behave just like old-API buffered. Cursors will behave like a bit different,
1821 				  but mostly like old-API unbuffered and thus will populate this array with
1822 				  value.
1823 				*/
1824 				packet->fields = (zval **) mnd_pecalloc(packet->field_count, sizeof(zval *),
1825 														packet->persistent_alloc);
1826 			}
1827 		} else {
1828 			MYSQLND_INC_CONN_STATISTIC(conn->stats,
1829 										packet->binary_protocol? STAT_ROWS_SKIPPED_PS:
1830 																 STAT_ROWS_SKIPPED_NORMAL);
1831 		}
1832 	}
1833 
1834 end:
1835 	net->stream->chunk_size = old_chunk_size;
1836 	DBG_RETURN(ret);
1837 }
1838 /* }}} */
1839 
1840 
1841 /* {{{ php_mysqlnd_rowp_free_mem */
1842 static void
php_mysqlnd_rowp_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)1843 php_mysqlnd_rowp_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
1844 {
1845 	MYSQLND_PACKET_ROW *p;
1846 
1847 	DBG_ENTER("php_mysqlnd_rowp_free_mem");
1848 	p = (MYSQLND_PACKET_ROW *) _packet;
1849 	if (p->row_buffer) {
1850 		p->row_buffer->free_chunk(p->row_buffer TSRMLS_CC);
1851 		p->row_buffer = NULL;
1852 	}
1853 	DBG_INF_FMT("stack_allocation=%u persistent=%u", (int)stack_allocation, (int)p->header.persistent);
1854 	/*
1855 	  Don't free packet->fields :
1856 	  - normal queries -> store_result() | fetch_row_unbuffered() will transfer
1857 	    the ownership and NULL it.
1858 	  - PS will pass in it the bound variables, we have to use them! and of course
1859 	    not free the array. As it is passed to us, we should not clean it ourselves.
1860 	*/
1861 	if (!stack_allocation) {
1862 		mnd_pefree(p, p->header.persistent);
1863 	}
1864 	DBG_VOID_RETURN;
1865 }
1866 /* }}} */
1867 
1868 
1869 /* {{{ php_mysqlnd_stats_read */
1870 static enum_func_status
php_mysqlnd_stats_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)1871 php_mysqlnd_stats_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
1872 {
1873 	MYSQLND_PACKET_STATS *packet= (MYSQLND_PACKET_STATS *) _packet;
1874 	size_t buf_len = conn->net->cmd_buffer.length;
1875 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
1876 
1877 	DBG_ENTER("php_mysqlnd_stats_read");
1878 
1879 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "statistics", PROT_STATS_PACKET);
1880 
1881 	packet->message = mnd_emalloc(packet->header.size + 1);
1882 	memcpy(packet->message, buf, packet->header.size);
1883 	packet->message[packet->header.size] = '\0';
1884 	packet->message_len = packet->header.size;
1885 
1886 	DBG_RETURN(PASS);
1887 }
1888 /* }}} */
1889 
1890 
1891 /* {{{ php_mysqlnd_stats_free_mem */
1892 static
php_mysqlnd_stats_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)1893 void php_mysqlnd_stats_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
1894 {
1895 	MYSQLND_PACKET_STATS *p= (MYSQLND_PACKET_STATS *) _packet;
1896 	if (p->message) {
1897 		mnd_efree(p->message);
1898 		p->message = NULL;
1899 	}
1900 	if (!stack_allocation) {
1901 		mnd_pefree(p, p->header.persistent);
1902 	}
1903 }
1904 /* }}} */
1905 
1906 
1907 /* 1 + 4 (id) + 2 (field_c) + 2 (param_c) + 1 (filler) + 2 (warnings ) */
1908 #define PREPARE_RESPONSE_SIZE_41 9
1909 #define PREPARE_RESPONSE_SIZE_50 12
1910 
1911 /* {{{ php_mysqlnd_prepare_read */
1912 static enum_func_status
php_mysqlnd_prepare_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)1913 php_mysqlnd_prepare_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
1914 {
1915 	/* In case of an error, we should have place to put it */
1916 	size_t buf_len = conn->net->cmd_buffer.length;
1917 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
1918 	zend_uchar *p = buf;
1919 	zend_uchar *begin = buf;
1920 	unsigned int data_size;
1921 	MYSQLND_PACKET_PREPARE_RESPONSE *packet= (MYSQLND_PACKET_PREPARE_RESPONSE *) _packet;
1922 
1923 	DBG_ENTER("php_mysqlnd_prepare_read");
1924 
1925 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "prepare", PROT_PREPARE_RESP_PACKET);
1926 	BAIL_IF_NO_MORE_DATA;
1927 
1928 	data_size = packet->header.size;
1929 	packet->error_code = uint1korr(p);
1930 	p++;
1931 	BAIL_IF_NO_MORE_DATA;
1932 
1933 	if (ERROR_MARKER == packet->error_code) {
1934 		php_mysqlnd_read_error_from_line(p, data_size - 1,
1935 										 packet->error_info.error,
1936 										 sizeof(packet->error_info.error),
1937 										 &packet->error_info.error_no,
1938 										 packet->error_info.sqlstate
1939 										 TSRMLS_CC);
1940 		DBG_RETURN(PASS);
1941 	}
1942 
1943 	if (data_size != PREPARE_RESPONSE_SIZE_41 &&
1944 		data_size != PREPARE_RESPONSE_SIZE_50 &&
1945 		!(data_size > PREPARE_RESPONSE_SIZE_50)) {
1946 		DBG_ERR_FMT("Wrong COM_STMT_PREPARE response size. Received %u", data_size);
1947 		php_error(E_WARNING, "Wrong COM_STMT_PREPARE response size. Received %u", data_size);
1948 		DBG_RETURN(FAIL);
1949 	}
1950 
1951 	packet->stmt_id = uint4korr(p);
1952 	p += 4;
1953 	BAIL_IF_NO_MORE_DATA;
1954 
1955 	/* Number of columns in result set */
1956 	packet->field_count = uint2korr(p);
1957 	p += 2;
1958 	BAIL_IF_NO_MORE_DATA;
1959 
1960 	packet->param_count = uint2korr(p);
1961 	p += 2;
1962 	BAIL_IF_NO_MORE_DATA;
1963 
1964 	if (data_size > 9) {
1965 		/* 0x0 filler sent by the server for 5.0+ clients */
1966 		p++;
1967 		BAIL_IF_NO_MORE_DATA;
1968 
1969 		packet->warning_count = uint2korr(p);
1970 	}
1971 
1972 	DBG_INF_FMT("Prepare packet read: stmt_id=%u fields=%u params=%u",
1973 				packet->stmt_id, packet->field_count, packet->param_count);
1974 
1975 	BAIL_IF_NO_MORE_DATA;
1976 
1977 	DBG_RETURN(PASS);
1978 premature_end:
1979 	DBG_ERR_FMT("PREPARE packet %d bytes shorter than expected", p - begin - packet->header.size);
1980 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "PREPARE packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
1981 					 p - begin - packet->header.size);
1982 	DBG_RETURN(FAIL);
1983 }
1984 /* }}} */
1985 
1986 
1987 /* {{{ php_mysqlnd_prepare_free_mem */
1988 static void
php_mysqlnd_prepare_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)1989 php_mysqlnd_prepare_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
1990 {
1991 	MYSQLND_PACKET_PREPARE_RESPONSE *p= (MYSQLND_PACKET_PREPARE_RESPONSE *) _packet;
1992 	if (!stack_allocation) {
1993 		mnd_pefree(p, p->header.persistent);
1994 	}
1995 }
1996 /* }}} */
1997 
1998 
1999 /* {{{ php_mysqlnd_chg_user_read */
2000 static enum_func_status
php_mysqlnd_chg_user_read(void * _packet,MYSQLND_CONN_DATA * conn TSRMLS_DC)2001 php_mysqlnd_chg_user_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC)
2002 {
2003 	/* There could be an error message */
2004 	size_t buf_len = conn->net->cmd_buffer.length;
2005 	zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer;
2006 	zend_uchar *p = buf;
2007 	zend_uchar *begin = buf;
2008 	MYSQLND_PACKET_CHG_USER_RESPONSE *packet= (MYSQLND_PACKET_CHG_USER_RESPONSE *) _packet;
2009 
2010 	DBG_ENTER("php_mysqlnd_chg_user_read");
2011 
2012 	PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "change user response", PROT_CHG_USER_RESP_PACKET);
2013 	BAIL_IF_NO_MORE_DATA;
2014 
2015 	/*
2016 	  Don't increment. First byte is ERROR_MARKER on error, but otherwise is starting byte
2017 	  of encoded sequence for length.
2018 	*/
2019 
2020 	/* Should be always 0x0 or ERROR_MARKER for error */
2021 	packet->response_code = uint1korr(p);
2022 	p++;
2023 
2024 	if (packet->header.size == 1 && buf[0] == EODATA_MARKER && packet->server_capabilities & CLIENT_SECURE_CONNECTION) {
2025 		/* We don't handle 3.23 authentication */
2026 		packet->server_asked_323_auth = TRUE;
2027 		DBG_RETURN(FAIL);
2028 	}
2029 
2030 	if (ERROR_MARKER == packet->response_code) {
2031 		php_mysqlnd_read_error_from_line(p, packet->header.size - 1,
2032 										 packet->error_info.error,
2033 										 sizeof(packet->error_info.error),
2034 										 &packet->error_info.error_no,
2035 										 packet->error_info.sqlstate
2036 										 TSRMLS_CC);
2037 	}
2038 	BAIL_IF_NO_MORE_DATA;
2039 	if (packet->response_code == 0xFE && packet->header.size > (size_t) (p - buf)) {
2040 		packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE);
2041 		packet->new_auth_protocol_len = strlen(packet->new_auth_protocol);
2042 		p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */
2043 		packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf);
2044 		if (packet->new_auth_protocol_data_len) {
2045 			packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len);
2046 			memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len);
2047 		}
2048 		DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol);
2049 		DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data);
2050 	}
2051 
2052 	DBG_RETURN(PASS);
2053 premature_end:
2054 	DBG_ERR_FMT("CHANGE_USER packet %d bytes shorter than expected", p - begin - packet->header.size);
2055 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "CHANGE_USER packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected",
2056 						 p - begin - packet->header.size);
2057 	DBG_RETURN(FAIL);
2058 }
2059 /* }}} */
2060 
2061 
2062 /* {{{ php_mysqlnd_chg_user_free_mem */
2063 static void
php_mysqlnd_chg_user_free_mem(void * _packet,zend_bool stack_allocation TSRMLS_DC)2064 php_mysqlnd_chg_user_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC)
2065 {
2066 	MYSQLND_PACKET_CHG_USER_RESPONSE * p = (MYSQLND_PACKET_CHG_USER_RESPONSE *) _packet;
2067 
2068 	if (p->new_auth_protocol) {
2069 		mnd_efree(p->new_auth_protocol);
2070 		p->new_auth_protocol = NULL;
2071 	}
2072 	p->new_auth_protocol_len = 0;
2073 
2074 	if (p->new_auth_protocol_data) {
2075 		mnd_efree(p->new_auth_protocol_data);
2076 		p->new_auth_protocol_data = NULL;
2077 	}
2078 	p->new_auth_protocol_data_len = 0;
2079 
2080 	if (!stack_allocation) {
2081 		mnd_pefree(p, p->header.persistent);
2082 	}
2083 }
2084 /* }}} */
2085 
2086 
2087 /* {{{ packet_methods */
2088 static
2089 mysqlnd_packet_methods packet_methods[PROT_LAST] =
2090 {
2091 	{
2092 		sizeof(MYSQLND_PACKET_GREET),
2093 		php_mysqlnd_greet_read,
2094 		NULL, /* write */
2095 		php_mysqlnd_greet_free_mem,
2096 	}, /* PROT_GREET_PACKET */
2097 	{
2098 		sizeof(MYSQLND_PACKET_AUTH),
2099 		NULL, /* read */
2100 		php_mysqlnd_auth_write,
2101 		php_mysqlnd_auth_free_mem,
2102 	}, /* PROT_AUTH_PACKET */
2103 	{
2104 		sizeof(MYSQLND_PACKET_AUTH_RESPONSE),
2105 		php_mysqlnd_auth_response_read, /* read */
2106 		NULL, /* write */
2107 		php_mysqlnd_auth_response_free_mem,
2108 	}, /* PROT_AUTH_RESP_PACKET */
2109 	{
2110 		sizeof(MYSQLND_PACKET_CHANGE_AUTH_RESPONSE),
2111 		NULL, /* read */
2112 		php_mysqlnd_change_auth_response_write, /* write */
2113 		php_mysqlnd_change_auth_response_free_mem,
2114 	}, /* PROT_CHANGE_AUTH_RESP_PACKET */
2115 	{
2116 		sizeof(MYSQLND_PACKET_OK),
2117 		php_mysqlnd_ok_read, /* read */
2118 		NULL, /* write */
2119 		php_mysqlnd_ok_free_mem,
2120 	}, /* PROT_OK_PACKET */
2121 	{
2122 		sizeof(MYSQLND_PACKET_EOF),
2123 		php_mysqlnd_eof_read, /* read */
2124 		NULL, /* write */
2125 		php_mysqlnd_eof_free_mem,
2126 	}, /* PROT_EOF_PACKET */
2127 	{
2128 		sizeof(MYSQLND_PACKET_COMMAND),
2129 		NULL, /* read */
2130 		php_mysqlnd_cmd_write, /* write */
2131 		php_mysqlnd_cmd_free_mem,
2132 	}, /* PROT_CMD_PACKET */
2133 	{
2134 		sizeof(MYSQLND_PACKET_RSET_HEADER),
2135 		php_mysqlnd_rset_header_read, /* read */
2136 		NULL, /* write */
2137 		php_mysqlnd_rset_header_free_mem,
2138 	}, /* PROT_RSET_HEADER_PACKET */
2139 	{
2140 		sizeof(MYSQLND_PACKET_RES_FIELD),
2141 		php_mysqlnd_rset_field_read, /* read */
2142 		NULL, /* write */
2143 		php_mysqlnd_rset_field_free_mem,
2144 	}, /* PROT_RSET_FLD_PACKET */
2145 	{
2146 		sizeof(MYSQLND_PACKET_ROW),
2147 		php_mysqlnd_rowp_read, /* read */
2148 		NULL, /* write */
2149 		php_mysqlnd_rowp_free_mem,
2150 	}, /* PROT_ROW_PACKET */
2151 	{
2152 		sizeof(MYSQLND_PACKET_STATS),
2153 		php_mysqlnd_stats_read, /* read */
2154 		NULL, /* write */
2155 		php_mysqlnd_stats_free_mem,
2156 	}, /* PROT_STATS_PACKET */
2157 	{
2158 		sizeof(MYSQLND_PACKET_PREPARE_RESPONSE),
2159 		php_mysqlnd_prepare_read, /* read */
2160 		NULL, /* write */
2161 		php_mysqlnd_prepare_free_mem,
2162 	}, /* PROT_PREPARE_RESP_PACKET */
2163 	{
2164 		sizeof(MYSQLND_PACKET_CHG_USER_RESPONSE),
2165 		php_mysqlnd_chg_user_read, /* read */
2166 		NULL, /* write */
2167 		php_mysqlnd_chg_user_free_mem,
2168 	} /* PROT_CHG_USER_RESP_PACKET */
2169 };
2170 /* }}} */
2171 
2172 
2173 /* {{{ mysqlnd_protocol::get_greet_packet */
2174 static struct st_mysqlnd_packet_greet *
MYSQLND_METHOD(mysqlnd_protocol,get_greet_packet)2175 MYSQLND_METHOD(mysqlnd_protocol, get_greet_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2176 {
2177 	struct st_mysqlnd_packet_greet * packet = mnd_pecalloc(1, packet_methods[PROT_GREET_PACKET].struct_size, persistent);
2178 	DBG_ENTER("mysqlnd_protocol::get_greet_packet");
2179 	if (packet) {
2180 		packet->header.m = &packet_methods[PROT_GREET_PACKET];
2181 		packet->header.persistent = persistent;
2182 	}
2183 	DBG_RETURN(packet);
2184 }
2185 /* }}} */
2186 
2187 
2188 /* {{{ mysqlnd_protocol::get_auth_packet */
2189 static struct st_mysqlnd_packet_auth *
MYSQLND_METHOD(mysqlnd_protocol,get_auth_packet)2190 MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2191 {
2192 	struct st_mysqlnd_packet_auth * packet = mnd_pecalloc(1, packet_methods[PROT_AUTH_PACKET].struct_size, persistent);
2193 	DBG_ENTER("mysqlnd_protocol::get_auth_packet");
2194 	if (packet) {
2195 		packet->header.m = &packet_methods[PROT_AUTH_PACKET];
2196 		packet->header.persistent = persistent;
2197 	}
2198 	DBG_RETURN(packet);
2199 }
2200 /* }}} */
2201 
2202 
2203 /* {{{ mysqlnd_protocol::get_auth_response_packet */
2204 static struct st_mysqlnd_packet_auth_response *
MYSQLND_METHOD(mysqlnd_protocol,get_auth_response_packet)2205 MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2206 {
2207 	struct st_mysqlnd_packet_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_AUTH_RESP_PACKET].struct_size, persistent);
2208 	DBG_ENTER("mysqlnd_protocol::get_auth_response_packet");
2209 	if (packet) {
2210 		packet->header.m = &packet_methods[PROT_AUTH_RESP_PACKET];
2211 		packet->header.persistent = persistent;
2212 	}
2213 	DBG_RETURN(packet);
2214 }
2215 /* }}} */
2216 
2217 
2218 /* {{{ mysqlnd_protocol::get_change_auth_response_packet */
2219 static struct st_mysqlnd_packet_change_auth_response *
MYSQLND_METHOD(mysqlnd_protocol,get_change_auth_response_packet)2220 MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2221 {
2222 	struct st_mysqlnd_packet_change_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_CHANGE_AUTH_RESP_PACKET].struct_size, persistent);
2223 	DBG_ENTER("mysqlnd_protocol::get_change_auth_response_packet");
2224 	if (packet) {
2225 		packet->header.m = &packet_methods[PROT_CHANGE_AUTH_RESP_PACKET];
2226 		packet->header.persistent = persistent;
2227 	}
2228 	DBG_RETURN(packet);
2229 }
2230 /* }}} */
2231 
2232 
2233 /* {{{ mysqlnd_protocol::get_ok_packet */
2234 static struct st_mysqlnd_packet_ok *
MYSQLND_METHOD(mysqlnd_protocol,get_ok_packet)2235 MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2236 {
2237 	struct st_mysqlnd_packet_ok * packet = mnd_pecalloc(1, packet_methods[PROT_OK_PACKET].struct_size, persistent);
2238 	DBG_ENTER("mysqlnd_protocol::get_ok_packet");
2239 	if (packet) {
2240 		packet->header.m = &packet_methods[PROT_OK_PACKET];
2241 		packet->header.persistent = persistent;
2242 	}
2243 	DBG_RETURN(packet);
2244 }
2245 /* }}} */
2246 
2247 
2248 /* {{{ mysqlnd_protocol::get_eof_packet */
2249 static struct st_mysqlnd_packet_eof *
MYSQLND_METHOD(mysqlnd_protocol,get_eof_packet)2250 MYSQLND_METHOD(mysqlnd_protocol, get_eof_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2251 {
2252 	struct st_mysqlnd_packet_eof * packet = mnd_pecalloc(1, packet_methods[PROT_EOF_PACKET].struct_size, persistent);
2253 	DBG_ENTER("mysqlnd_protocol::get_eof_packet");
2254 	if (packet) {
2255 		packet->header.m = &packet_methods[PROT_EOF_PACKET];
2256 		packet->header.persistent = persistent;
2257 	}
2258 	DBG_RETURN(packet);
2259 }
2260 /* }}} */
2261 
2262 
2263 /* {{{ mysqlnd_protocol::get_command_packet */
2264 static struct st_mysqlnd_packet_command *
MYSQLND_METHOD(mysqlnd_protocol,get_command_packet)2265 MYSQLND_METHOD(mysqlnd_protocol, get_command_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2266 {
2267 	struct st_mysqlnd_packet_command * packet = mnd_pecalloc(1, packet_methods[PROT_CMD_PACKET].struct_size, persistent);
2268 	DBG_ENTER("mysqlnd_protocol::get_command_packet");
2269 	if (packet) {
2270 		packet->header.m = &packet_methods[PROT_CMD_PACKET];
2271 		packet->header.persistent = persistent;
2272 	}
2273 	DBG_RETURN(packet);
2274 }
2275 /* }}} */
2276 
2277 
2278 /* {{{ mysqlnd_protocol::get_rset_packet */
2279 static struct st_mysqlnd_packet_rset_header *
MYSQLND_METHOD(mysqlnd_protocol,get_rset_header_packet)2280 MYSQLND_METHOD(mysqlnd_protocol, get_rset_header_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2281 {
2282 	struct st_mysqlnd_packet_rset_header * packet = mnd_pecalloc(1, packet_methods[PROT_RSET_HEADER_PACKET].struct_size, persistent);
2283 	DBG_ENTER("mysqlnd_protocol::get_rset_header_packet");
2284 	if (packet) {
2285 		packet->header.m = &packet_methods[PROT_RSET_HEADER_PACKET];
2286 		packet->header.persistent = persistent;
2287 	}
2288 	DBG_RETURN(packet);
2289 }
2290 /* }}} */
2291 
2292 
2293 /* {{{ mysqlnd_protocol::get_result_field_packet */
2294 static struct st_mysqlnd_packet_res_field *
MYSQLND_METHOD(mysqlnd_protocol,get_result_field_packet)2295 MYSQLND_METHOD(mysqlnd_protocol, get_result_field_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2296 {
2297 	struct st_mysqlnd_packet_res_field * packet = mnd_pecalloc(1, packet_methods[PROT_RSET_FLD_PACKET].struct_size, persistent);
2298 	DBG_ENTER("mysqlnd_protocol::get_result_field_packet");
2299 	if (packet) {
2300 		packet->header.m = &packet_methods[PROT_RSET_FLD_PACKET];
2301 		packet->header.persistent = persistent;
2302 	}
2303 	DBG_RETURN(packet);
2304 }
2305 /* }}} */
2306 
2307 
2308 /* {{{ mysqlnd_protocol::get_row_packet */
2309 static struct st_mysqlnd_packet_row *
MYSQLND_METHOD(mysqlnd_protocol,get_row_packet)2310 MYSQLND_METHOD(mysqlnd_protocol, get_row_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2311 {
2312 	struct st_mysqlnd_packet_row * packet = mnd_pecalloc(1, packet_methods[PROT_ROW_PACKET].struct_size, persistent);
2313 	DBG_ENTER("mysqlnd_protocol::get_row_packet");
2314 	if (packet) {
2315 		packet->header.m = &packet_methods[PROT_ROW_PACKET];
2316 		packet->header.persistent = persistent;
2317 	}
2318 	DBG_RETURN(packet);
2319 }
2320 /* }}} */
2321 
2322 
2323 /* {{{ mysqlnd_protocol::get_stats_packet */
2324 static struct st_mysqlnd_packet_stats *
MYSQLND_METHOD(mysqlnd_protocol,get_stats_packet)2325 MYSQLND_METHOD(mysqlnd_protocol, get_stats_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2326 {
2327 	struct st_mysqlnd_packet_stats * packet = mnd_pecalloc(1, packet_methods[PROT_STATS_PACKET].struct_size, persistent);
2328 	DBG_ENTER("mysqlnd_protocol::get_stats_packet");
2329 	if (packet) {
2330 		packet->header.m = &packet_methods[PROT_STATS_PACKET];
2331 		packet->header.persistent = persistent;
2332 	}
2333 	DBG_RETURN(packet);
2334 }
2335 /* }}} */
2336 
2337 
2338 /* {{{ mysqlnd_protocol::get_prepare_response_packet */
2339 static struct st_mysqlnd_packet_prepare_response *
MYSQLND_METHOD(mysqlnd_protocol,get_prepare_response_packet)2340 MYSQLND_METHOD(mysqlnd_protocol, get_prepare_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2341 {
2342 	struct st_mysqlnd_packet_prepare_response * packet = mnd_pecalloc(1, packet_methods[PROT_PREPARE_RESP_PACKET].struct_size, persistent);
2343 	DBG_ENTER("mysqlnd_protocol::get_prepare_response_packet");
2344 	if (packet) {
2345 		packet->header.m = &packet_methods[PROT_PREPARE_RESP_PACKET];
2346 		packet->header.persistent = persistent;
2347 	}
2348 	DBG_RETURN(packet);
2349 }
2350 /* }}} */
2351 
2352 
2353 /* {{{ mysqlnd_protocol::get_change_user_response_packet */
2354 static struct st_mysqlnd_packet_chg_user_resp*
MYSQLND_METHOD(mysqlnd_protocol,get_change_user_response_packet)2355 MYSQLND_METHOD(mysqlnd_protocol, get_change_user_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC)
2356 {
2357 	struct st_mysqlnd_packet_chg_user_resp * packet = mnd_pecalloc(1, packet_methods[PROT_CHG_USER_RESP_PACKET].struct_size, persistent);
2358 	DBG_ENTER("mysqlnd_protocol::get_change_user_response_packet");
2359 	if (packet) {
2360 		packet->header.m = &packet_methods[PROT_CHG_USER_RESP_PACKET];
2361 		packet->header.persistent = persistent;
2362 	}
2363 	DBG_RETURN(packet);
2364 }
2365 /* }}} */
2366 
2367 
2368 MYSQLND_CLASS_METHODS_START(mysqlnd_protocol)
2369 	MYSQLND_METHOD(mysqlnd_protocol, get_greet_packet),
2370 	MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet),
2371 	MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet),
2372 	MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet),
2373 	MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet),
2374 	MYSQLND_METHOD(mysqlnd_protocol, get_command_packet),
2375 	MYSQLND_METHOD(mysqlnd_protocol, get_eof_packet),
2376 	MYSQLND_METHOD(mysqlnd_protocol, get_rset_header_packet),
2377 	MYSQLND_METHOD(mysqlnd_protocol, get_result_field_packet),
2378 	MYSQLND_METHOD(mysqlnd_protocol, get_row_packet),
2379 	MYSQLND_METHOD(mysqlnd_protocol, get_stats_packet),
2380 	MYSQLND_METHOD(mysqlnd_protocol, get_prepare_response_packet),
2381 	MYSQLND_METHOD(mysqlnd_protocol, get_change_user_response_packet)
2382 MYSQLND_CLASS_METHODS_END;
2383 
2384 
2385 /* {{{ mysqlnd_protocol_init */
2386 PHPAPI MYSQLND_PROTOCOL *
mysqlnd_protocol_init(zend_bool persistent TSRMLS_DC)2387 mysqlnd_protocol_init(zend_bool persistent TSRMLS_DC)
2388 {
2389 	MYSQLND_PROTOCOL * ret;
2390 	DBG_ENTER("mysqlnd_protocol_init");
2391 	ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_protocol_decoder(persistent TSRMLS_CC);
2392 	DBG_RETURN(ret);
2393 }
2394 /* }}} */
2395 
2396 
2397 /* {{{ mysqlnd_protocol_free */
2398 PHPAPI void
mysqlnd_protocol_free(MYSQLND_PROTOCOL * const protocol TSRMLS_DC)2399 mysqlnd_protocol_free(MYSQLND_PROTOCOL * const protocol TSRMLS_DC)
2400 {
2401 	DBG_ENTER("mysqlnd_protocol_free");
2402 
2403 	if (protocol) {
2404 		zend_bool pers = protocol->persistent;
2405 		mnd_pefree(protocol, pers);
2406 	}
2407 	DBG_VOID_RETURN;
2408 }
2409 /* }}} */
2410 
2411 
2412 /*
2413  * Local variables:
2414  * tab-width: 4
2415  * c-basic-offset: 4
2416  * End:
2417  * vim600: noet sw=4 ts=4 fdm=marker
2418  * vim<600: noet sw=4 ts=4
2419  */
2420