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