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