From: Andrey Hristov Date: Mon, 24 Jan 2011 12:34:47 +0000 (+0000) Subject: Handle MySQL 5.5 authentication features. X-Git-Tag: php-5.4.0alpha1~191^2~321 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ce4e6f4db5ce8f7a292bd6479c70f66c509fadb0;p=php Handle MySQL 5.5 authentication features. Authentication protocol can be changed, a new raw packet is introduced, which includes only the "encrypted" data for the auth plugin, sent after change protocol (0xFE) is sent to the client. --- diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index 8eda41374e..1c730c6165 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -329,7 +329,7 @@ MYSQLND_METHOD(mysqlnd_conn, simple_command_handle_response)(MYSQLND * conn, enu /* {{{ mysqlnd_conn::simple_command */ static enum_func_status MYSQLND_METHOD(mysqlnd_conn, simple_command)(MYSQLND * conn, enum php_mysqlnd_server_command command, - const char * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, + const zend_uchar * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC) { enum_func_status ret = PASS; @@ -395,7 +395,7 @@ static enum_func_status MYSQLND_METHOD(mysqlnd_conn, set_server_option)(MYSQLND * const conn, enum_mysqlnd_server_option option TSRMLS_DC) { enum_func_status ret; - char buffer[2]; + zend_uchar buffer[2]; DBG_ENTER("mysqlnd_conn::set_server_option"); int2store(buffer, (unsigned int) option); @@ -508,43 +508,112 @@ mysqlnd_connect_run_authentication( ret = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC); if (PASS == ret) { + zend_bool first_call = TRUE; + char * switch_to_auth_protocol = NULL; + size_t switch_to_auth_protocol_len = 0; char * requested_protocol = NULL; + zend_uchar * plugin_data; + size_t plugin_data_len; + + plugin_data_len = greet_packet->auth_plugin_data_len; + plugin_data = mnd_emalloc(plugin_data_len); + if (!plugin_data) { + ret = FAIL; + goto end; + } + memcpy(plugin_data, greet_packet->auth_plugin_data, plugin_data_len); + + requested_protocol = mnd_pestrdup(greet_packet->auth_protocol? greet_packet->auth_protocol: "mysql_native_password", FALSE); + if (!requested_protocol) { + ret = FAIL; + goto end; + } do { struct st_mysqlnd_authentication_plugin * auth_plugin; - char * plugin_name = NULL; - requested_protocol = switch_to_auth_protocol? switch_to_auth_protocol: - (greet_packet->auth_protocol? - greet_packet->auth_protocol: - "mysql_native_password" - ); - spprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); - DBG_INF_FMT("looking for %s auth plugin", plugin_name); - auth_plugin = mysqlnd_plugin_find(plugin_name); - efree(plugin_name); - if (!auth_plugin) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method uknown to the client [%s]", requested_protocol); - SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method uknown to the client"); - break; - } + { + char * plugin_name = NULL; + spprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); + + DBG_INF_FMT("looking for %s auth plugin", plugin_name); + auth_plugin = mysqlnd_plugin_find(plugin_name); + efree(plugin_name); + + if (!auth_plugin) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol); + SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method umknown to the client"); + break; + } + } DBG_INF("plugin found"); - ret = mysqlnd_auth_handshake(conn, user, passwd, db, db_len, passwd_len, greet_packet, options, mysql_flags, - auth_plugin, &switch_to_auth_protocol TSRMLS_CC); - DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); - } while (ret == FAIL && switch_to_auth_protocol != NULL); + { + zend_uchar * switch_to_auth_protocol_data = NULL; + size_t switch_to_auth_protocol_data_len = 0; + zend_uchar * scrambled_data = NULL; + size_t scrambled_data_len = 0; + + switch_to_auth_protocol = NULL; + switch_to_auth_protocol_len = 0; + + if (conn->auth_plugin_data) { + mnd_pefree(conn->auth_plugin_data, conn->persistent); + conn->auth_plugin_data = NULL; + } + conn->auth_plugin_data_len = plugin_data_len; + conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent); + if (!conn->auth_plugin_data) { + SET_OOM_ERROR(conn->error_info); + goto end; + } + memcpy(conn->auth_plugin_data, plugin_data, plugin_data_len); + + DBG_INF_FMT("salt=[%*s]", plugin_data_len - 1, plugin_data); + /* The data should be allocated with malloc() */ + scrambled_data = + auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len, + plugin_data, plugin_data_len, options, mysql_flags TSRMLS_CC); + + + ret = mysqlnd_auth_handshake(conn, user, passwd, db, db_len, passwd_len, options, mysql_flags, greet_packet->charset_no, + first_call, + requested_protocol, + scrambled_data, scrambled_data_len, + &switch_to_auth_protocol, &switch_to_auth_protocol_len, + &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len + TSRMLS_CC); + first_call = FALSE; + free(scrambled_data); + + DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); + if (requested_protocol) { + mnd_efree(requested_protocol); + } + requested_protocol = switch_to_auth_protocol; + if (plugin_data) { + mnd_efree(plugin_data); + } + plugin_data_len = switch_to_auth_protocol_data_len; + plugin_data = switch_to_auth_protocol_data; + } + DBG_INF_FMT("conn->error_info.error_no = %d", conn->error_info.error_no); + } while (ret == FAIL && conn->error_info.error_no == 0 && switch_to_auth_protocol != NULL); + if (plugin_data) { + mnd_efree(plugin_data); + } + if (ret == PASS) { conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC); } - if (switch_to_auth_protocol) { - mnd_efree(switch_to_auth_protocol); - switch_to_auth_protocol = NULL; + if (requested_protocol) { + mnd_efree(requested_protocol); } } +end: DBG_RETURN(ret); } /* }}} */ @@ -937,7 +1006,7 @@ MYSQLND_METHOD(mysqlnd_conn, query)(MYSQLND * conn, const char * query, unsigned DBG_ENTER("mysqlnd_conn::query"); DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); - if (PASS != conn->m->simple_command(conn, COM_QUERY, query, query_len, + if (PASS != conn->m->simple_command(conn, COM_QUERY, (zend_uchar *) query, query_len, PROT_LAST /* we will handle the OK packet*/, FALSE, FALSE TSRMLS_CC)) { DBG_RETURN(FAIL); @@ -965,7 +1034,7 @@ MYSQLND_METHOD(mysqlnd_conn, send_query)(MYSQLND * conn, const char * query, uns DBG_ENTER("mysqlnd_conn::send_query"); DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); - ret = conn->m->simple_command(conn, COM_QUERY, query, query_len, + ret = conn->m->simple_command(conn, COM_QUERY, (zend_uchar *) query, query_len, PROT_LAST /* we will handle the OK packet*/, FALSE, FALSE TSRMLS_CC); CONN_SET_STATE(conn, CONN_QUERY_SENT); @@ -1184,7 +1253,7 @@ MYSQLND_RES * MYSQLND_METHOD(mysqlnd_conn, list_fields)(MYSQLND * conn, const char *table, const char *achtung_wild TSRMLS_DC) { /* db + \0 + wild + \0 (for wild) */ - char buff[MYSQLND_MAX_ALLOWED_DB_LEN * 2 + 1 + 1], *p; + zend_uchar buff[MYSQLND_MAX_ALLOWED_DB_LEN * 2 + 1 + 1], *p; size_t table_len, wild_len; MYSQLND_RES *result = NULL; DBG_ENTER("mysqlnd_conn::list_fields"); @@ -1360,7 +1429,7 @@ MYSQLND_METHOD(mysqlnd_conn, select_db)(MYSQLND * const conn, const char * const DBG_ENTER("mysqlnd_conn::select_db"); DBG_INF_FMT("conn=%llu db=%s", conn->thread_id, db); - ret = conn->m->simple_command(conn, COM_INIT_DB, db, db_len, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC); + ret = conn->m->simple_command(conn, COM_INIT_DB, (zend_uchar*) db, db_len, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC); /* The server sends 0 but libmysql doesn't read it and has established a protocol of giving back -1. Thus we have to follow it :( @@ -1444,7 +1513,7 @@ static enum_func_status MYSQLND_METHOD(mysqlnd_conn, kill)(MYSQLND * conn, unsigned int pid TSRMLS_DC) { enum_func_status ret; - char buff[4]; + zend_uchar buff[4]; DBG_ENTER("mysqlnd_conn::kill"); DBG_INF_FMT("conn=%llu pid=%lu", conn->thread_id, pid); @@ -1512,7 +1581,7 @@ MYSQLND_METHOD(mysqlnd_conn, refresh)(MYSQLND * const conn, uint8_t options TSRM int1store(bits, options); - DBG_RETURN(conn->m->simple_command(conn, COM_REFRESH, (char *)bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC)); + DBG_RETURN(conn->m->simple_command(conn, COM_REFRESH, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC)); } /* }}} */ @@ -1527,7 +1596,7 @@ MYSQLND_METHOD(mysqlnd_conn, shutdown)(MYSQLND * const conn, uint8_t level TSRML int1store(bits, level); - DBG_RETURN(conn->m->simple_command(conn, COM_SHUTDOWN, (char *)bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC)); + DBG_RETURN(conn->m->simple_command(conn, COM_SHUTDOWN, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC)); } /* }}} */ @@ -1930,6 +1999,7 @@ MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn, DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s silent=%u", conn->thread_id, user?user:"", passwd?"***":"null", db?db:"", (silent == TRUE)?1:0 ); + SET_EMPTY_ERROR(conn->error_info); SET_ERROR_AFF_ROWS(conn); if (!user) { @@ -1944,47 +2014,116 @@ MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn, { + zend_bool first_call = TRUE; char * switch_to_auth_protocol = NULL; - const char * requested_protocol = NULL; + size_t switch_to_auth_protocol_len = 0; + char * requested_protocol = NULL; + zend_uchar * plugin_data; + size_t plugin_data_len; + + plugin_data_len = conn->auth_plugin_data_len; + plugin_data = mnd_emalloc(plugin_data_len); + if (!plugin_data) { + ret = FAIL; + goto end; + } + memcpy(plugin_data, conn->auth_plugin_data, plugin_data_len); + + requested_protocol = mnd_pestrdup(conn->options.auth_protocol? conn->options.auth_protocol:"mysql_native_password", FALSE); + if (!requested_protocol) { + ret = FAIL; + goto end; + } do { struct st_mysqlnd_authentication_plugin * auth_plugin; - char * plugin_name = NULL; - requested_protocol = switch_to_auth_protocol? switch_to_auth_protocol: - ((conn->server_capabilities & CLIENT_PLUGIN_AUTH)? - conn->options.auth_protocol: - "mysql_native_password" - ); - - spprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); - DBG_INF_FMT("looking for %s auth plugin", plugin_name); - auth_plugin = mysqlnd_plugin_find(plugin_name); - efree(plugin_name); - if (!auth_plugin) { - if (!silent) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method uknown to the client [%s]", requested_protocol); + { + char * plugin_name = NULL; + + spprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); + + DBG_INF_FMT("looking for %s auth plugin", plugin_name); + auth_plugin = mysqlnd_plugin_find(plugin_name); + efree(plugin_name); + + if (!auth_plugin) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol); + SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method umknown to the client"); + break; } - SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method uknown to the client"); - break; - } + } DBG_INF("plugin found"); - ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, db, strlen(db), passwd_len, silent, - auth_plugin, &switch_to_auth_protocol TSRMLS_CC); - DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); - } while (ret == FAIL && switch_to_auth_protocol != NULL); + { + zend_uchar * switch_to_auth_protocol_data = NULL; + size_t switch_to_auth_protocol_data_len = 0; + zend_uchar * scrambled_data = NULL; + size_t scrambled_data_len = 0; + + switch_to_auth_protocol = NULL; + switch_to_auth_protocol_len = 0; + + if (conn->auth_plugin_data) { + mnd_pefree(conn->auth_plugin_data, conn->persistent); + conn->auth_plugin_data = NULL; + } + conn->auth_plugin_data_len = plugin_data_len; + conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent); + if (!conn->auth_plugin_data) { + SET_OOM_ERROR(conn->error_info); + ret = FAIL; + goto end; + } + memcpy(conn->auth_plugin_data, plugin_data, plugin_data_len); + + DBG_INF_FMT("salt=[%*s]", plugin_data_len - 1, plugin_data); + + /* The data should be allocated with malloc() */ + scrambled_data = + auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len, + plugin_data, plugin_data_len, 0, conn->server_capabilities TSRMLS_CC); + + + ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, db, strlen(db), passwd_len, silent, + first_call, + requested_protocol, + scrambled_data, scrambled_data_len, + &switch_to_auth_protocol, &switch_to_auth_protocol_len, + &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len + TSRMLS_CC); + + first_call = FALSE; + free(scrambled_data); + + DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); + if (requested_protocol) { + mnd_efree(requested_protocol); + } + requested_protocol = switch_to_auth_protocol; + + if (plugin_data) { + mnd_efree(plugin_data); + } + plugin_data_len = switch_to_auth_protocol_data_len; + plugin_data = switch_to_auth_protocol_data; + } + DBG_INF_FMT("conn->error_info.error_no = %d", conn->error_info.error_no); + } while (ret == FAIL && conn->error_info.error_no == 0 && switch_to_auth_protocol != NULL); + if (plugin_data) { + mnd_efree(plugin_data); + } if (ret == PASS) { conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC); } - if (switch_to_auth_protocol) { - mnd_efree(switch_to_auth_protocol); - switch_to_auth_protocol = NULL; + if (requested_protocol) { + mnd_efree(requested_protocol); } } /* Here we should close all statements. Unbuffered queries should not be a problem as we won't allow sending COM_CHANGE_USER. */ +end: DBG_INF(ret == PASS? "PASS":"FAIL"); DBG_RETURN(ret); } @@ -2107,9 +2246,9 @@ MYSQLND_METHOD(mysqlnd_conn, set_client_option)(MYSQLND * const conn, break; case MYSQLND_OPT_AUTH_PROTOCOL: { - char * new_auth_protocol = mnd_pestrdup(value, conn->persistent); + char * new_auth_protocol = value? mnd_pestrdup(value, conn->persistent) : NULL; DBG_INF("MYSQLND_OPT_AUTH_PROTOCOL"); - if (!new_auth_protocol) { + if (value && !new_auth_protocol) { goto oom; } if (conn->options.auth_protocol) { @@ -2356,7 +2495,7 @@ PHPAPI void mysqlnd_library_init(TSRMLS_D) } mysqlnd_example_plugin_register(TSRMLS_C); mysqlnd_debug_trace_plugin_register(TSRMLS_C); - mysqlnd_native_authentication_plugin_register(TSRMLS_C); + mysqlnd_register_builtin_authentication_plugins(TSRMLS_C); } } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index 9183a88b69..184238f893 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -29,7 +29,7 @@ #include "mysqlnd_debug.h" -/* {{{ mysqlnd_native_auth_handshake */ +/* {{{ mysqlnd_auth_handshake */ enum_func_status mysqlnd_auth_handshake(MYSQLND * conn, const char * const user, @@ -37,93 +37,122 @@ mysqlnd_auth_handshake(MYSQLND * conn, const char * const db, const size_t db_len, const size_t passwd_len, - const MYSQLND_PACKET_GREET * const greet_packet, const MYSQLND_OPTIONS * const options, unsigned long mysql_flags, - struct st_mysqlnd_authentication_plugin * auth_plugin, - char ** switch_to_auth_protocol + 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) { enum_func_status ret = FAIL; const MYSQLND_CHARSET * charset = NULL; - MYSQLND_PACKET_OK * ok_packet = NULL; + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL; + MYSQLND_PACKET_AUTH_RESPONSE * auth_resp_packet = NULL; MYSQLND_PACKET_AUTH * auth_packet = NULL; - DBG_ENTER("mysqlnd_native_auth_handshake"); + DBG_ENTER("mysqlnd_auth_handshake"); - ok_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC); - auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + auth_resp_packet = conn->protocol->m.get_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); - if (!ok_packet || !auth_packet) { + if (!auth_resp_packet) { SET_OOM_ERROR(conn->error_info); goto end; } - auth_packet->client_flags = mysql_flags; - auth_packet->max_packet_size = options->max_allowed_packet; - if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) { - auth_packet->charset_no = charset->nr; + if (use_full_blown_auth_packet != TRUE) { + change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); + if (!change_auth_resp_packet) { + SET_OOM_ERROR(conn->error_info); + goto end; + } + + change_auth_resp_packet->auth_data = auth_plugin_data; + change_auth_resp_packet->auth_data_len = auth_plugin_data_len; + + if (!PACKET_WRITE(change_auth_resp_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } } else { + auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + + auth_packet->client_flags = mysql_flags; + auth_packet->max_packet_size = options->max_allowed_packet; + if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) { + auth_packet->charset_no = charset->nr; + } else { #if MYSQLND_UNICODE - auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */ + auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */ #else - auth_packet->charset_no = greet_packet->charset_no; + auth_packet->charset_no = server_charset_no; #endif - } - - auth_packet->send_auth_data = TRUE; - auth_packet->user = user; - auth_packet->db = db; - auth_packet->db_len = db_len; + } - conn->auth_plugin_data_len = greet_packet->auth_plugin_data_len; - conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent); - if (!conn->auth_plugin_data) { - SET_OOM_ERROR(conn->error_info); - goto end; - } - memcpy(conn->auth_plugin_data, greet_packet->auth_plugin_data, greet_packet->auth_plugin_data_len); + auth_packet->send_auth_data = TRUE; + auth_packet->user = user; + auth_packet->db = db; + auth_packet->db_len = db_len; - auth_packet->auth_data = - auth_plugin->methods.get_auth_data(NULL, &auth_packet->auth_data_len, conn, user, passwd, passwd_len, - conn->auth_plugin_data, conn->auth_plugin_data_len, options, mysql_flags TSRMLS_CC); - + auth_packet->auth_data = auth_plugin_data; + auth_packet->auth_data_len = auth_plugin_data_len; + auth_packet->auth_plugin_name = auth_protocol; - if (!PACKET_WRITE(auth_packet, conn)) { - CONN_SET_STATE(conn, CONN_QUIT_SENT); - SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); - goto end; + if (!PACKET_WRITE(auth_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } } - if (FAIL == PACKET_READ(ok_packet, conn) || ok_packet->field_count >= 0xFE) { - if (ok_packet->field_count == 0xFE) { + if (FAIL == PACKET_READ(auth_resp_packet, conn) || auth_resp_packet->response_code >= 0xFE) { + if (auth_resp_packet->response_code == 0xFE) { /* old authentication with new server !*/ - DBG_ERR(mysqlnd_old_passwd); - SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); - } else if (ok_packet->field_count == 0xFF) { - if (ok_packet->sqlstate[0]) { - strlcpy(conn->error_info.sqlstate, ok_packet->sqlstate, sizeof(conn->error_info.sqlstate)); - DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", ok_packet->error_no, ok_packet->sqlstate, ok_packet->error); + if (!auth_resp_packet->new_auth_protocol) { + DBG_ERR(mysqlnd_old_passwd); + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); + } else { + *switch_to_auth_protocol = mnd_pestrndup(auth_resp_packet->new_auth_protocol, auth_resp_packet->new_auth_protocol_len, FALSE); + *switch_to_auth_protocol_len = auth_resp_packet->new_auth_protocol_len; + if (auth_resp_packet->new_auth_protocol_data) { + *switch_to_auth_protocol_data_len = auth_resp_packet->new_auth_protocol_data_len; + *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len); + memcpy(*switch_to_auth_protocol_data, auth_resp_packet->new_auth_protocol_data, *switch_to_auth_protocol_data_len); + } else { + *switch_to_auth_protocol_data = NULL; + *switch_to_auth_protocol_data_len = 0; + } } - conn->error_info.error_no = ok_packet->error_no; - strlcpy(conn->error_info.error, ok_packet->error, sizeof(conn->error_info.error)); + } else if (auth_resp_packet->response_code == 0xFF) { + if (auth_resp_packet->sqlstate[0]) { + strlcpy(conn->error_info.sqlstate, auth_resp_packet->sqlstate, sizeof(conn->error_info.sqlstate)); + DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", auth_resp_packet->error_no, auth_resp_packet->sqlstate, auth_resp_packet->error); + } + conn->error_info.error_no = auth_resp_packet->error_no; + strlcpy(conn->error_info.error, auth_resp_packet->error, sizeof(conn->error_info.error)); } goto end; } - SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, ok_packet->message, ok_packet->message_len, conn->persistent); + SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, auth_resp_packet->message, auth_resp_packet->message_len, conn->persistent); conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no); ret = PASS; end: - mnd_efree(auth_packet->auth_data); + PACKET_FREE(change_auth_resp_packet); PACKET_FREE(auth_packet); - PACKET_FREE(ok_packet); + PACKET_FREE(auth_resp_packet); DBG_RETURN(ret); } /* }}} */ -/* {{{ mysqlnd_native_auth_change_user */ +/* {{{ mysqlnd_auth_change_user */ enum_func_status mysqlnd_auth_change_user(MYSQLND * const conn, const char * const user, @@ -133,8 +162,14 @@ mysqlnd_auth_change_user(MYSQLND * const conn, const size_t db_len, const size_t passwd_len, const zend_bool silent, - struct st_mysqlnd_authentication_plugin * auth_plugin, - char ** switch_to_auth_protocol + 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) { /* @@ -146,40 +181,87 @@ mysqlnd_auth_change_user(MYSQLND * const conn, char buffer[MYSQLND_MAX_ALLOWED_USER_LEN + 1 + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 2 /* charset*/ ]; char *p = buffer; const MYSQLND_CHARSET * old_cs = conn->charset; - MYSQLND_PACKET_AUTH * auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); - MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = conn->protocol->m.get_change_user_response_packet(conn->protocol, FALSE TSRMLS_CC); + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL; + MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = NULL; + MYSQLND_PACKET_AUTH * auth_packet = NULL; + + DBG_ENTER("mysqlnd_auth_change_user"); - DBG_ENTER("mysqlnd_native_auth_change_user"); + chg_user_resp = conn->protocol->m.get_change_user_response_packet(conn->protocol, FALSE TSRMLS_CC); - if (!auth_packet || !chg_user_resp) { + if (!chg_user_resp) { SET_OOM_ERROR(conn->error_info); goto end; } - auth_packet->is_change_user_packet = TRUE; - auth_packet->user = user; - auth_packet->db = db; - auth_packet->db_len = db_len; - auth_packet->silent = silent; + if (use_full_blown_auth_packet != TRUE) { + change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); + if (!change_auth_resp_packet) { + SET_OOM_ERROR(conn->error_info); + goto end; + } - auth_packet->auth_data = - auth_plugin->methods.get_auth_data(NULL, &auth_packet->auth_data_len, conn, user, passwd, passwd_len, - conn->auth_plugin_data, conn->auth_plugin_data_len, NULL /* options */, 0 /* mysql_flags */ TSRMLS_CC); + change_auth_resp_packet->auth_data = auth_plugin_data; + change_auth_resp_packet->auth_data_len = auth_plugin_data_len; - if (mysqlnd_get_server_version(conn) >= 50123) { - auth_packet->charset_no = conn->charset->nr; - p+=2; - } + if (!PACKET_WRITE(change_auth_resp_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } + } else { + auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + + if (!auth_packet) { + SET_OOM_ERROR(conn->error_info); + goto end; + } + + auth_packet->is_change_user_packet = TRUE; + auth_packet->user = user; + auth_packet->db = db; + auth_packet->db_len = db_len; + auth_packet->silent = silent; + + auth_packet->auth_data = auth_plugin_data; + auth_packet->auth_data_len = auth_plugin_data_len; + auth_packet->auth_plugin_name = auth_protocol; + + + if (mysqlnd_get_server_version(conn) >= 50123) { + auth_packet->charset_no = conn->charset->nr; + p+=2; + } - if (!PACKET_WRITE(auth_packet, conn)) { - CONN_SET_STATE(conn, CONN_QUIT_SENT); - SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); - goto end; + if (!PACKET_WRITE(auth_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } } ret = PACKET_READ(chg_user_resp, conn); conn->error_info = chg_user_resp->error_info; + if (0xFE == chg_user_resp->response_code) { + ret = FAIL; + if (!chg_user_resp->new_auth_protocol) { + DBG_ERR(mysqlnd_old_passwd); + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); + } else { + *switch_to_auth_protocol = mnd_pestrndup(chg_user_resp->new_auth_protocol, chg_user_resp->new_auth_protocol_len, FALSE); + *switch_to_auth_protocol_len = chg_user_resp->new_auth_protocol_len; + if (chg_user_resp->new_auth_protocol_data) { + *switch_to_auth_protocol_data_len = chg_user_resp->new_auth_protocol_data_len; + *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len); + memcpy(*switch_to_auth_protocol_data, chg_user_resp->new_auth_protocol_data, *switch_to_auth_protocol_data_len); + } else { + *switch_to_auth_protocol_data = NULL; + *switch_to_auth_protocol_data_len = 0; + } + } + } + if (conn->error_info.error_no) { ret = FAIL; /* @@ -228,7 +310,7 @@ mysqlnd_auth_change_user(MYSQLND * const conn, SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); } end: - mnd_efree(auth_packet->auth_data); + PACKET_FREE(change_auth_resp_packet); PACKET_FREE(auth_packet); PACKET_FREE(chg_user_resp); DBG_RETURN(ret); @@ -236,6 +318,51 @@ end: /* }}} */ +/******************************************* MySQL Native Password ***********************************/ + +#include "ext/standard/sha1.h" + +/* {{{ php_mysqlnd_crypt */ +static void +php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len) +{ + const zend_uchar *s1_end = s1 + len; + while (s1 < s1_end) { + *buffer++= *s1++ ^ *s2++; + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_scramble */ +void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, size_t password_len) +{ + PHP_SHA1_CTX context; + zend_uchar sha1[SHA1_MAX_LENGTH]; + zend_uchar sha2[SHA1_MAX_LENGTH]; + + /* Phase 1: hash password */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, password, password_len); + PHP_SHA1Final(sha1, &context); + + /* Phase 2: hash sha1 */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH); + PHP_SHA1Final(sha2, &context); + + /* Phase 3: hash scramble + sha2 */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH); + PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH); + PHP_SHA1Final(buffer, &context); + + /* let's crypt buffer now */ + php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH); +} +/* }}} */ + + /* {{{ mysqlnd_native_auth_get_auth_data */ static zend_uchar * mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self, @@ -250,7 +377,7 @@ mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self *auth_data_len = 0; /* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */ - if (auth_plugin_data_len != SCRAMBLE_LENGTH && (auth_plugin_data_len != 21)) { + if (auth_plugin_data_len < SCRAMBLE_LENGTH) { /* mysql_native_password only works with SCRAMBLE_LENGTH scramble */ SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble"); DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH); @@ -259,7 +386,7 @@ mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self /* copy scrambled pass*/ if (passwd && passwd_len) { - ret = mnd_emalloc(SCRAMBLE_LENGTH); + ret = malloc(SCRAMBLE_LENGTH); *auth_data_len = SCRAMBLE_LENGTH; /* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */ php_mysqlnd_scramble((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len); @@ -292,11 +419,59 @@ static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin = }; -/* {{{ mysqlnd_native_authentication_plugin_register */ +/******************************************* PAM Authentication ***********************************/ + +/* {{{ mysqlnd_pam_auth_get_auth_data */ +static zend_uchar * +mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self, + size_t * auth_data_len, + MYSQLND * 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) +{ + zend_uchar * ret = NULL; + + /* copy pass*/ + if (passwd && passwd_len) { + ret = (zend_uchar*) strndup(passwd, passwd_len); + } + *auth_data_len = passwd_len; + + return ret; +} +/* }}} */ + + +static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "auth_plugin_authentication_pam", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "Proprietatry", + "Andrey Hristov , Ulf Wendel , Georg Richter ", + { + NULL, /* no statistics , will be filled later if there are some */ + NULL, /* no statistics */ + }, + { + NULL /* plugin shutdown */ + } + }, + {/* methods */ + mysqlnd_pam_auth_get_auth_data + } +}; + + +/* {{{ mysqlnd_register_builtin_authentication_plugins */ void -mysqlnd_native_authentication_plugin_register(TSRMLS_D) +mysqlnd_register_builtin_authentication_plugins(TSRMLS_D) { mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin TSRMLS_CC); + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin TSRMLS_CC); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h index 79fee34176..e51b87a69d 100644 --- a/ext/mysqlnd/mysqlnd_enum_n_def.h +++ b/ext/mysqlnd/mysqlnd_enum_n_def.h @@ -100,7 +100,7 @@ #define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \ CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \ - CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_LOCAL_FILES) + CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_LOCAL_FILES | CLIENT_PLUGIN_AUTH) #define MYSQLND_NET_FLAG_USE_COMPRESSION 1 @@ -522,6 +522,8 @@ enum mysqlnd_packet_type { PROT_GREET_PACKET= 0, PROT_AUTH_PACKET, + PROT_AUTH_RESP_PACKET, + PROT_CHANGE_AUTH_RESP_PACKET, PROT_OK_PACKET, PROT_EOF_PACKET, PROT_CMD_PACKET, diff --git a/ext/mysqlnd/mysqlnd_net.c b/ext/mysqlnd/mysqlnd_net.c index 974f1918e9..0acc6ccd6e 100644 --- a/ext/mysqlnd/mysqlnd_net.c +++ b/ext/mysqlnd/mysqlnd_net.c @@ -227,7 +227,7 @@ MYSQLND_METHOD(mysqlnd_net, connect)(MYSQLND_NET * net, const char * const schem count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer) */ size_t -MYSQLND_METHOD(mysqlnd_net, send)(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_net, send)(MYSQLND * const conn, zend_uchar * const buf, size_t count TSRMLS_DC) { zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))]; zend_uchar *safe_storage = safe_buf; diff --git a/ext/mysqlnd/mysqlnd_priv.h b/ext/mysqlnd/mysqlnd_priv.h index 31c382f9a1..4bd063d7bf 100644 --- a/ext/mysqlnd/mysqlnd_priv.h +++ b/ext/mysqlnd/mysqlnd_priv.h @@ -192,7 +192,7 @@ void ps_fetch_from_1_to_8_bytes(zval *zv, const MYSQLND_FIELD * const field, void mysqlnd_plugin_subsystem_init(TSRMLS_D); void mysqlnd_plugin_subsystem_end(TSRMLS_D); -void mysqlnd_native_authentication_plugin_register(TSRMLS_D); +void mysqlnd_register_builtin_authentication_plugins(TSRMLS_D); void mysqlnd_example_plugin_register(TSRMLS_D); @@ -206,11 +206,17 @@ mysqlnd_auth_handshake(MYSQLND * conn, const char * const db, const size_t db_len, const size_t passwd_len, - const struct st_mysqlnd_packet_greet * const greet_packet, const MYSQLND_OPTIONS * const options, unsigned long mysql_flags, - struct st_mysqlnd_authentication_plugin * auth_plugin, - char ** switch_to_auth_protocol + 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); enum_func_status @@ -222,8 +228,14 @@ mysqlnd_auth_change_user(MYSQLND * const conn, const size_t db_len, const size_t passwd_len, const zend_bool silent, - struct st_mysqlnd_authentication_plugin * auth_plugin, - char ** switch_to_auth_protocol + 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); #endif /* MYSQLND_PRIV_H */ diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h index a081468ef6..f043ea9eb8 100644 --- a/ext/mysqlnd/mysqlnd_structs.h +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -252,7 +252,7 @@ typedef struct st_mysqlnd_read_buffer { typedef enum_func_status (*func_mysqlnd_net__connect)(MYSQLND_NET * net, const char * const scheme, size_t scheme_len, zend_bool persistent, char **errstr, int * errcode TSRMLS_DC); -typedef size_t (*func_mysqlnd_net__send)(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC); +typedef size_t (*func_mysqlnd_net__send)(MYSQLND * const conn, zend_uchar * const buf, size_t count TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_net__receive)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_net__set_client_option)(MYSQLND_NET * const net, enum_mysqlnd_option option, const char * const value TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_net__network_read)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC); @@ -304,6 +304,8 @@ struct st_mysqlnd_packet_auth_pam; typedef struct st_mysqlnd_packet_greet * (*func_mysqlnd_protocol__get_greet_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); typedef struct st_mysqlnd_packet_auth * (*func_mysqlnd_protocol__get_auth_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_auth_response *(*func_mysqlnd_protocol__get_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_change_auth_response * (*func_mysqlnd_protocol__get_change_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); typedef struct st_mysqlnd_packet_ok * (*func_mysqlnd_protocol__get_ok_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); typedef struct st_mysqlnd_packet_command * (*func_mysqlnd_protocol__get_command_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); typedef struct st_mysqlnd_packet_eof * (*func_mysqlnd_protocol__get_eof_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); @@ -318,6 +320,8 @@ struct st_mysqlnd_protocol_methods { func_mysqlnd_protocol__get_greet_packet get_greet_packet; func_mysqlnd_protocol__get_auth_packet get_auth_packet; + func_mysqlnd_protocol__get_auth_response_packet get_auth_response_packet; + func_mysqlnd_protocol__get_change_auth_response_packet get_change_auth_response_packet; func_mysqlnd_protocol__get_ok_packet get_ok_packet; func_mysqlnd_protocol__get_command_packet get_command_packet; func_mysqlnd_protocol__get_eof_packet get_eof_packet; @@ -396,7 +400,7 @@ typedef enum_func_status (*func_mysqlnd_conn__free_reference)(MYSQLND * const co typedef enum mysqlnd_connection_state (*func_mysqlnd_conn__get_state)(MYSQLND * const conn TSRMLS_DC); typedef void (*func_mysqlnd_conn__set_state)(MYSQLND * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC); -typedef enum_func_status (*func_mysqlnd_conn__simple_command)(MYSQLND *conn, enum php_mysqlnd_server_command command, const char * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn__simple_command)(MYSQLND *conn, enum php_mysqlnd_server_command command, const zend_uchar * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_conn__simple_command_handle_response)(MYSQLND *conn, enum mysqlnd_packet_type ok_packet, zend_bool silent, enum php_mysqlnd_server_command command, zend_bool ignore_upsert_status TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_conn__restart_psession)(MYSQLND *conn TSRMLS_DC); diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index e7205066f3..c05b1e2462 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -25,7 +25,6 @@ #include "mysqlnd_statistics.h" #include "mysqlnd_debug.h" #include "mysqlnd_block_alloc.h" -#include "ext/standard/sha1.h" #include "zend_ini.h" #define MYSQLND_SILENT 1 @@ -459,57 +458,16 @@ void php_mysqlnd_greet_free_mem(void *_packet, zend_bool stack_allocation TSRMLS /* }}} */ -/* {{{ php_mysqlnd_crypt */ -static void -php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len) -{ - const zend_uchar *s1_end = s1 + len; - while (s1 < s1_end) { - *buffer++= *s1++ ^ *s2++; - } -} -/* }}} */ - - -/* {{{ php_mysqlnd_scramble */ -void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, size_t password_len) -{ - PHP_SHA1_CTX context; - zend_uchar sha1[SHA1_MAX_LENGTH]; - zend_uchar sha2[SHA1_MAX_LENGTH]; - - /* Phase 1: hash password */ - PHP_SHA1Init(&context); - PHP_SHA1Update(&context, password, password_len); - PHP_SHA1Final(sha1, &context); - - /* Phase 2: hash sha1 */ - PHP_SHA1Init(&context); - PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH); - PHP_SHA1Final(sha2, &context); - - /* Phase 3: hash scramble + sha2 */ - PHP_SHA1Init(&context); - PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH); - PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH); - PHP_SHA1Final(buffer, &context); - - /* let's crypt buffer now */ - php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH); -} -/* }}} */ - - #define AUTH_WRITE_BUFFER_LEN (MYSQLND_HEADER_SIZE + MYSQLND_MAX_ALLOWED_USER_LEN + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 1024) /* {{{ php_mysqlnd_auth_write */ static size_t php_mysqlnd_auth_write(void *_packet, MYSQLND * conn TSRMLS_DC) { - char buffer[AUTH_WRITE_BUFFER_LEN]; - register char *p= buffer + MYSQLND_HEADER_SIZE; /* start after the header */ + zend_uchar buffer[AUTH_WRITE_BUFFER_LEN]; + zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */ int len; - register MYSQLND_PACKET_AUTH *packet= (MYSQLND_PACKET_AUTH *) _packet; + MYSQLND_PACKET_AUTH * packet= (MYSQLND_PACKET_AUTH *) _packet; DBG_ENTER("php_mysqlnd_auth_write"); @@ -534,8 +492,8 @@ size_t php_mysqlnd_auth_write(void *_packet, MYSQLND * conn TSRMLS_DC) *p++ = '\0'; /* defensive coding */ - if (!packet->auth_data) { - packet->auth_data = 0; + if (packet->auth_data == NULL) { + packet->auth_data_len = 0; } int1store(p, packet->auth_data_len); ++p; @@ -555,6 +513,8 @@ size_t php_mysqlnd_auth_write(void *_packet, MYSQLND * conn TSRMLS_DC) memcpy(p, packet->db, real_db_len); p+= real_db_len; *p++= '\0'; + } else if (packet->is_change_user_packet) { + *p++= '\0'; } /* no \0 for no DB */ @@ -563,13 +523,13 @@ size_t php_mysqlnd_auth_write(void *_packet, MYSQLND * conn TSRMLS_DC) int2store(p, packet->charset_no); p+= 2; } - } else { - if (packet->auth_plugin_name) { - size_t len = MIN(strlen(packet->auth_plugin_name), sizeof(buffer) - (p - buffer) - 1); - memcpy(p, packet->auth_plugin_name, len); - p+= len; - *p++= '\0'; - } + } + + if (packet->auth_plugin_name) { + size_t len = MIN(strlen(packet->auth_plugin_name), sizeof(buffer) - (p - buffer) - 1); + memcpy(p, packet->auth_plugin_name, len); + p+= len; + *p++= '\0'; } } if (packet->is_change_user_packet) { @@ -598,6 +558,166 @@ void php_mysqlnd_auth_free_mem(void *_packet, zend_bool stack_allocation TSRMLS_ /* }}} */ +#define AUTH_RESP_BUFFER_SIZE 2048 + +/* {{{ php_mysqlnd_auth_response_read */ +static enum_func_status +php_mysqlnd_auth_response_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + zend_uchar local_buf[AUTH_RESP_BUFFER_SIZE]; + size_t buf_len = conn->net->cmd_buffer.buffer? conn->net->cmd_buffer.length: AUTH_RESP_BUFFER_SIZE; + zend_uchar *buf = conn->net->cmd_buffer.buffer? (zend_uchar *) conn->net->cmd_buffer.buffer : local_buf; + zend_uchar *p = buf; + zend_uchar *begin = buf; + unsigned long i; + register MYSQLND_PACKET_AUTH_RESPONSE * packet= (MYSQLND_PACKET_AUTH_RESPONSE *) _packet; + + DBG_ENTER("php_mysqlnd_auth_response_read"); + + /* leave space for terminating safety \0 */ + buf_len--; + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "OK", PROT_OK_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* + zero-terminate the buffer for safety. We are sure there is place for the \0 + because buf_len is -1 the size of the buffer pointed + */ + buf[packet->header.size] = '\0'; + + /* Should be always 0x0 or ERROR_MARKER for error */ + packet->response_code = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->response_code) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + if (0xFE == packet->response_code) { + /* Authentication Switch Response */ + if (packet->header.size > (size_t) (p - buf)) { + packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); + packet->new_auth_protocol_len = strlen(packet->new_auth_protocol); + p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */ + + packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf); + if (packet->new_auth_protocol_data_len) { + packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len); + memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len); + } + DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol); + DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data); + } + } else { + /* Everything was fine! */ + packet->affected_rows = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + packet->warning_count = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + /* There is a message */ + if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) { + packet->message_len = MIN(i, buf_len - (p - begin)); + packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); + } else { + packet->message = NULL; + packet->message_len = 0; + } + + DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%u warnings=%u", + packet->affected_rows, packet->last_insert_id, packet->server_status, + packet->warning_count); + } + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("OK packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "AUTH_RESPONSE packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_auth_response_free_mem */ +static void +php_mysqlnd_auth_response_free_mem(void *_packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_AUTH_RESPONSE * p = (MYSQLND_PACKET_AUTH_RESPONSE *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (p->new_auth_protocol) { + mnd_efree(p->new_auth_protocol); + p->new_auth_protocol = NULL; + } + p->new_auth_protocol_len = 0; + + if (p->new_auth_protocol_data) { + mnd_efree(p->new_auth_protocol_data); + p->new_auth_protocol_data = NULL; + } + p->new_auth_protocol_data_len = 0; + + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_change_auth_response_write */ +static +size_t php_mysqlnd_change_auth_response_write(void *_packet, MYSQLND * conn TSRMLS_DC) +{ + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *packet= (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet; + zend_uchar * buffer = conn->net->cmd_buffer.length >= packet->auth_data_len? conn->net->cmd_buffer.buffer : mnd_emalloc(packet->auth_data_len); + zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */ + + DBG_ENTER("php_mysqlnd_change_auth_response_write"); + + if (packet->auth_data_len) { + memcpy(p, packet->auth_data, packet->auth_data_len); + p+= packet->auth_data_len; + } + + { + size_t ret = conn->net->m.send(conn, buffer, p - buffer - MYSQLND_HEADER_SIZE TSRMLS_CC); + if (buffer != conn->net->cmd_buffer.buffer) { + mnd_efree(buffer); + } + DBG_RETURN(ret); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_change_auth_response_free_mem */ +static +void php_mysqlnd_change_auth_response_free_mem(void *_packet, zend_bool stack_allocation TSRMLS_DC) +{ + if (!stack_allocation) { + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * p = (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet; + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + #define OK_BUFFER_SIZE 2048 /* {{{ php_mysqlnd_ok_read */ @@ -791,7 +911,7 @@ size_t php_mysqlnd_cmd_write(void *_packet, MYSQLND *conn TSRMLS_DC) #endif if (!packet->argument || !packet->arg_len) { - char buffer[MYSQLND_HEADER_SIZE + 1]; + zend_uchar buffer[MYSQLND_HEADER_SIZE + 1]; int1store(buffer + MYSQLND_HEADER_SIZE, packet->command); written = conn->net->m.send(conn, buffer, 1 TSRMLS_CC); @@ -809,7 +929,7 @@ size_t php_mysqlnd_cmd_write(void *_packet, MYSQLND *conn TSRMLS_DC) memcpy(p, packet->argument, packet->arg_len); - ret = conn->net->m.send(conn, (char *)tmp, tmp_len - MYSQLND_HEADER_SIZE TSRMLS_CC); + ret = conn->net->m.send(conn, tmp, tmp_len - MYSQLND_HEADER_SIZE TSRMLS_CC); if (tmp != net->cmd_buffer.buffer) { MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CMD_BUFFER_TOO_SMALL); mnd_efree(tmp); @@ -1875,7 +1995,7 @@ php_mysqlnd_chg_user_read(void *_packet, MYSQLND *conn TSRMLS_DC) */ /* Should be always 0x0 or ERROR_MARKER for error */ - packet->field_count= uint1korr(p); + packet->response_code = uint1korr(p); p++; if (packet->header.size == 1 && buf[0] == EODATA_MARKER && packet->server_capabilities & CLIENT_SECURE_CONNECTION) { @@ -1884,7 +2004,7 @@ php_mysqlnd_chg_user_read(void *_packet, MYSQLND *conn TSRMLS_DC) DBG_RETURN(FAIL); } - if (ERROR_MARKER == packet->field_count) { + if (ERROR_MARKER == packet->response_code) { php_mysqlnd_read_error_from_line(p, packet->header.size - 1, packet->error_info.error, sizeof(packet->error_info.error), @@ -1893,6 +2013,18 @@ php_mysqlnd_chg_user_read(void *_packet, MYSQLND *conn TSRMLS_DC) TSRMLS_CC); } BAIL_IF_NO_MORE_DATA; + if (packet->response_code == 0xFE && packet->header.size > (size_t) (p - buf)) { + packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); + packet->new_auth_protocol_len = strlen(packet->new_auth_protocol); + p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */ + packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf); + if (packet->new_auth_protocol_data_len) { + packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len); + memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len); + } + DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol); + DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data); + } DBG_RETURN(PASS); premature_end: @@ -1908,8 +2040,22 @@ premature_end: static void php_mysqlnd_chg_user_free_mem(void *_packet, zend_bool stack_allocation TSRMLS_DC) { + MYSQLND_PACKET_CHG_USER_RESPONSE * p = (MYSQLND_PACKET_CHG_USER_RESPONSE *) _packet; + + if (p->new_auth_protocol) { + mnd_efree(p->new_auth_protocol); + p->new_auth_protocol = NULL; + } + p->new_auth_protocol_len = 0; + + if (p->new_auth_protocol_data) { + mnd_efree(p->new_auth_protocol_data); + p->new_auth_protocol_data = NULL; + } + p->new_auth_protocol_data_len = 0; + if (!stack_allocation) { - mnd_pefree(_packet, ((MYSQLND_PACKET_CHG_USER_RESPONSE *)_packet)->header.persistent); + mnd_pefree(p, p->header.persistent); } } /* }}} */ @@ -1931,6 +2077,18 @@ mysqlnd_packet_methods packet_methods[PROT_LAST] = php_mysqlnd_auth_write, php_mysqlnd_auth_free_mem, }, /* PROT_AUTH_PACKET */ + { + sizeof(MYSQLND_PACKET_AUTH_RESPONSE), + php_mysqlnd_auth_response_read, /* read */ + NULL, /* write */ + php_mysqlnd_auth_response_free_mem, + }, /* PROT_AUTH_RESP_PACKET */ + { + sizeof(MYSQLND_PACKET_CHANGE_AUTH_RESPONSE), + NULL, /* read */ + php_mysqlnd_change_auth_response_write, /* write */ + php_mysqlnd_change_auth_response_free_mem, + }, /* PROT_CHANGE_AUTH_RESP_PACKET */ { sizeof(MYSQLND_PACKET_OK), php_mysqlnd_ok_read, /* read */ @@ -2019,6 +2177,36 @@ MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet)(MYSQLND_PROTOCOL * const proto /* }}} */ +/* {{{ mysqlnd_protocol::get_auth_response_packet */ +static struct st_mysqlnd_packet_auth_response * +MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_AUTH_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_auth_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_AUTH_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_change_auth_response_packet */ +static struct st_mysqlnd_packet_change_auth_response * +MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_change_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_CHANGE_AUTH_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_change_auth_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_CHANGE_AUTH_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + /* {{{ mysqlnd_protocol::get_ok_packet */ static struct st_mysqlnd_packet_ok * MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) @@ -2158,6 +2346,8 @@ static MYSQLND_CLASS_METHODS_START(mysqlnd_protocol) MYSQLND_METHOD(mysqlnd_protocol, get_greet_packet), MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet), MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet), MYSQLND_METHOD(mysqlnd_protocol, get_command_packet), MYSQLND_METHOD(mysqlnd_protocol, get_eof_packet), diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index eb1b418166..7ce5ca16e1 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -82,7 +82,7 @@ typedef struct st_mysqlnd_packet_greet { /* If error packet, we use these */ char error[MYSQLND_ERRMSG_SIZE+1]; char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; - unsigned int error_no; + unsigned int error_no; char *auth_protocol; } MYSQLND_PACKET_GREET; @@ -94,10 +94,10 @@ typedef struct st_mysqlnd_packet_auth { uint32_t max_packet_size; uint8_t charset_no; const char *user; - zend_uchar *auth_data; + const zend_uchar *auth_data; size_t auth_data_len; const char *db; - char *auth_plugin_name; + const char *auth_plugin_name; /* Here the packet ends. This is user supplied data */ size_t db_len; zend_bool send_auth_data; @@ -106,6 +106,36 @@ typedef struct st_mysqlnd_packet_auth { } MYSQLND_PACKET_AUTH; +/* Auth response packet */ +typedef struct st_mysqlnd_packet_auth_response { + MYSQLND_PACKET_HEADER header; + uint8_t response_code; + uint64_t affected_rows; + uint64_t last_insert_id; + uint16_t server_status; + uint16_t warning_count; + char *message; + size_t message_len; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; + + char *new_auth_protocol; + size_t new_auth_protocol_len; + zend_uchar *new_auth_protocol_data; + size_t new_auth_protocol_data_len; +} MYSQLND_PACKET_AUTH_RESPONSE; + + +/* Auth response packet */ +typedef struct st_mysqlnd_packet_change_auth_response { + MYSQLND_PACKET_HEADER header; + const zend_uchar *auth_data; + size_t auth_data_len; +} MYSQLND_PACKET_CHANGE_AUTH_RESPONSE; + + /* OK packet */ typedef struct st_mysqlnd_packet_ok { MYSQLND_PACKET_HEADER header; @@ -127,7 +157,7 @@ typedef struct st_mysqlnd_packet_ok { typedef struct st_mysqlnd_packet_command { MYSQLND_PACKET_HEADER header; enum php_mysqlnd_server_command command; - const char *argument; + const zend_uchar *argument; size_t arg_len; } MYSQLND_PACKET_COMMAND; @@ -241,13 +271,18 @@ typedef struct st_mysqlnd_packet_prepare_response { /* Statistics packet */ typedef struct st_mysqlnd_packet_chg_user_resp { MYSQLND_PACKET_HEADER header; - uint32_t field_count; + uint32_t response_code; /* message_len is not part of the packet*/ uint16_t server_capabilities; /* If error packet, we use these */ MYSQLND_ERROR_INFO error_info; zend_bool server_asked_323_auth; + + char *new_auth_protocol; + size_t new_auth_protocol_len; + zend_uchar *new_auth_protocol_data; + size_t new_auth_protocol_data_len; } MYSQLND_PACKET_CHG_USER_RESPONSE;