1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2014 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Georg Richter <georg@mysql.com> |
16 | Andrey Hristov <andrey@mysql.com> |
17 | Ulf Wendel <uwendel@mysql.com> |
18 +----------------------------------------------------------------------+
19 */
20
21 /* $Id: mysqlnd.c 307377 2011-01-11 13:02:57Z andrey $ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_structs.h"
25 #include "mysqlnd_wireprotocol.h"
26 #include "mysqlnd_priv.h"
27 #include "mysqlnd_result.h"
28 #include "mysqlnd_charset.h"
29 #include "mysqlnd_debug.h"
30
31
32 /* {{{ mysqlnd_auth_handshake */
33 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_OPTIONS * const options,unsigned long mysql_flags,unsigned int server_charset_no,zend_bool use_full_blown_auth_packet,const char * const auth_protocol,const zend_uchar * const auth_plugin_data,const size_t auth_plugin_data_len,char ** switch_to_auth_protocol,size_t * switch_to_auth_protocol_len,zend_uchar ** switch_to_auth_protocol_data,size_t * switch_to_auth_protocol_data_len TSRMLS_DC)34 mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
35 const char * const user,
36 const char * const passwd,
37 const size_t passwd_len,
38 const char * const db,
39 const size_t db_len,
40 const MYSQLND_OPTIONS * const options,
41 unsigned long mysql_flags,
42 unsigned int server_charset_no,
43 zend_bool use_full_blown_auth_packet,
44 const char * const auth_protocol,
45 const zend_uchar * const auth_plugin_data,
46 const size_t auth_plugin_data_len,
47 char ** switch_to_auth_protocol,
48 size_t * switch_to_auth_protocol_len,
49 zend_uchar ** switch_to_auth_protocol_data,
50 size_t * switch_to_auth_protocol_data_len
51 TSRMLS_DC)
52 {
53 enum_func_status ret = FAIL;
54 const MYSQLND_CHARSET * charset = NULL;
55 MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL;
56 MYSQLND_PACKET_AUTH_RESPONSE * auth_resp_packet = NULL;
57 MYSQLND_PACKET_AUTH * auth_packet = NULL;
58
59 DBG_ENTER("mysqlnd_auth_handshake");
60
61 auth_resp_packet = conn->protocol->m.get_auth_response_packet(conn->protocol, FALSE TSRMLS_CC);
62
63 if (!auth_resp_packet) {
64 SET_OOM_ERROR(*conn->error_info);
65 goto end;
66 }
67
68 if (use_full_blown_auth_packet != TRUE) {
69 change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC);
70 if (!change_auth_resp_packet) {
71 SET_OOM_ERROR(*conn->error_info);
72 goto end;
73 }
74
75 change_auth_resp_packet->auth_data = auth_plugin_data;
76 change_auth_resp_packet->auth_data_len = auth_plugin_data_len;
77
78 if (!PACKET_WRITE(change_auth_resp_packet, conn)) {
79 CONN_SET_STATE(conn, CONN_QUIT_SENT);
80 SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
81 goto end;
82 }
83 } else {
84 auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC);
85
86 auth_packet->client_flags = mysql_flags;
87 auth_packet->max_packet_size = options->max_allowed_packet;
88 if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) {
89 auth_packet->charset_no = charset->nr;
90 } else {
91 #if MYSQLND_UNICODE
92 auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */
93 #else
94 auth_packet->charset_no = server_charset_no;
95 #endif
96 }
97
98 auth_packet->send_auth_data = TRUE;
99 auth_packet->user = user;
100 auth_packet->db = db;
101 auth_packet->db_len = db_len;
102
103 auth_packet->auth_data = auth_plugin_data;
104 auth_packet->auth_data_len = auth_plugin_data_len;
105 auth_packet->auth_plugin_name = auth_protocol;
106
107 if (!PACKET_WRITE(auth_packet, conn)) {
108 goto end;
109 }
110 }
111 if (use_full_blown_auth_packet == TRUE) {
112 conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no);
113 }
114
115 if (FAIL == PACKET_READ(auth_resp_packet, conn) || auth_resp_packet->response_code >= 0xFE) {
116 if (auth_resp_packet->response_code == 0xFE) {
117 /* old authentication with new server !*/
118 if (!auth_resp_packet->new_auth_protocol) {
119 DBG_ERR(mysqlnd_old_passwd);
120 SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
121 } else {
122 *switch_to_auth_protocol = mnd_pestrndup(auth_resp_packet->new_auth_protocol, auth_resp_packet->new_auth_protocol_len, FALSE);
123 *switch_to_auth_protocol_len = auth_resp_packet->new_auth_protocol_len;
124 if (auth_resp_packet->new_auth_protocol_data) {
125 *switch_to_auth_protocol_data_len = auth_resp_packet->new_auth_protocol_data_len;
126 *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
127 memcpy(*switch_to_auth_protocol_data, auth_resp_packet->new_auth_protocol_data, *switch_to_auth_protocol_data_len);
128 } else {
129 *switch_to_auth_protocol_data = NULL;
130 *switch_to_auth_protocol_data_len = 0;
131 }
132 }
133 } else if (auth_resp_packet->response_code == 0xFF) {
134 if (auth_resp_packet->sqlstate[0]) {
135 strlcpy(conn->error_info->sqlstate, auth_resp_packet->sqlstate, sizeof(conn->error_info->sqlstate));
136 DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", auth_resp_packet->error_no, auth_resp_packet->sqlstate, auth_resp_packet->error);
137 }
138 SET_CLIENT_ERROR(*conn->error_info, auth_resp_packet->error_no, UNKNOWN_SQLSTATE, auth_resp_packet->error);
139 }
140 goto end;
141 }
142
143 SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, auth_resp_packet->message, auth_resp_packet->message_len, conn->persistent);
144 ret = PASS;
145 end:
146 PACKET_FREE(change_auth_resp_packet);
147 PACKET_FREE(auth_packet);
148 PACKET_FREE(auth_resp_packet);
149 DBG_RETURN(ret);
150 }
151 /* }}} */
152
153
154 /* {{{ mysqlnd_auth_change_user */
155 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,zend_bool use_full_blown_auth_packet,const char * const auth_protocol,zend_uchar * auth_plugin_data,size_t auth_plugin_data_len,char ** switch_to_auth_protocol,size_t * switch_to_auth_protocol_len,zend_uchar ** switch_to_auth_protocol_data,size_t * switch_to_auth_protocol_data_len TSRMLS_DC)156 mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn,
157 const char * const user,
158 const size_t user_len,
159 const char * const passwd,
160 const size_t passwd_len,
161 const char * const db,
162 const size_t db_len,
163 const zend_bool silent,
164 zend_bool use_full_blown_auth_packet,
165 const char * const auth_protocol,
166 zend_uchar * auth_plugin_data,
167 size_t auth_plugin_data_len,
168 char ** switch_to_auth_protocol,
169 size_t * switch_to_auth_protocol_len,
170 zend_uchar ** switch_to_auth_protocol_data,
171 size_t * switch_to_auth_protocol_data_len
172 TSRMLS_DC)
173 {
174 enum_func_status ret = FAIL;
175 const MYSQLND_CHARSET * old_cs = conn->charset;
176 MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL;
177 MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = NULL;
178 MYSQLND_PACKET_AUTH * auth_packet = NULL;
179
180 DBG_ENTER("mysqlnd_auth_change_user");
181
182 chg_user_resp = conn->protocol->m.get_change_user_response_packet(conn->protocol, FALSE TSRMLS_CC);
183
184 if (!chg_user_resp) {
185 SET_OOM_ERROR(*conn->error_info);
186 goto end;
187 }
188
189 if (use_full_blown_auth_packet != TRUE) {
190 change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC);
191 if (!change_auth_resp_packet) {
192 SET_OOM_ERROR(*conn->error_info);
193 goto end;
194 }
195
196 change_auth_resp_packet->auth_data = auth_plugin_data;
197 change_auth_resp_packet->auth_data_len = auth_plugin_data_len;
198
199 if (!PACKET_WRITE(change_auth_resp_packet, conn)) {
200 CONN_SET_STATE(conn, CONN_QUIT_SENT);
201 SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
202 goto end;
203 }
204 } else {
205 auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC);
206
207 if (!auth_packet) {
208 SET_OOM_ERROR(*conn->error_info);
209 goto end;
210 }
211
212 auth_packet->is_change_user_packet = TRUE;
213 auth_packet->user = user;
214 auth_packet->db = db;
215 auth_packet->db_len = db_len;
216 auth_packet->silent = silent;
217
218 auth_packet->auth_data = auth_plugin_data;
219 auth_packet->auth_data_len = auth_plugin_data_len;
220 auth_packet->auth_plugin_name = auth_protocol;
221
222
223 if (conn->m->get_server_version(conn TSRMLS_CC) >= 50123) {
224 auth_packet->charset_no = conn->charset->nr;
225 }
226
227 if (!PACKET_WRITE(auth_packet, conn)) {
228 CONN_SET_STATE(conn, CONN_QUIT_SENT);
229 SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
230 goto end;
231 }
232 }
233
234 ret = PACKET_READ(chg_user_resp, conn);
235 COPY_CLIENT_ERROR(*conn->error_info, chg_user_resp->error_info);
236
237 if (0xFE == chg_user_resp->response_code) {
238 ret = FAIL;
239 if (!chg_user_resp->new_auth_protocol) {
240 DBG_ERR(mysqlnd_old_passwd);
241 SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
242 } else {
243 *switch_to_auth_protocol = mnd_pestrndup(chg_user_resp->new_auth_protocol, chg_user_resp->new_auth_protocol_len, FALSE);
244 *switch_to_auth_protocol_len = chg_user_resp->new_auth_protocol_len;
245 if (chg_user_resp->new_auth_protocol_data) {
246 *switch_to_auth_protocol_data_len = chg_user_resp->new_auth_protocol_data_len;
247 *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
248 memcpy(*switch_to_auth_protocol_data, chg_user_resp->new_auth_protocol_data, *switch_to_auth_protocol_data_len);
249 } else {
250 *switch_to_auth_protocol_data = NULL;
251 *switch_to_auth_protocol_data_len = 0;
252 }
253 }
254 }
255
256 if (conn->error_info->error_no) {
257 ret = FAIL;
258 /*
259 COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
260 bug#25371 mysql_change_user() triggers "packets out of sync"
261 When it gets fixed, there should be one more check here
262 */
263 if (conn->m->get_server_version(conn TSRMLS_CC) > 50113L &&conn->m->get_server_version(conn TSRMLS_CC) < 50118L) {
264 MYSQLND_PACKET_OK * redundant_error_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC);
265 if (redundant_error_packet) {
266 PACKET_READ(redundant_error_packet, conn);
267 PACKET_FREE(redundant_error_packet);
268 DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", conn->m->get_server_version(conn TSRMLS_CC));
269 } else {
270 SET_OOM_ERROR(*conn->error_info);
271 }
272 }
273 }
274 if (ret == PASS) {
275 char * tmp = NULL;
276 /* if we get conn->user as parameter and then we first free it, then estrndup it, we will crash */
277 tmp = mnd_pestrndup(user, user_len, conn->persistent);
278 if (conn->user) {
279 mnd_pefree(conn->user, conn->persistent);
280 }
281 conn->user = tmp;
282
283 tmp = mnd_pestrdup(passwd, conn->persistent);
284 if (conn->passwd) {
285 mnd_pefree(conn->passwd, conn->persistent);
286 }
287 conn->passwd = tmp;
288
289 if (conn->last_message) {
290 mnd_pefree(conn->last_message, conn->persistent);
291 conn->last_message = NULL;
292 }
293 memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
294 /* set charset for old servers */
295 if (conn->m->get_server_version(conn TSRMLS_CC) < 50123) {
296 ret = conn->m->set_charset(conn, old_cs->name TSRMLS_CC);
297 }
298 } else if (ret == FAIL && chg_user_resp->server_asked_323_auth == TRUE) {
299 /* old authentication with new server !*/
300 DBG_ERR(mysqlnd_old_passwd);
301 SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
302 }
303 end:
304 PACKET_FREE(change_auth_resp_packet);
305 PACKET_FREE(auth_packet);
306 PACKET_FREE(chg_user_resp);
307 DBG_RETURN(ret);
308 }
309 /* }}} */
310
311
312 /******************************************* MySQL Native Password ***********************************/
313
314 #include "ext/standard/sha1.h"
315
316 /* {{{ php_mysqlnd_crypt */
317 static void
php_mysqlnd_crypt(zend_uchar * buffer,const zend_uchar * s1,const zend_uchar * s2,size_t len)318 php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len)
319 {
320 const zend_uchar *s1_end = s1 + len;
321 while (s1 < s1_end) {
322 *buffer++= *s1++ ^ *s2++;
323 }
324 }
325 /* }}} */
326
327
328 /* {{{ php_mysqlnd_scramble */
php_mysqlnd_scramble(zend_uchar * const buffer,const zend_uchar * const scramble,const zend_uchar * const password,size_t password_len)329 void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, size_t password_len)
330 {
331 PHP_SHA1_CTX context;
332 zend_uchar sha1[SHA1_MAX_LENGTH];
333 zend_uchar sha2[SHA1_MAX_LENGTH];
334
335 /* Phase 1: hash password */
336 PHP_SHA1Init(&context);
337 PHP_SHA1Update(&context, password, password_len);
338 PHP_SHA1Final(sha1, &context);
339
340 /* Phase 2: hash sha1 */
341 PHP_SHA1Init(&context);
342 PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH);
343 PHP_SHA1Final(sha2, &context);
344
345 /* Phase 3: hash scramble + sha2 */
346 PHP_SHA1Init(&context);
347 PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH);
348 PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH);
349 PHP_SHA1Final(buffer, &context);
350
351 /* let's crypt buffer now */
352 php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH);
353 }
354 /* }}} */
355
356
357 /* {{{ mysqlnd_native_auth_get_auth_data */
358 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,size_t auth_plugin_data_len,const MYSQLND_OPTIONS * const options,unsigned long mysql_flags TSRMLS_DC)359 mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
360 size_t * auth_data_len,
361 MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
362 const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
363 const MYSQLND_OPTIONS * const options, unsigned long mysql_flags
364 TSRMLS_DC)
365 {
366 zend_uchar * ret = NULL;
367 DBG_ENTER("mysqlnd_native_auth_get_auth_data");
368 *auth_data_len = 0;
369
370 /* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */
371 if (auth_plugin_data_len < SCRAMBLE_LENGTH) {
372 /* mysql_native_password only works with SCRAMBLE_LENGTH scramble */
373 SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble");
374 DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH);
375 DBG_RETURN(NULL);
376 }
377
378 /* copy scrambled pass*/
379 if (passwd && passwd_len) {
380 ret = malloc(SCRAMBLE_LENGTH);
381 *auth_data_len = SCRAMBLE_LENGTH;
382 /* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */
383 php_mysqlnd_scramble((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len);
384 }
385 DBG_RETURN(ret);
386 }
387 /* }}} */
388
389
390 static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin =
391 {
392 {
393 MYSQLND_PLUGIN_API_VERSION,
394 "auth_plugin_mysql_native_password",
395 MYSQLND_VERSION_ID,
396 MYSQLND_VERSION,
397 "PHP License 3.01",
398 "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
399 {
400 NULL, /* no statistics , will be filled later if there are some */
401 NULL, /* no statistics */
402 },
403 {
404 NULL /* plugin shutdown */
405 }
406 },
407 {/* methods */
408 mysqlnd_native_auth_get_auth_data
409 }
410 };
411
412
413 /******************************************* PAM Authentication ***********************************/
414
415 /* {{{ mysqlnd_pam_auth_get_auth_data */
416 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,size_t auth_plugin_data_len,const MYSQLND_OPTIONS * const options,unsigned long mysql_flags TSRMLS_DC)417 mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
418 size_t * auth_data_len,
419 MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
420 const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
421 const MYSQLND_OPTIONS * const options, unsigned long mysql_flags
422 TSRMLS_DC)
423 {
424 zend_uchar * ret = NULL;
425
426 /* copy pass*/
427 if (passwd && passwd_len) {
428 ret = (zend_uchar*) zend_strndup(passwd, passwd_len);
429 }
430 *auth_data_len = passwd_len;
431
432 return ret;
433 }
434 /* }}} */
435
436
437 static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin =
438 {
439 {
440 MYSQLND_PLUGIN_API_VERSION,
441 "auth_plugin_mysql_clear_password",
442 MYSQLND_VERSION_ID,
443 MYSQLND_VERSION,
444 "PHP License 3.01",
445 "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
446 {
447 NULL, /* no statistics , will be filled later if there are some */
448 NULL, /* no statistics */
449 },
450 {
451 NULL /* plugin shutdown */
452 }
453 },
454 {/* methods */
455 mysqlnd_pam_auth_get_auth_data
456 }
457 };
458
459
460 /* {{{ mysqlnd_register_builtin_authentication_plugins */
461 void
mysqlnd_register_builtin_authentication_plugins(TSRMLS_D)462 mysqlnd_register_builtin_authentication_plugins(TSRMLS_D)
463 {
464 mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin TSRMLS_CC);
465 mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin TSRMLS_CC);
466 }
467 /* }}} */
468
469
470 /*
471 * Local variables:
472 * tab-width: 4
473 * c-basic-offset: 4
474 * End:
475 * vim600: noet sw=4 ts=4 fdm=marker
476 * vim<600: noet sw=4 ts=4
477 */
478