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