xref: /PHP-7.4/ext/mysqlnd/mysqlnd_auth.c (revision 36466042)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Andrey Hristov <andrey@php.net>                             |
16   |          Ulf Wendel <uw@php.net>                                     |
17   +----------------------------------------------------------------------+
18 */
19 
20 #include "php.h"
21 #include "mysqlnd.h"
22 #include "mysqlnd_structs.h"
23 #include "mysqlnd_auth.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_connection.h"
26 #include "mysqlnd_priv.h"
27 #include "mysqlnd_charset.h"
28 #include "mysqlnd_debug.h"
29 
30 static const char * const mysqlnd_old_passwd  = "mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication. "
31 "Please use an administration tool to reset your password with the command SET PASSWORD = PASSWORD('your_existing_password'). This will "
32 "store a new, and more secure, hash value in mysql.user. If this user is used in other scripts executed by PHP 5.2 or earlier you might need to remove the old-passwords "
33 "flag from your my.cnf file";
34 
35 
36 /* {{{ mysqlnd_run_authentication */
37 enum_func_status
mysqlnd_run_authentication(MYSQLND_CONN_DATA * const conn,const char * const user,const char * const passwd,const size_t passwd_len,const char * const db,const size_t db_len,const MYSQLND_STRING auth_plugin_data,const char * const auth_protocol,const unsigned int charset_no,const MYSQLND_SESSION_OPTIONS * const session_options,const zend_ulong mysql_flags,const zend_bool silent,const zend_bool is_change_user)38 mysqlnd_run_authentication(
39 			MYSQLND_CONN_DATA * const conn,
40 			const char * const user,
41 			const char * const passwd,
42 			const size_t passwd_len,
43 			const char * const db,
44 			const size_t db_len,
45 			const MYSQLND_STRING auth_plugin_data,
46 			const char * const auth_protocol,
47 			const unsigned int charset_no,
48 			const MYSQLND_SESSION_OPTIONS * const session_options,
49 			const zend_ulong mysql_flags,
50 			const zend_bool silent,
51 			const zend_bool is_change_user
52 			)
53 {
54 	enum_func_status ret = FAIL;
55 	zend_bool first_call = TRUE;
56 
57 	char * switch_to_auth_protocol = NULL;
58 	size_t switch_to_auth_protocol_len = 0;
59 	char * requested_protocol = NULL;
60 	zend_uchar * plugin_data;
61 	size_t plugin_data_len;
62 
63 	DBG_ENTER("mysqlnd_run_authentication");
64 
65 	plugin_data_len = auth_plugin_data.l;
66 	plugin_data = mnd_emalloc(plugin_data_len + 1);
67 	if (!plugin_data) {
68 		goto end;
69 	}
70 	memcpy(plugin_data, auth_plugin_data.s, plugin_data_len);
71 	plugin_data[plugin_data_len] = '\0';
72 
73 	requested_protocol = mnd_pestrdup(auth_protocol? auth_protocol : MYSQLND_DEFAULT_AUTH_PROTOCOL, FALSE);
74 	if (!requested_protocol) {
75 		goto end;
76 	}
77 
78 	do {
79 		struct st_mysqlnd_authentication_plugin * auth_plugin = conn->m->fetch_auth_plugin_by_name(requested_protocol);
80 
81 		if (!auth_plugin) {
82 			if (first_call) {
83 				mnd_pefree(requested_protocol, FALSE);
84 				requested_protocol = mnd_pestrdup(MYSQLND_DEFAULT_AUTH_PROTOCOL, FALSE);
85 			} else {
86 				php_error_docref(NULL, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol);
87 				SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method unknown to the client");
88 				goto end;
89 			}
90 		}
91 
92 
93 		{
94 			zend_uchar * switch_to_auth_protocol_data = NULL;
95 			size_t switch_to_auth_protocol_data_len = 0;
96 			zend_uchar * scrambled_data = NULL;
97 			size_t scrambled_data_len = 0;
98 
99 			switch_to_auth_protocol = NULL;
100 			switch_to_auth_protocol_len = 0;
101 
102 			if (conn->authentication_plugin_data.s) {
103 				mnd_pefree(conn->authentication_plugin_data.s, conn->persistent);
104 				conn->authentication_plugin_data.s = NULL;
105 			}
106 			conn->authentication_plugin_data.l = plugin_data_len;
107 			conn->authentication_plugin_data.s = mnd_pemalloc(conn->authentication_plugin_data.l, conn->persistent);
108 			if (!conn->authentication_plugin_data.s) {
109 				SET_OOM_ERROR(conn->error_info);
110 				goto end;
111 			}
112 			memcpy(conn->authentication_plugin_data.s, plugin_data, plugin_data_len);
113 
114 			DBG_INF_FMT("salt(%d)=[%.*s]", plugin_data_len, plugin_data_len, plugin_data);
115 			/* The data should be allocated with malloc() */
116 			if (auth_plugin) {
117 				scrambled_data = auth_plugin->methods.get_auth_data(
118 					NULL, &scrambled_data_len, conn, user, passwd,
119 					passwd_len, plugin_data, plugin_data_len,
120 					session_options, conn->protocol_frame_codec->data,
121 					mysql_flags);
122 			}
123 
124 			if (conn->error_info->error_no) {
125 				goto end;
126 			}
127 			if (FALSE == is_change_user) {
128 				ret = mysqlnd_auth_handshake(conn, user, passwd, passwd_len, db, db_len, session_options, mysql_flags,
129 											charset_no,
130 											first_call,
131 											requested_protocol,
132 											auth_plugin, plugin_data, plugin_data_len,
133 											scrambled_data, scrambled_data_len,
134 											&switch_to_auth_protocol, &switch_to_auth_protocol_len,
135 											&switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
136 											);
137 			} else {
138 				ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, passwd_len, db, db_len, silent,
139 											   first_call,
140 											   requested_protocol,
141 											   auth_plugin, plugin_data, plugin_data_len,
142 											   scrambled_data, scrambled_data_len,
143 											   &switch_to_auth_protocol, &switch_to_auth_protocol_len,
144 											   &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
145 											  );
146 			}
147 			first_call = FALSE;
148 			free(scrambled_data);
149 
150 			DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a");
151 			if (requested_protocol && switch_to_auth_protocol) {
152 				mnd_efree(requested_protocol);
153 				requested_protocol = switch_to_auth_protocol;
154 			}
155 
156 			if (plugin_data) {
157 				mnd_efree(plugin_data);
158 			}
159 			plugin_data_len = switch_to_auth_protocol_data_len;
160 			plugin_data = switch_to_auth_protocol_data;
161 		}
162 		DBG_INF_FMT("conn->error_info->error_no = %d", conn->error_info->error_no);
163 	} while (ret == FAIL && conn->error_info->error_no == 0 && switch_to_auth_protocol != NULL);
164 
165 	if (ret == PASS) {
166 		DBG_INF_FMT("saving requested_protocol=%s", requested_protocol);
167 		conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol);
168 	}
169 end:
170 	if (plugin_data) {
171 		mnd_efree(plugin_data);
172 	}
173 	if (requested_protocol) {
174 		mnd_efree(requested_protocol);
175 	}
176 
177 	DBG_RETURN(ret);
178 }
179 /* }}} */
180 
181 
182 /* {{{ mysqlnd_switch_to_ssl_if_needed */
183 static enum_func_status
mysqlnd_switch_to_ssl_if_needed(MYSQLND_CONN_DATA * const conn,unsigned int charset_no,const size_t server_capabilities,const MYSQLND_SESSION_OPTIONS * const session_options,const zend_ulong mysql_flags)184 mysqlnd_switch_to_ssl_if_needed(MYSQLND_CONN_DATA * const conn,
185 								unsigned int charset_no,
186 								const size_t server_capabilities,
187 								const MYSQLND_SESSION_OPTIONS * const session_options,
188 								const zend_ulong mysql_flags)
189 {
190 	enum_func_status ret = FAIL;
191 	const MYSQLND_CHARSET * charset;
192 	DBG_ENTER("mysqlnd_switch_to_ssl_if_needed");
193 
194 	if (session_options->charset_name && (charset = mysqlnd_find_charset_name(session_options->charset_name))) {
195 		charset_no = charset->nr;
196 	}
197 
198 	{
199 		const size_t client_capabilities = mysql_flags;
200 		ret = conn->command->enable_ssl(conn, client_capabilities, server_capabilities, charset_no);
201 	}
202 	DBG_RETURN(ret);
203 }
204 /* }}} */
205 
206 
207 /* {{{ mysqlnd_connect_run_authentication */
208 enum_func_status
mysqlnd_connect_run_authentication(MYSQLND_CONN_DATA * const conn,const char * const user,const char * const passwd,const char * const db,const size_t db_len,const size_t passwd_len,const MYSQLND_STRING authentication_plugin_data,const char * const authentication_protocol,const unsigned int charset_no,const size_t server_capabilities,const MYSQLND_SESSION_OPTIONS * const session_options,const zend_ulong mysql_flags)209 mysqlnd_connect_run_authentication(
210 			MYSQLND_CONN_DATA * const conn,
211 			const char * const user,
212 			const char * const passwd,
213 			const char * const db,
214 			const size_t db_len,
215 			const size_t passwd_len,
216 			const MYSQLND_STRING authentication_plugin_data,
217 			const char * const authentication_protocol,
218 			const unsigned int charset_no,
219 			const size_t server_capabilities,
220 			const MYSQLND_SESSION_OPTIONS * const session_options,
221 			const zend_ulong mysql_flags
222 			)
223 {
224 	enum_func_status ret = FAIL;
225 	DBG_ENTER("mysqlnd_connect_run_authentication");
226 
227 	ret = mysqlnd_switch_to_ssl_if_needed(conn, charset_no, server_capabilities, session_options, mysql_flags);
228 	if (PASS == ret) {
229 		ret = mysqlnd_run_authentication(conn, user, passwd, passwd_len, db, db_len,
230 										 authentication_plugin_data, authentication_protocol,
231 										 charset_no, session_options, mysql_flags, FALSE /*silent*/, FALSE/*is_change*/);
232 	}
233 	DBG_RETURN(ret);
234 }
235 /* }}} */
236 
237 
238 /* {{{ mysqlnd_auth_handshake */
239 enum_func_status
mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,const char * const user,const char * const passwd,const size_t passwd_len,const char * const db,const size_t db_len,const MYSQLND_SESSION_OPTIONS * const session_options,const zend_ulong mysql_flags,const unsigned int server_charset_no,const zend_bool use_full_blown_auth_packet,const char * const auth_protocol,struct st_mysqlnd_authentication_plugin * auth_plugin,const zend_uchar * const orig_auth_plugin_data,const size_t orig_auth_plugin_data_len,const zend_uchar * const auth_plugin_data,const size_t auth_plugin_data_len,char ** switch_to_auth_protocol,size_t * const switch_to_auth_protocol_len,zend_uchar ** switch_to_auth_protocol_data,size_t * const switch_to_auth_protocol_data_len)240 mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
241 							  const char * const user,
242 							  const char * const passwd,
243 							  const size_t passwd_len,
244 							  const char * const db,
245 							  const size_t db_len,
246 							  const MYSQLND_SESSION_OPTIONS * const session_options,
247 							  const zend_ulong mysql_flags,
248 							  const unsigned int server_charset_no,
249 							  const zend_bool use_full_blown_auth_packet,
250 							  const char * const auth_protocol,
251 							  struct st_mysqlnd_authentication_plugin * auth_plugin,
252 							  const zend_uchar * const orig_auth_plugin_data,
253 							  const size_t orig_auth_plugin_data_len,
254 							  const zend_uchar * const auth_plugin_data,
255 							  const size_t auth_plugin_data_len,
256 							  char ** switch_to_auth_protocol,
257 							  size_t * const switch_to_auth_protocol_len,
258 							  zend_uchar ** switch_to_auth_protocol_data,
259 							  size_t * const switch_to_auth_protocol_data_len
260 							 )
261 {
262 	enum_func_status ret = FAIL;
263 	const MYSQLND_CHARSET * charset = NULL;
264 	MYSQLND_PACKET_AUTH_RESPONSE auth_resp_packet;
265 
266 	DBG_ENTER("mysqlnd_auth_handshake");
267 
268 	conn->payload_decoder_factory->m.init_auth_response_packet(&auth_resp_packet);
269 
270 	if (use_full_blown_auth_packet != TRUE) {
271 		MYSQLND_PACKET_CHANGE_AUTH_RESPONSE change_auth_resp_packet;
272 
273 		conn->payload_decoder_factory->m.init_change_auth_response_packet(&change_auth_resp_packet);
274 
275 		change_auth_resp_packet.auth_data = auth_plugin_data;
276 		change_auth_resp_packet.auth_data_len = auth_plugin_data_len;
277 
278 		if (!PACKET_WRITE(conn, &change_auth_resp_packet)) {
279 			SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
280 			SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
281 			PACKET_FREE(&change_auth_resp_packet);
282 			goto end;
283 		}
284 		PACKET_FREE(&change_auth_resp_packet);
285 	} else {
286 		MYSQLND_PACKET_AUTH auth_packet;
287 
288 		conn->payload_decoder_factory->m.init_auth_packet(&auth_packet);
289 
290 		auth_packet.client_flags = mysql_flags;
291 		auth_packet.max_packet_size = session_options->max_allowed_packet;
292 		if (session_options->charset_name && (charset = mysqlnd_find_charset_name(session_options->charset_name))) {
293 			auth_packet.charset_no	= charset->nr;
294 		} else {
295 			auth_packet.charset_no	= server_charset_no;
296 		}
297 
298 		auth_packet.send_auth_data = TRUE;
299 		auth_packet.user		= user;
300 		auth_packet.db			= db;
301 		auth_packet.db_len		= db_len;
302 
303 		auth_packet.auth_data = auth_plugin_data;
304 		auth_packet.auth_data_len = auth_plugin_data_len;
305 		auth_packet.auth_plugin_name = auth_protocol;
306 
307 		if (conn->server_capabilities & CLIENT_CONNECT_ATTRS) {
308 			auth_packet.connect_attr = conn->options->connect_attr;
309 		}
310 
311 		if (!PACKET_WRITE(conn, &auth_packet)) {
312 			PACKET_FREE(&auth_packet);
313 			goto end;
314 		}
315 
316 		if (use_full_blown_auth_packet == TRUE) {
317 			conn->charset = mysqlnd_find_charset_nr(auth_packet.charset_no);
318 		}
319 
320 		PACKET_FREE(&auth_packet);
321 	}
322 
323 	if (auth_plugin && auth_plugin->methods.handle_server_response) {
324 		if (FAIL == auth_plugin->methods.handle_server_response(auth_plugin, conn,
325 				orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len,
326 				switch_to_auth_protocol, switch_to_auth_protocol_len,
327 				switch_to_auth_protocol_data, switch_to_auth_protocol_data_len)) {
328 			goto end;
329 		}
330 	}
331 
332 	if (FAIL == PACKET_READ(conn, &auth_resp_packet) || auth_resp_packet.response_code >= 0xFE) {
333 		if (auth_resp_packet.response_code == 0xFE) {
334 			/* old authentication with new server  !*/
335 			if (!auth_resp_packet.new_auth_protocol) {
336 				DBG_ERR(mysqlnd_old_passwd);
337 				SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
338 			} else {
339 				*switch_to_auth_protocol = mnd_pestrndup(auth_resp_packet.new_auth_protocol, auth_resp_packet.new_auth_protocol_len, FALSE);
340 				*switch_to_auth_protocol_len = auth_resp_packet.new_auth_protocol_len;
341 				if (auth_resp_packet.new_auth_protocol_data) {
342 					*switch_to_auth_protocol_data_len = auth_resp_packet.new_auth_protocol_data_len;
343 					*switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
344 					memcpy(*switch_to_auth_protocol_data, auth_resp_packet.new_auth_protocol_data, *switch_to_auth_protocol_data_len);
345 				} else {
346 					*switch_to_auth_protocol_data = NULL;
347 					*switch_to_auth_protocol_data_len = 0;
348 				}
349 			}
350 		} else if (auth_resp_packet.response_code == 0xFF) {
351 			if (auth_resp_packet.sqlstate[0]) {
352 				strlcpy(conn->error_info->sqlstate, auth_resp_packet.sqlstate, sizeof(conn->error_info->sqlstate));
353 				DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", auth_resp_packet.error_no, auth_resp_packet.sqlstate, auth_resp_packet.error);
354 			}
355 			SET_CLIENT_ERROR(conn->error_info, auth_resp_packet.error_no, UNKNOWN_SQLSTATE, auth_resp_packet.error);
356 		}
357 		goto end;
358 	}
359 
360 	SET_NEW_MESSAGE(conn->last_message.s, conn->last_message.l, auth_resp_packet.message, auth_resp_packet.message_len);
361 	ret = PASS;
362 end:
363 	PACKET_FREE(&auth_resp_packet);
364 	DBG_RETURN(ret);
365 }
366 /* }}} */
367 
368 
369 /* {{{ mysqlnd_auth_change_user */
370 enum_func_status
mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn,const char * const user,const size_t user_len,const char * const passwd,const size_t passwd_len,const char * const db,const size_t db_len,const zend_bool silent,const zend_bool use_full_blown_auth_packet,const char * const auth_protocol,struct st_mysqlnd_authentication_plugin * auth_plugin,const zend_uchar * const orig_auth_plugin_data,const size_t orig_auth_plugin_data_len,const zend_uchar * const auth_plugin_data,const size_t auth_plugin_data_len,char ** switch_to_auth_protocol,size_t * const switch_to_auth_protocol_len,zend_uchar ** switch_to_auth_protocol_data,size_t * const switch_to_auth_protocol_data_len)371 mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn,
372 								const char * const user,
373 								const size_t user_len,
374 								const char * const passwd,
375 								const size_t passwd_len,
376 								const char * const db,
377 								const size_t db_len,
378 								const zend_bool silent,
379 								const zend_bool use_full_blown_auth_packet,
380 								const char * const auth_protocol,
381 								struct st_mysqlnd_authentication_plugin * auth_plugin,
382 								const zend_uchar * const orig_auth_plugin_data,
383 								const size_t orig_auth_plugin_data_len,
384 								const zend_uchar * const auth_plugin_data,
385 								const size_t auth_plugin_data_len,
386 								char ** switch_to_auth_protocol,
387 								size_t * const switch_to_auth_protocol_len,
388 								zend_uchar ** switch_to_auth_protocol_data,
389 								size_t * const switch_to_auth_protocol_data_len
390 								)
391 {
392 	enum_func_status ret = FAIL;
393 	const MYSQLND_CHARSET * old_cs = conn->charset;
394 	MYSQLND_PACKET_CHG_USER_RESPONSE chg_user_resp;
395 
396 	DBG_ENTER("mysqlnd_auth_change_user");
397 
398 	conn->payload_decoder_factory->m.init_change_user_response_packet(&chg_user_resp);
399 
400 	if (use_full_blown_auth_packet != TRUE) {
401 		MYSQLND_PACKET_CHANGE_AUTH_RESPONSE change_auth_resp_packet;
402 
403 		conn->payload_decoder_factory->m.init_change_auth_response_packet(&change_auth_resp_packet);
404 
405 		change_auth_resp_packet.auth_data = auth_plugin_data;
406 		change_auth_resp_packet.auth_data_len = auth_plugin_data_len;
407 
408 		if (!PACKET_WRITE(conn, &change_auth_resp_packet)) {
409 			SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
410 			SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
411 			PACKET_FREE(&change_auth_resp_packet);
412 			goto end;
413 		}
414 
415 		PACKET_FREE(&change_auth_resp_packet);
416 	} else {
417 		MYSQLND_PACKET_AUTH auth_packet;
418 
419 		conn->payload_decoder_factory->m.init_auth_packet(&auth_packet);
420 
421 		auth_packet.is_change_user_packet = TRUE;
422 		auth_packet.user		= user;
423 		auth_packet.db			= db;
424 		auth_packet.db_len		= db_len;
425 		auth_packet.silent		= silent;
426 
427 		auth_packet.auth_data = auth_plugin_data;
428 		auth_packet.auth_data_len = auth_plugin_data_len;
429 		auth_packet.auth_plugin_name = auth_protocol;
430 
431         if (conn->server_capabilities & CLIENT_CONNECT_ATTRS) {
432 			auth_packet.connect_attr = conn->options->connect_attr;
433         }
434 
435 		if (conn->m->get_server_version(conn) >= 50123) {
436 			auth_packet.charset_no	= conn->charset->nr;
437 		}
438 
439 		if (!PACKET_WRITE(conn, &auth_packet)) {
440 			SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
441 			SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
442 			PACKET_FREE(&auth_packet);
443 			goto end;
444 		}
445 
446 		PACKET_FREE(&auth_packet);
447 	}
448 
449 	if (auth_plugin && auth_plugin->methods.handle_server_response) {
450 		if (FAIL == auth_plugin->methods.handle_server_response(auth_plugin, conn,
451 				orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len,
452 				switch_to_auth_protocol, switch_to_auth_protocol_len,
453 				switch_to_auth_protocol_data, switch_to_auth_protocol_data_len)) {
454 			goto end;
455 		}
456 	}
457 
458 	ret = PACKET_READ(conn, &chg_user_resp);
459 	COPY_CLIENT_ERROR(conn->error_info, chg_user_resp.error_info);
460 
461 	if (0xFE == chg_user_resp.response_code) {
462 		ret = FAIL;
463 		if (!chg_user_resp.new_auth_protocol) {
464 			DBG_ERR(mysqlnd_old_passwd);
465 			SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
466 		} else {
467 			*switch_to_auth_protocol = mnd_pestrndup(chg_user_resp.new_auth_protocol, chg_user_resp.new_auth_protocol_len, FALSE);
468 			*switch_to_auth_protocol_len = chg_user_resp.new_auth_protocol_len;
469 			if (chg_user_resp.new_auth_protocol_data) {
470 				*switch_to_auth_protocol_data_len = chg_user_resp.new_auth_protocol_data_len;
471 				*switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
472 				memcpy(*switch_to_auth_protocol_data, chg_user_resp.new_auth_protocol_data, *switch_to_auth_protocol_data_len);
473 			} else {
474 				*switch_to_auth_protocol_data = NULL;
475 				*switch_to_auth_protocol_data_len = 0;
476 			}
477 		}
478 	}
479 
480 	if (conn->error_info->error_no) {
481 		ret = FAIL;
482 		/*
483 		  COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
484 		  bug#25371 mysql_change_user() triggers "packets out of sync"
485 		  When it gets fixed, there should be one more check here
486 		*/
487 		if (conn->m->get_server_version(conn) > 50113L &&conn->m->get_server_version(conn) < 50118L) {
488 			MYSQLND_PACKET_OK redundant_error_packet;
489 
490 			conn->payload_decoder_factory->m.init_ok_packet(&redundant_error_packet);
491 			PACKET_READ(conn, &redundant_error_packet);
492 			PACKET_FREE(&redundant_error_packet);
493 			DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", conn->m->get_server_version(conn));
494 		}
495 	}
496 	if (ret == PASS) {
497 		char * tmp = NULL;
498 		/* if we get conn->username as parameter and then we first free it, then estrndup it, we will crash */
499 		tmp = mnd_pestrndup(user, user_len, conn->persistent);
500 		if (conn->username.s) {
501 			mnd_pefree(conn->username.s, conn->persistent);
502 		}
503 		conn->username.s = tmp;
504 
505 		tmp = mnd_pestrdup(passwd, conn->persistent);
506 		if (conn->password.s) {
507 			mnd_pefree(conn->password.s, conn->persistent);
508 		}
509 		conn->password.s = tmp;
510 
511 		if (conn->last_message.s) {
512 			mnd_efree(conn->last_message.s);
513 			conn->last_message.s = NULL;
514 		}
515 		UPSERT_STATUS_RESET(conn->upsert_status);
516 		/* set charset for old servers */
517 		if (conn->m->get_server_version(conn) < 50123) {
518 			ret = conn->m->set_charset(conn, old_cs->name);
519 		}
520 	} else if (ret == FAIL && chg_user_resp.server_asked_323_auth == TRUE) {
521 		/* old authentication with new server  !*/
522 		DBG_ERR(mysqlnd_old_passwd);
523 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
524 	}
525 end:
526 	PACKET_FREE(&chg_user_resp);
527 	DBG_RETURN(ret);
528 }
529 /* }}} */
530 
531 
532 /******************************************* MySQL Native Password ***********************************/
533 
534 #include "ext/standard/sha1.h"
535 
536 /* {{{ php_mysqlnd_crypt */
537 static void
php_mysqlnd_crypt(zend_uchar * buffer,const zend_uchar * s1,const zend_uchar * s2,size_t len)538 php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len)
539 {
540 	const zend_uchar *s1_end = s1 + len;
541 	while (s1 < s1_end) {
542 		*buffer++= *s1++ ^ *s2++;
543 	}
544 }
545 /* }}} */
546 
547 
548 /* {{{ php_mysqlnd_scramble */
php_mysqlnd_scramble(zend_uchar * const buffer,const zend_uchar * const scramble,const zend_uchar * const password,const size_t password_len)549 void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, const size_t password_len)
550 {
551 	PHP_SHA1_CTX context;
552 	zend_uchar sha1[SHA1_MAX_LENGTH];
553 	zend_uchar sha2[SHA1_MAX_LENGTH];
554 
555 	/* Phase 1: hash password */
556 	PHP_SHA1Init(&context);
557 	PHP_SHA1Update(&context, password, password_len);
558 	PHP_SHA1Final(sha1, &context);
559 
560 	/* Phase 2: hash sha1 */
561 	PHP_SHA1Init(&context);
562 	PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH);
563 	PHP_SHA1Final(sha2, &context);
564 
565 	/* Phase 3: hash scramble + sha2 */
566 	PHP_SHA1Init(&context);
567 	PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH);
568 	PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH);
569 	PHP_SHA1Final(buffer, &context);
570 
571 	/* let's crypt buffer now */
572 	php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH);
573 }
574 /* }}} */
575 
576 
577 /* {{{ mysqlnd_native_auth_get_auth_data */
578 static zend_uchar *
mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,size_t * auth_data_len,MYSQLND_CONN_DATA * conn,const char * const user,const char * const passwd,const size_t passwd_len,zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,const MYSQLND_SESSION_OPTIONS * const session_options,const MYSQLND_PFC_DATA * const pfc_data,const zend_ulong mysql_flags)579 mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
580 								  size_t * auth_data_len,
581 								  MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
582 								  const size_t passwd_len, zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
583 								  const MYSQLND_SESSION_OPTIONS * const session_options,
584 								  const MYSQLND_PFC_DATA * const pfc_data,
585 								  const zend_ulong mysql_flags
586 								 )
587 {
588 	zend_uchar * ret = NULL;
589 	DBG_ENTER("mysqlnd_native_auth_get_auth_data");
590 	*auth_data_len = 0;
591 
592 	/* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */
593 	if (auth_plugin_data_len < SCRAMBLE_LENGTH) {
594 		/* mysql_native_password only works with SCRAMBLE_LENGTH scramble */
595 		SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble");
596 		DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH);
597 		DBG_RETURN(NULL);
598 	}
599 
600 	/* copy scrambled pass*/
601 	if (passwd && passwd_len) {
602 		ret = malloc(SCRAMBLE_LENGTH);
603 		*auth_data_len = SCRAMBLE_LENGTH;
604 		/* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */
605 		php_mysqlnd_scramble((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len);
606 	}
607 	DBG_RETURN(ret);
608 }
609 /* }}} */
610 
611 
612 static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin =
613 {
614 	{
615 		MYSQLND_PLUGIN_API_VERSION,
616 		"auth_plugin_mysql_native_password",
617 		MYSQLND_VERSION_ID,
618 		PHP_MYSQLND_VERSION,
619 		"PHP License 3.01",
620 		"Andrey Hristov <andrey@php.net>,  Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
621 		{
622 			NULL, /* no statistics , will be filled later if there are some */
623 			NULL, /* no statistics */
624 		},
625 		{
626 			NULL /* plugin shutdown */
627 		}
628 	},
629 	{/* methods */
630 		mysqlnd_native_auth_get_auth_data,
631 		NULL
632 	}
633 };
634 
635 
636 /******************************************* PAM Authentication ***********************************/
637 
638 /* {{{ mysqlnd_pam_auth_get_auth_data */
639 static zend_uchar *
mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,size_t * auth_data_len,MYSQLND_CONN_DATA * conn,const char * const user,const char * const passwd,const size_t passwd_len,zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,const MYSQLND_SESSION_OPTIONS * const session_options,const MYSQLND_PFC_DATA * const pfc_data,const zend_ulong mysql_flags)640 mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
641 							   size_t * auth_data_len,
642 							   MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
643 							   const size_t passwd_len, zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
644 							   const MYSQLND_SESSION_OPTIONS * const session_options,
645 							   const MYSQLND_PFC_DATA * const pfc_data,
646 							   const zend_ulong mysql_flags
647 							  )
648 {
649 	zend_uchar * ret = NULL;
650 
651 	/* copy pass*/
652 	if (passwd && passwd_len) {
653 		ret = (zend_uchar*) zend_strndup(passwd, passwd_len);
654 	}
655 	/*
656 	  Trailing null required. bug#78680
657 	  https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html
658 	*/
659 	*auth_data_len = passwd_len + 1;
660 
661 	return ret;
662 }
663 /* }}} */
664 
665 
666 static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin =
667 {
668 	{
669 		MYSQLND_PLUGIN_API_VERSION,
670 		"auth_plugin_mysql_clear_password",
671 		MYSQLND_VERSION_ID,
672 		PHP_MYSQLND_VERSION,
673 		"PHP License 3.01",
674 		"Andrey Hristov <andrey@php.net>,  Ulf Wendel <uw@php.net>, Georg Richter <georg@php.net>",
675 		{
676 			NULL, /* no statistics , will be filled later if there are some */
677 			NULL, /* no statistics */
678 		},
679 		{
680 			NULL /* plugin shutdown */
681 		}
682 	},
683 	{/* methods */
684 		mysqlnd_pam_auth_get_auth_data,
685 		NULL
686 	}
687 };
688 
689 
690 /******************************************* SHA256 Password ***********************************/
691 #ifdef MYSQLND_HAVE_SSL
692 static void
mysqlnd_xor_string(char * dst,const size_t dst_len,const char * xor_str,const size_t xor_str_len)693 mysqlnd_xor_string(char * dst, const size_t dst_len, const char * xor_str, const size_t xor_str_len)
694 {
695 	unsigned int i;
696 	for (i = 0; i <= dst_len; ++i) {
697 		dst[i] ^= xor_str[i % xor_str_len];
698 	}
699 }
700 
701 #ifndef PHP_WIN32
702 
703 #include <openssl/rsa.h>
704 #include <openssl/pem.h>
705 #include <openssl/err.h>
706 
707 typedef RSA * mysqlnd_rsa_t;
708 
709 /* {{{ mysqlnd_sha256_get_rsa_from_pem */
710 static mysqlnd_rsa_t
mysqlnd_sha256_get_rsa_from_pem(const char * buf,size_t len)711 mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len)
712 {
713 	BIO * bio = BIO_new_mem_buf(buf, len);
714 	RSA * ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
715 	BIO_free(bio);
716 	return ret;
717 }
718 /* }}} */
719 
720 /* {{{ mysqlnd_sha256_public_encrypt */
721 static zend_uchar *
mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn,mysqlnd_rsa_t server_public_key,size_t passwd_len,size_t * auth_data_len,char * xor_str)722 mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, size_t * auth_data_len, char *xor_str)
723 {
724 	zend_uchar * ret = NULL;
725 	size_t server_public_key_len = (size_t) RSA_size(server_public_key);
726 
727 	DBG_ENTER("mysqlnd_sha256_public_encrypt");
728 	/*
729 		Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
730 		RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
731 		http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
732 	*/
733 	if (server_public_key_len <= passwd_len + 41) {
734 		/* password message is to long */
735 		RSA_free(server_public_key);
736 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
737 		DBG_ERR("password is too long");
738 		DBG_RETURN(NULL);
739 	}
740 
741 	*auth_data_len = server_public_key_len;
742 	ret = malloc(*auth_data_len);
743 	RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING);
744 	RSA_free(server_public_key);
745 	DBG_RETURN(ret);
746 }
747 /* }}} */
748 
749 #else
750 
751 #include <wincrypt.h>
752 #include <bcrypt.h>
753 
754 typedef HANDLE mysqlnd_rsa_t;
755 
756 /* {{{ mysqlnd_sha256_get_rsa_from_pem */
757 static mysqlnd_rsa_t
mysqlnd_sha256_get_rsa_from_pem(const char * buf,size_t len)758 mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len)
759 {
760 	BCRYPT_KEY_HANDLE ret = 0;
761 	LPCSTR der_buf = NULL;
762 	DWORD der_len;
763 	CERT_PUBLIC_KEY_INFO *key_info = NULL;
764 	DWORD key_info_len;
765 	ALLOCA_FLAG(use_heap);
766 
767 	if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, NULL, &der_len, NULL, NULL)) {
768 		goto finish;
769 	}
770 	der_buf = do_alloca(der_len, use_heap);
771 	if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, der_buf, &der_len, NULL, NULL)) {
772 		goto finish;
773 	}
774 	if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, der_buf, der_len, CRYPT_ENCODE_ALLOC_FLAG, NULL, &key_info, &key_info_len)) {
775 		goto finish;
776 	}
777 	if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, key_info, CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG, NULL, &ret)) {
778 		goto finish;
779 	}
780 
781 finish:
782 	if (key_info) {
783 		LocalFree(key_info);
784 	}
785 	if (der_buf) {
786 		free_alloca(der_buf, use_heap);
787 	}
788 	return (mysqlnd_rsa_t) ret;
789 }
790 /* }}} */
791 
792 /* {{{ mysqlnd_sha256_public_encrypt */
793 static zend_uchar *
mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn,mysqlnd_rsa_t server_public_key,size_t passwd_len,size_t * auth_data_len,char * xor_str)794 mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, size_t * auth_data_len, char *xor_str)
795 {
796 	zend_uchar * ret = NULL;
797 	DWORD server_public_key_len = passwd_len;
798 	BCRYPT_OAEP_PADDING_INFO padding_info;
799 
800 	DBG_ENTER("mysqlnd_sha256_public_encrypt");
801 
802 	ZeroMemory(&padding_info, sizeof padding_info);
803 	padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM;
804 	if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
805 			NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) {
806 		DBG_RETURN(0);
807 	}
808 
809 	/*
810 		Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
811 		RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
812 		http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
813 	*/
814 	if ((size_t) server_public_key_len <= passwd_len + 41) {
815 		/* password message is to long */
816 		BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
817 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
818 		DBG_ERR("password is too long");
819 		DBG_RETURN(0);
820 	}
821 
822 	*auth_data_len = server_public_key_len;
823 	ret = malloc(*auth_data_len);
824 	if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
825 			NULL, 0, ret, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) {
826 		BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
827 		DBG_RETURN(0);
828 	}
829 	BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
830 	DBG_RETURN(ret);
831 }
832 /* }}} */
833 
834 #endif
835 
836 /* {{{ mysqlnd_sha256_get_rsa_key */
837 static mysqlnd_rsa_t
mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,const MYSQLND_SESSION_OPTIONS * const session_options,const MYSQLND_PFC_DATA * const pfc_data)838 mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,
839 						   const MYSQLND_SESSION_OPTIONS * const session_options,
840 						   const MYSQLND_PFC_DATA * const pfc_data
841 						  )
842 {
843 	mysqlnd_rsa_t ret = NULL;
844 	const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
845 								pfc_data->sha256_server_public_key:
846 								MYSQLND_G(sha256_server_public_key);
847 	php_stream * stream;
848 	DBG_ENTER("mysqlnd_sha256_get_rsa_key");
849 	DBG_INF_FMT("options_s256_pk=[%s] MYSQLND_G(sha256_server_public_key)=[%s]",
850 				 pfc_data->sha256_server_public_key? pfc_data->sha256_server_public_key:"n/a",
851 				 MYSQLND_G(sha256_server_public_key)? MYSQLND_G(sha256_server_public_key):"n/a");
852 	if (!fname || fname[0] == '\0') {
853 		MYSQLND_PACKET_SHA256_PK_REQUEST pk_req_packet;
854 		MYSQLND_PACKET_SHA256_PK_REQUEST_RESPONSE pk_resp_packet;
855 
856 		do {
857 			DBG_INF("requesting the public key from the server");
858 			conn->payload_decoder_factory->m.init_sha256_pk_request_packet(&pk_req_packet);
859 			conn->payload_decoder_factory->m.init_sha256_pk_request_response_packet(&pk_resp_packet);
860 
861 			if (! PACKET_WRITE(conn, &pk_req_packet)) {
862 				DBG_ERR_FMT("Error while sending public key request packet");
863 				php_error(E_WARNING, "Error while sending public key request packet. PID=%d", getpid());
864 				SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
865 				break;
866 			}
867 			if (FAIL == PACKET_READ(conn, &pk_resp_packet) || NULL == pk_resp_packet.public_key) {
868 				DBG_ERR_FMT("Error while receiving public key");
869 				php_error(E_WARNING, "Error while receiving public key. PID=%d", getpid());
870 				SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
871 				break;
872 			}
873 			DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key);
874 			/* now extract the public key */
875 			ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len);
876 		} while (0);
877 		PACKET_FREE(&pk_req_packet);
878 		PACKET_FREE(&pk_resp_packet);
879 
880 		DBG_INF_FMT("ret=%p", ret);
881 		DBG_RETURN(ret);
882 
883 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE,
884 			"sha256_server_public_key is not set for the connection or as mysqlnd.sha256_server_public_key");
885 		DBG_ERR("server_public_key is not set");
886 		DBG_RETURN(NULL);
887 	} else {
888 		zend_string * key_str;
889 		DBG_INF_FMT("Key in a file. [%s]", fname);
890 		stream = php_stream_open_wrapper((char *) fname, "rb", REPORT_ERRORS, NULL);
891 
892 		if (stream) {
893 			if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
894 				ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
895 				DBG_INF("Successfully loaded");
896 				DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
897 				zend_string_release_ex(key_str, 0);
898 			}
899 			php_stream_close(stream);
900 		}
901 	}
902 	DBG_RETURN(ret);
903 }
904 /* }}} */
905 
906 
907 /* {{{ mysqlnd_sha256_auth_get_auth_data */
908 static zend_uchar *
mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,size_t * auth_data_len,MYSQLND_CONN_DATA * conn,const char * const user,const char * const passwd,const size_t passwd_len,zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,const MYSQLND_SESSION_OPTIONS * const session_options,const MYSQLND_PFC_DATA * const pfc_data,const zend_ulong mysql_flags)909 mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
910 								  size_t * auth_data_len,
911 								  MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
912 								  const size_t passwd_len, zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
913 								  const MYSQLND_SESSION_OPTIONS * const session_options,
914 								  const MYSQLND_PFC_DATA * const pfc_data,
915 								  const zend_ulong mysql_flags
916 								 )
917 {
918 	mysqlnd_rsa_t server_public_key;
919 	zend_uchar * ret = NULL;
920 	DBG_ENTER("mysqlnd_sha256_auth_get_auth_data");
921 	DBG_INF_FMT("salt(%d)=[%.*s]", auth_plugin_data_len, auth_plugin_data_len, auth_plugin_data);
922 
923 
924 	if (conn->vio->data->ssl) {
925 		DBG_INF("simple clear text under SSL");
926 		/* clear text under SSL */
927 		*auth_data_len = passwd_len;
928 		ret = malloc(passwd_len);
929 		memcpy(ret, passwd, passwd_len);
930 	} else {
931 		*auth_data_len = 0;
932 		server_public_key = mysqlnd_sha256_get_rsa_key(conn, session_options, pfc_data);
933 
934 		if (server_public_key) {
935 			ALLOCA_FLAG(use_heap);
936 			char *xor_str = do_alloca(passwd_len + 1, use_heap);
937 			memcpy(xor_str, passwd, passwd_len);
938 			xor_str[passwd_len] = '\0';
939 			mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len);
940 			ret = mysqlnd_sha256_public_encrypt(conn, server_public_key, passwd_len, auth_data_len, xor_str);
941 			free_alloca(xor_str, use_heap);
942 		}
943 	}
944 
945 	DBG_RETURN(ret);
946 }
947 /* }}} */
948 
949 
950 static struct st_mysqlnd_authentication_plugin mysqlnd_sha256_authentication_plugin =
951 {
952 	{
953 		MYSQLND_PLUGIN_API_VERSION,
954 		"auth_plugin_sha256_password",
955 		MYSQLND_VERSION_ID,
956 		PHP_MYSQLND_VERSION,
957 		"PHP License 3.01",
958 		"Andrey Hristov <andrey@php.net>,  Ulf Wendel <uwendel@mysql.com>",
959 		{
960 			NULL, /* no statistics , will be filled later if there are some */
961 			NULL, /* no statistics */
962 		},
963 		{
964 			NULL /* plugin shutdown */
965 		}
966 	},
967 	{/* methods */
968 		mysqlnd_sha256_auth_get_auth_data,
969 		NULL
970 	}
971 };
972 #endif
973 
974 /*************************************** CACHING SHA2 Password *******************************/
975 #ifdef MYSQLND_HAVE_SSL
976 
977 #undef L64
978 
979 #include "ext/hash/php_hash.h"
980 #include "ext/hash/php_hash_sha.h"
981 
982 #define SHA256_LENGTH 32
983 
984 /* {{{ php_mysqlnd_scramble_sha2 */
php_mysqlnd_scramble_sha2(zend_uchar * const buffer,const zend_uchar * const scramble,const zend_uchar * const password,const size_t password_len)985 void php_mysqlnd_scramble_sha2(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, const size_t password_len)
986 {
987 	PHP_SHA256_CTX context;
988 	zend_uchar sha1[SHA256_LENGTH];
989 	zend_uchar sha2[SHA256_LENGTH];
990 
991 	/* Phase 1: hash password */
992 	PHP_SHA256Init(&context);
993 	PHP_SHA256Update(&context, password, password_len);
994 	PHP_SHA256Final(sha1, &context);
995 
996 	/* Phase 2: hash sha1 */
997 	PHP_SHA256Init(&context);
998 	PHP_SHA256Update(&context, (zend_uchar*)sha1, SHA256_LENGTH);
999 	PHP_SHA256Final(sha2, &context);
1000 
1001 	/* Phase 3: hash scramble + sha2 */
1002 	PHP_SHA256Init(&context);
1003 	PHP_SHA256Update(&context, (zend_uchar*)sha2, SHA256_LENGTH);
1004 	PHP_SHA256Update(&context, scramble, SCRAMBLE_LENGTH);
1005 	PHP_SHA256Final(buffer, &context);
1006 
1007 	/* let's crypt buffer now */
1008 	php_mysqlnd_crypt(buffer, (const zend_uchar *)sha1, (const zend_uchar *)buffer, SHA256_LENGTH);
1009 }
1010 /* }}} */
1011 
1012 #ifndef PHP_WIN32
1013 
1014 /* {{{ mysqlnd_caching_sha2_public_encrypt */
1015 static size_t
mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn,mysqlnd_rsa_t server_public_key,size_t passwd_len,unsigned char ** crypted,char * xor_str)1016 mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, unsigned char **crypted, char *xor_str)
1017 {
1018 	size_t server_public_key_len = (size_t) RSA_size(server_public_key);
1019 
1020 	DBG_ENTER("mysqlnd_caching_sha2_public_encrypt");
1021 	/*
1022 		Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1023 		RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1024 		http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1025 	*/
1026 	if (server_public_key_len <= passwd_len + 41) {
1027 		/* password message is to long */
1028 		RSA_free(server_public_key);
1029 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1030 		DBG_ERR("password is too long");
1031 		DBG_RETURN(0);
1032 	}
1033 
1034 	*crypted = emalloc(server_public_key_len);
1035 	RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING);
1036 	RSA_free(server_public_key);
1037 	DBG_RETURN(server_public_key_len);
1038 }
1039 /* }}} */
1040 
1041 #else
1042 
1043 /* {{{ mysqlnd_caching_sha2_public_encrypt */
1044 static size_t
mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn,mysqlnd_rsa_t server_public_key,size_t passwd_len,unsigned char ** crypted,char * xor_str)1045 mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, unsigned char **crypted, char *xor_str)
1046 {
1047 	DWORD server_public_key_len = passwd_len;
1048 	BCRYPT_OAEP_PADDING_INFO padding_info;
1049 
1050 	DBG_ENTER("mysqlnd_caching_sha2_public_encrypt");
1051 
1052 	ZeroMemory(&padding_info, sizeof padding_info);
1053 	padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM;
1054 	if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
1055 			NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) {
1056 		DBG_RETURN(0);
1057 	}
1058 
1059 	/*
1060 		Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1061 		RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1062 		http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1063 	*/
1064 	if ((size_t) server_public_key_len <= passwd_len + 41) {
1065 		/* password message is to long */
1066 		BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
1067 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1068 		DBG_ERR("password is too long");
1069 		DBG_RETURN(0);
1070 	}
1071 
1072 	*crypted = emalloc(server_public_key_len);
1073 	if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
1074 			NULL, 0, *crypted, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) {
1075 		BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
1076 		DBG_RETURN(0);
1077 	}
1078 	BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
1079 	DBG_RETURN(server_public_key_len);
1080 }
1081 /* }}} */
1082 
1083 #endif
1084 
1085 /* {{{ mysqlnd_native_auth_get_auth_data */
1086 static zend_uchar *
mysqlnd_caching_sha2_get_auth_data(struct st_mysqlnd_authentication_plugin * self,size_t * auth_data_len,MYSQLND_CONN_DATA * conn,const char * const user,const char * const passwd,const size_t passwd_len,zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,const MYSQLND_SESSION_OPTIONS * const session_options,const MYSQLND_PFC_DATA * const pfc_data,const zend_ulong mysql_flags)1087 mysqlnd_caching_sha2_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
1088 								   size_t * auth_data_len,
1089 							 	   MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
1090 								   const size_t passwd_len, zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
1091 								   const MYSQLND_SESSION_OPTIONS * const session_options,
1092 								   const MYSQLND_PFC_DATA * const pfc_data,
1093 								   const zend_ulong mysql_flags
1094 								  )
1095 {
1096 	zend_uchar * ret = NULL;
1097 	DBG_ENTER("mysqlnd_caching_sha2_get_auth_data");
1098 	DBG_INF_FMT("salt(%d)=[%.*s]", auth_plugin_data_len, auth_plugin_data_len, auth_plugin_data);
1099 	*auth_data_len = 0;
1100 
1101 	if (auth_plugin_data_len < SCRAMBLE_LENGTH) {
1102 		SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble");
1103 		DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH);
1104 		DBG_RETURN(NULL);
1105 	}
1106 
1107 	DBG_INF("First auth step: send hashed password");
1108 	/* copy scrambled pass*/
1109 	if (passwd && passwd_len) {
1110 		ret = malloc(SHA256_LENGTH + 1);
1111 		*auth_data_len = SHA256_LENGTH;
1112 		php_mysqlnd_scramble_sha2((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len);
1113 		ret[SHA256_LENGTH] = '\0';
1114 		DBG_INF_FMT("hash(%d)=[%.*s]", *auth_data_len, *auth_data_len, ret);
1115 	}
1116 
1117 	DBG_RETURN(ret);
1118 }
1119 /* }}} */
1120 
1121 static mysqlnd_rsa_t
mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA * conn)1122 mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
1123 {
1124 	mysqlnd_rsa_t ret = NULL;
1125 	const MYSQLND_PFC_DATA * const pfc_data = conn->protocol_frame_codec->data;
1126 	const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
1127 								pfc_data->sha256_server_public_key:
1128 								MYSQLND_G(sha256_server_public_key);
1129 	php_stream * stream;
1130 	DBG_ENTER("mysqlnd_cached_sha2_get_key");
1131 	DBG_INF_FMT("options_s256_pk=[%s] MYSQLND_G(sha256_server_public_key)=[%s]",
1132 				 pfc_data->sha256_server_public_key? pfc_data->sha256_server_public_key:"n/a",
1133 				 MYSQLND_G(sha256_server_public_key)? MYSQLND_G(sha256_server_public_key):"n/a");
1134 	if (!fname || fname[0] == '\0') {
1135 		MYSQLND_PACKET_CACHED_SHA2_RESULT req_packet;
1136 		MYSQLND_PACKET_SHA256_PK_REQUEST_RESPONSE pk_resp_packet;
1137 
1138 		do {
1139 			DBG_INF("requesting the public key from the server");
1140 			conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&req_packet);
1141 			conn->payload_decoder_factory->m.init_sha256_pk_request_response_packet(&pk_resp_packet);
1142 			req_packet.request = 1;
1143 
1144 			if (! PACKET_WRITE(conn, &req_packet)) {
1145 				DBG_ERR_FMT("Error while sending public key request packet");
1146 				php_error(E_WARNING, "Error while sending public key request packet. PID=%d", getpid());
1147 				SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
1148 				break;
1149 			}
1150 			if (FAIL == PACKET_READ(conn, &pk_resp_packet) || NULL == pk_resp_packet.public_key) {
1151 				DBG_ERR_FMT("Error while receiving public key");
1152 				php_error(E_WARNING, "Error while receiving public key. PID=%d", getpid());
1153 				SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
1154 				break;
1155 			}
1156 			DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key);
1157 			/* now extract the public key */
1158 			ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len);
1159 		} while (0);
1160 		PACKET_FREE(&req_packet);
1161 		PACKET_FREE(&pk_resp_packet);
1162 
1163 		DBG_INF_FMT("ret=%p", ret);
1164 		DBG_RETURN(ret);
1165 
1166 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE,
1167 			"caching_sha2_server_public_key is not set for the connection or as mysqlnd.sha256_server_public_key");
1168 		DBG_ERR("server_public_key is not set");
1169 		DBG_RETURN(NULL);
1170 	} else {
1171 		zend_string * key_str;
1172 		DBG_INF_FMT("Key in a file. [%s]", fname);
1173 		stream = php_stream_open_wrapper((char *) fname, "rb", REPORT_ERRORS, NULL);
1174 
1175 		if (stream) {
1176 			if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
1177 				ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
1178 				DBG_INF("Successfully loaded");
1179 				DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
1180 				zend_string_release(key_str);
1181 			}
1182 			php_stream_close(stream);
1183 		}
1184 	}
1185 	DBG_RETURN(ret);
1186 
1187 }
1188 
1189 
1190 /* {{{ mysqlnd_caching_sha2_get_and_use_key */
1191 static size_t
mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA * conn,const zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,unsigned char ** crypted,const char * const passwd,const size_t passwd_len)1192 mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn,
1193 		const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
1194 		unsigned char **crypted,
1195 		const char * const passwd,
1196 		const size_t passwd_len)
1197 {
1198 	mysqlnd_rsa_t server_public_key = mysqlnd_caching_sha2_get_key(conn);
1199 
1200 	DBG_ENTER("mysqlnd_caching_sha2_get_and_use_key(");
1201 
1202 	if (server_public_key) {
1203 		int server_public_key_len;
1204 		ALLOCA_FLAG(use_heap)
1205 		char *xor_str = do_alloca(passwd_len + 1, use_heap);
1206 		memcpy(xor_str, passwd, passwd_len);
1207 		xor_str[passwd_len] = '\0';
1208 		mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, SCRAMBLE_LENGTH);
1209 		server_public_key_len = mysqlnd_caching_sha2_public_encrypt(conn, server_public_key, passwd_len, crypted, xor_str);
1210 		free_alloca(xor_str, use_heap);
1211 		DBG_RETURN(server_public_key_len);
1212 	}
1213 	DBG_RETURN(0);
1214 }
1215 /* }}} */
1216 
is_secure_transport(MYSQLND_CONN_DATA * conn)1217 static int is_secure_transport(MYSQLND_CONN_DATA *conn) {
1218 	if (conn->vio->data->ssl) {
1219 		return 1;
1220 	}
1221 
1222 	return strcmp(conn->vio->data->stream->ops->label, "unix_socket") == 0;
1223 }
1224 
1225 /* {{{ mysqlnd_caching_sha2_handle_server_response */
1226 static enum_func_status
mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plugin * self,MYSQLND_CONN_DATA * conn,const zend_uchar * auth_plugin_data,const size_t auth_plugin_data_len,const char * const passwd,const size_t passwd_len,char ** new_auth_protocol,size_t * new_auth_protocol_len,zend_uchar ** new_auth_protocol_data,size_t * new_auth_protocol_data_len)1227 mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plugin *self,
1228 		MYSQLND_CONN_DATA * conn,
1229 		const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
1230 		const char * const passwd,
1231 		const size_t passwd_len,
1232 		char **new_auth_protocol, size_t *new_auth_protocol_len,
1233 		zend_uchar **new_auth_protocol_data, size_t *new_auth_protocol_data_len
1234 		)
1235 {
1236 	DBG_ENTER("mysqlnd_caching_sha2_handle_server_response");
1237 	MYSQLND_PACKET_CACHED_SHA2_RESULT result_packet;
1238 
1239 	if (passwd_len == 0) {
1240 		DBG_INF("empty password fast path");
1241 		DBG_RETURN(PASS);
1242 	}
1243 
1244 	conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&result_packet);
1245 	if (FAIL == PACKET_READ(conn, &result_packet)) {
1246 		DBG_RETURN(PASS);
1247 	}
1248 
1249 	switch (result_packet.response_code) {
1250 		case 0xFF:
1251 			if (result_packet.sqlstate[0]) {
1252 				strlcpy(conn->error_info->sqlstate, result_packet.sqlstate, sizeof(conn->error_info->sqlstate));
1253 				DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", result_packet.error_no, result_packet.sqlstate, result_packet.error);
1254 			}
1255 			SET_CLIENT_ERROR(conn->error_info, result_packet.error_no, UNKNOWN_SQLSTATE, result_packet.error);
1256 			DBG_RETURN(FAIL);
1257 		case 0xFE:
1258 			DBG_INF("auth switch response");
1259 			*new_auth_protocol = result_packet.new_auth_protocol;
1260 			*new_auth_protocol_len = result_packet.new_auth_protocol_len;
1261 			*new_auth_protocol_data = result_packet.new_auth_protocol_data;
1262 			*new_auth_protocol_data_len = result_packet.new_auth_protocol_data_len;
1263 			DBG_RETURN(FAIL);
1264 		case 3:
1265 			DBG_INF("fast path succeeded");
1266 			DBG_RETURN(PASS);
1267 		case 4:
1268 			if (is_secure_transport(conn)) {
1269 				DBG_INF("fast path failed, doing full auth via secure transport");
1270 				result_packet.password = (zend_uchar *)passwd;
1271 				result_packet.password_len = passwd_len + 1;
1272 				PACKET_WRITE(conn, &result_packet);
1273 			} else {
1274 				DBG_INF("fast path failed, doing full auth via insecure transport");
1275 				result_packet.password_len = mysqlnd_caching_sha2_get_and_use_key(conn, auth_plugin_data, auth_plugin_data_len, &result_packet.password, passwd, passwd_len);
1276 				PACKET_WRITE(conn, &result_packet);
1277 				efree(result_packet.password);
1278 			}
1279 			DBG_RETURN(PASS);
1280 		case 2:
1281 			// The server tried to send a key, which we didn't expect
1282 			// fall-through
1283 		default:
1284 			php_error_docref(NULL, E_WARNING, "Unexpected server response while doing caching_sha2 auth: %i", result_packet.response_code);
1285 	}
1286 
1287 	DBG_RETURN(PASS);
1288 }
1289 /* }}} */
1290 
1291 static struct st_mysqlnd_authentication_plugin mysqlnd_caching_sha2_auth_plugin =
1292 {
1293 	{
1294 		MYSQLND_PLUGIN_API_VERSION,
1295 		"auth_plugin_caching_sha2_password",
1296 		MYSQLND_VERSION_ID,
1297 		PHP_MYSQLND_VERSION,
1298 		"PHP License 3.01",
1299 		"Johannes Schlüter <johannes.schlueter@php.net>",
1300 		{
1301 			NULL, /* no statistics , will be filled later if there are some */
1302 			NULL, /* no statistics */
1303 		},
1304 		{
1305 			NULL /* plugin shutdown */
1306 		}
1307 	},
1308 	{/* methods */
1309 		mysqlnd_caching_sha2_get_auth_data,
1310 		mysqlnd_caching_sha2_handle_server_response
1311 	}
1312 };
1313 #endif
1314 
1315 
1316 /* {{{ mysqlnd_register_builtin_authentication_plugins */
1317 void
mysqlnd_register_builtin_authentication_plugins(void)1318 mysqlnd_register_builtin_authentication_plugins(void)
1319 {
1320 	mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin);
1321 	mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin);
1322 #ifdef MYSQLND_HAVE_SSL
1323 	mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_caching_sha2_auth_plugin);
1324 	mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_sha256_authentication_plugin);
1325 #endif
1326 }
1327 /* }}} */
1328