]> granicus.if.org Git - php/commitdiff
grok the MySQL 5.5 extended handshake.
authorAndrey Hristov <andrey@php.net>
Wed, 12 Jan 2011 21:40:05 +0000 (21:40 +0000)
committerAndrey Hristov <andrey@php.net>
Wed, 12 Jan 2011 21:40:05 +0000 (21:40 +0000)
Move the authentication routines, the native ones, to
separate file and encapsulate them in a plugin.
Depending on the server version and what the server
requests (or doesn't in old versions) load the authentication
plugin to handle it.
Currently only the 4.1+ authentication is supported. More to come

ext/mysqlnd/config.w32
ext/mysqlnd/config9.m4
ext/mysqlnd/mysqlnd.c
ext/mysqlnd/mysqlnd_auth.c [new file with mode: 0644]
ext/mysqlnd/mysqlnd_enum_n_def.h
ext/mysqlnd/mysqlnd_priv.h
ext/mysqlnd/mysqlnd_structs.h
ext/mysqlnd/mysqlnd_wireprotocol.c
ext/mysqlnd/mysqlnd_wireprotocol.h

index 6246ce11e449f7b938bbcfa5531fc8f4942373d5..59e0b0d03e990ef8aaaa4d78a05c59efedcd8c40 100644 (file)
@@ -7,6 +7,7 @@ if (PHP_MYSQLND != "no") {
        if (CHECK_LIB("ws2_32.lib", "mysqlnd")) {
                mysqlnd_source = 
                        "mysqlnd.c " +
+                       "mysqlnd_auth.c" +
                        "mysqlnd_block_alloc.c " +
                        "mysqlnd_charset.c " +
                        "mysqlnd_debug.c " +
index 470f215a6646eef085438d0661ae9bf37887f78b..9104a85889aac42bf8ef181616ffd0fd6eb870a0 100644 (file)
@@ -16,7 +16,7 @@ dnl If some extension uses mysqlnd it will get compiled in PHP core
 if test "$PHP_MYSQLND_ENABLED" = "yes"; then
   mysqlnd_sources="mysqlnd.c mysqlnd_charset.c mysqlnd_wireprotocol.c \
                    mysqlnd_ps.c mysqlnd_loaddata.c mysqlnd_net.c \
-                   mysqlnd_ps_codec.c mysqlnd_statistics.c \
+                   mysqlnd_ps_codec.c mysqlnd_statistics.c mysqlnd_auth.c \
                                   mysqlnd_result.c mysqlnd_result_meta.c mysqlnd_debug.c\
                                   mysqlnd_block_alloc.c mysqlnd_plugin.c php_mysqlnd.c"
 
index 6ee994c097dcbaca7618b69b12cba109100ab876..94cd5b89a88fd294456daa3c7079d1e13d6679f2 100644 (file)
@@ -92,6 +92,10 @@ MYSQLND_METHOD(mysqlnd_conn, free_options)(MYSQLND * conn TSRMLS_DC)
                mnd_pefree(conn->options.charset_name, pers);
                conn->options.charset_name = NULL;
        }
+       if (conn->options.auth_protocol) {
+               mnd_pefree(conn->options.auth_protocol, pers);
+               conn->options.auth_protocol = NULL;
+       }
        if (conn->options.num_commands) {
                unsigned int i;
                for (i = 0; i < conn->options.num_commands; i++) {
@@ -427,9 +431,8 @@ MYSQLND_METHOD(mysqlnd_conn, end_psession)(MYSQLND * conn TSRMLS_DC)
 /* }}} */
 
 
-#define MYSQLND_ASSEMBLED_PACKET_MAX_SIZE 3UL*1024UL*1024UL*1024UL
 /* {{{ mysqlnd_switch_to_ssl_if_needed */
-static MYSQLND_PACKET_AUTH *
+static enum_func_status
 mysqlnd_switch_to_ssl_if_needed(
                        MYSQLND * conn,
                        const MYSQLND_PACKET_GREET * const greet_packet,
@@ -438,13 +441,15 @@ mysqlnd_switch_to_ssl_if_needed(
                        TSRMLS_DC
                )
 {
-       const MYSQLND_CHARSET * charset = NULL;
-       MYSQLND_PACKET_AUTH * auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC);
+       enum_func_status ret = FAIL;
+       const MYSQLND_CHARSET * charset;
+       MYSQLND_PACKET_AUTH * auth_packet;
        DBG_ENTER("mysqlnd_switch_to_ssl_if_needed");
 
+       auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC);
        if (!auth_packet) {
                SET_OOM_ERROR(conn->error_info);
-               goto err;
+               goto end;
        }
        auth_packet->client_flags = mysql_flags;
        auth_packet->max_packet_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE;
@@ -466,20 +471,20 @@ mysqlnd_switch_to_ssl_if_needed(
                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 err;
+                       goto end;
                }
 
                conn->net->m.set_client_option(conn->net, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (const char *) &verify TSRMLS_CC);
 
                if (FAIL == conn->net->m.enable_ssl(conn->net TSRMLS_CC)) {
-                       goto err;
+                       goto end;
                }
        }
 #endif
-       DBG_RETURN(auth_packet);
-err:
+       ret = PASS;
+end:
        PACKET_FREE(auth_packet);
-       DBG_RETURN(NULL);
+       DBG_RETURN(ret);
 }
 /* }}} */
 
@@ -498,76 +503,52 @@ mysqlnd_connect_run_authentication(
                        TSRMLS_DC)
 {
        enum_func_status ret = FAIL;
-       MYSQLND_PACKET_AUTH * auth_packet = NULL;
-       MYSQLND_PACKET_OK * ok_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC);
-
        DBG_ENTER("mysqlnd_connect_run_authentication");
 
-       if (!ok_packet) {
-               SET_OOM_ERROR(conn->error_info);
-               goto err;
-       }
-
-       auth_packet = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC);
+       ret = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC);
+       if (PASS == ret) {
+               char * switch_to_auth_protocol = NULL;
+               char * requested_protocol = NULL;
+
+               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, "Server requested authentication method uknown to the client [%s]", requested_protocol);
+                               SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Server requested authentication method uknown to the client");
+                               break;
+                       }
 
-       if (!auth_packet) {
-               goto err;
-       }
+                       DBG_INF("plugin found");
 
-       auth_packet->send_auth_data = TRUE;
-       auth_packet->user               = user;
-       auth_packet->password   = passwd;
-       auth_packet->db                 = db;
-       auth_packet->db_len             = db_len;
+                       ret = auth_plugin->methods.auth_handshake(conn, user, passwd, db, db_len, greet_packet, options, mysql_flags,
+                                                                                                         &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);
 
-       conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(SCRAMBLE_LENGTH, conn->persistent);
-       if (!conn->scramble) {
-               SET_OOM_ERROR(conn->error_info);
-               goto err;
-       }
-       memcpy(auth_packet->server_scramble_buf, greet_packet->scramble_buf, SCRAMBLE_LENGTH);
-
-       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 err;
-       }
+               if (ret == PASS) {
+                       conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC);
+               }
 
-       if (FAIL == PACKET_READ(ok_packet, conn) || ok_packet->field_count >= 0xFE) {
-               if (ok_packet->field_count == 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);
-                       }
-                       conn->error_info.error_no = ok_packet->error_no;
-                       strlcpy(conn->error_info.error, ok_packet->error, sizeof(conn->error_info.error));
+               if (switch_to_auth_protocol) {
+                       mnd_efree(switch_to_auth_protocol);
+                       switch_to_auth_protocol = NULL;
                }
-               goto err;
        }
-
-       SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
-                                       ok_packet->message, ok_packet->message_len,
-                                       conn->persistent);
-       conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no);
-       ret = PASS;
-err:
-       PACKET_FREE(auth_packet);
-       PACKET_FREE(ok_packet);
        DBG_RETURN(ret);
 }
 /* }}} */
 
 
-#define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \
-                               CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \
-                               CLIENT_MULTI_RESULTS)
-
-
-
 /* {{{ mysqlnd_conn::connect */
 static enum_func_status
 MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn,
@@ -594,7 +575,7 @@ MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn,
                                host?host:"", user?user:"", db?db:"", port, mysql_flags,
                                conn? conn->persistent:0, conn? CONN_GET_STATE(conn):-1);
 
-       if (conn && CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) {
+       if (CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) {
                DBG_INF("Connecting on a connected handle.");
 
                if (CONN_GET_STATE(conn) < CONN_QUIT_SENT) {
@@ -618,6 +599,9 @@ MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn,
                        saved_compression = TRUE;
                        conn->net->compressed = FALSE;
                }
+       } else {
+               unsigned int max_allowed_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE;
+               conn->m->set_client_option(conn, MYSQLND_OPT_MAX_ALLOWED_PACKET, (char *)&max_allowed_size TSRMLS_CC);
        }
 
        if (!host || !host[0]) {
@@ -714,7 +698,6 @@ MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn,
 
        conn->greet_charset = mysqlnd_find_charset_nr(greet_packet->charset_no);
        /* we allow load data local infile by default */
-       mysql_flags |= CLIENT_LOCAL_FILES | CLIENT_PS_MULTI_RESULTS;
        mysql_flags |= MYSQLND_CAPABILITIES;
 
        if (db) {
@@ -1936,15 +1919,7 @@ MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn,
          Stack space is not that expensive, so use a bit more to be protected against
          buffer overflows.
        */
-       size_t user_len, db_len;
        enum_func_status ret = FAIL;
-       MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = NULL;
-       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);
-
 
        DBG_ENTER("mysqlnd_conn::change_user");
        DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s silent=%u",
@@ -1952,11 +1927,6 @@ MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn,
 
        SET_ERROR_AFF_ROWS(conn);
 
-       if (!auth_packet) {
-               SET_OOM_ERROR(conn->error_info);
-               goto end;
-       }
-
        if (!user) {
                user = "";
        }
@@ -1966,86 +1936,43 @@ MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn,
        if (!db) {
                db = "";
        }
-       user_len = strlen(user);
-       db_len = strlen(db);
-
-       auth_packet->is_change_user_packet = TRUE;
-       auth_packet->user               = user;
-       auth_packet->password   = passwd;
-       auth_packet->db                 = db;
-       auth_packet->db_len             = db_len;
-       auth_packet->server_scramble_buf = conn->scramble;
-       auth_packet->silent             = silent;
-       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;
-       }
-
-       chg_user_resp = conn->protocol->m.get_change_user_response_packet(conn->protocol, FALSE TSRMLS_CC);
-       if (!chg_user_resp) {
-               SET_OOM_ERROR(conn->error_info);
-               goto end;
-       }
-       ret = PACKET_READ(chg_user_resp, conn);
-       conn->error_info = chg_user_resp->error_info;
-
-       if (conn->error_info.error_no) {
-               ret = FAIL;
-               /*
-                 COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
-                 bug#25371 mysql_change_user() triggers "packets out of sync"
-                 When it gets fixed, there should be one more check here
-               */
-               if (mysqlnd_get_server_version(conn) > 50113L && mysqlnd_get_server_version(conn) < 50118L) {
-                       MYSQLND_PACKET_OK * redundant_error_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC);
-                       if (redundant_error_packet) {
-                               PACKET_READ(redundant_error_packet, conn);
-                               PACKET_FREE(redundant_error_packet);
-                               DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", mysqlnd_get_server_version(conn));
-                       } else {
-                               SET_OOM_ERROR(conn->error_info);
-                       }
-               }
-       }
-       if (ret == PASS) {
-               char * tmp = NULL;
-               /* if we get conn->user as parameter and then we first free it, then estrndup it, we will crash */
-               tmp = mnd_pestrndup(user, user_len, conn->persistent);
-               if (conn->user) {
-                       mnd_pefree(conn->user, conn->persistent);
-               }
-               conn->user = tmp;
-
-               tmp = mnd_pestrdup(passwd, conn->persistent);
-               if (conn->passwd) {
-                       mnd_pefree(conn->passwd, conn->persistent);
-               }
-               conn->passwd = tmp;
 
-               if (conn->last_message) {
-                       mnd_pefree(conn->last_message, conn->persistent);
-                       conn->last_message = NULL;
+       {
+               char * switch_to_auth_protocol = NULL;
+               const char * requested_protocol = NULL;
+
+               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, "Server requested authentication method uknown to the client [%s]", requested_protocol);
+                               }
+                               SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Server requested authentication method uknown to the client");
+                               break;
+                       }       
+                       DBG_INF("plugin found");
+                       ret = auth_plugin->methods.auth_change_user(conn, user, strlen(user), passwd, db, strlen(db), silent, &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);
+               if (ret == PASS) {
+                       conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC);
                }
-               memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
-               /* set charset for old servers */
-               if (mysqlnd_get_server_version(conn) < 50123) {
-                       ret = conn->m->set_charset(conn, old_cs->name TSRMLS_CC);
+               if (switch_to_auth_protocol) {
+                       mnd_efree(switch_to_auth_protocol);
+                       switch_to_auth_protocol = NULL;
                }
-       } else if (ret == FAIL && chg_user_resp->server_asked_323_auth == TRUE) {
-               /* old authentication with new server  !*/
-               DBG_ERR(mysqlnd_old_passwd);
-               SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
        }
-end:
-       PACKET_FREE(auth_packet);
-       PACKET_FREE(chg_user_resp);
-
        /*
          Here we should close all statements. Unbuffered queries should not be a
          problem as we won't allow sending COM_CHANGE_USER.
@@ -2165,7 +2092,25 @@ MYSQLND_METHOD(mysqlnd_conn, set_client_option)(MYSQLND * const conn,
                        /* todo: throw an error, we don't support embedded */
                        break;
 #endif
-
+               case MYSQLND_OPT_MAX_ALLOWED_PACKET:
+                       if (*(unsigned int*) value > (1<<16)) {
+                               conn->options.max_allowed_packet = *(unsigned int*) value;
+                       }
+                       break;
+               case MYSQLND_OPT_AUTH_PROTOCOL:
+               {
+                       char * new_auth_protocol = mnd_pestrdup(value, conn->persistent);
+                       DBG_INF("MYSQLND_OPT_AUTH_PROTOCOL");
+                       if (!new_auth_protocol) {
+                               goto oom;
+                       }
+                       if (conn->options.auth_protocol) {
+                               mnd_pefree(conn->options.auth_protocol, conn->persistent);
+                       }
+                       conn->options.auth_protocol = new_auth_protocol;
+                       DBG_INF_FMT("auth_protocol=%s", conn->options.auth_protocol);
+                       break;
+               }
 #ifdef WHEN_SUPPORTED_BY_MYSQLI
                case MYSQL_SHARED_MEMORY_BASE_NAME:
                case MYSQL_OPT_USE_RESULT:
@@ -2403,6 +2348,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);
        }
 }
 /* }}} */
diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c
new file mode 100644 (file)
index 0000000..cc360cb
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 2006-2011 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | license@php.net so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Authors: Georg Richter <georg@mysql.com>                             |
+  |          Andrey Hristov <andrey@mysql.com>                           |
+  |          Ulf Wendel <uwendel@mysql.com>                              |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id: mysqlnd.c 307377 2011-01-11 13:02:57Z andrey $ */
+#include "php.h"
+#include "mysqlnd.h"
+#include "mysqlnd_structs.h"
+#include "mysqlnd_wireprotocol.h"
+#include "mysqlnd_priv.h"
+#include "mysqlnd_result.h"
+#include "mysqlnd_charset.h"
+#include "mysqlnd_debug.h"
+
+
+/* {{{ mysqlnd_native_auth_handshake */
+static enum_func_status
+mysqlnd_native_auth_handshake(MYSQLND * conn,
+                                                         const char * const user,
+                                                         const char * const passwd,
+                                                         const char * const db,
+                                                         const size_t db_len,
+                                                         const MYSQLND_PACKET_GREET * const greet_packet,
+                                                         const MYSQLND_OPTIONS * const options,
+                                                         unsigned long mysql_flags,
+                                                         char ** switch_to_auth_protocol
+                                                         TSRMLS_DC)
+{
+       enum_func_status ret = FAIL;
+       const MYSQLND_CHARSET * charset = NULL;
+       MYSQLND_PACKET_OK * ok_packet = NULL;
+       MYSQLND_PACKET_AUTH * auth_packet = NULL;
+
+       DBG_ENTER("mysqlnd_native_auth_handshake");
+
+       /* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */
+       if (greet_packet->scramble_buf_len != SCRAMBLE_LENGTH && (greet_packet->scramble_buf_len != 21)) {
+               /* 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", greet_packet->scramble_buf_len, SCRAMBLE_LENGTH);
+               goto end;
+       }
+
+       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);
+
+       if (!ok_packet || !auth_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;
+       } else {
+#if MYSQLND_UNICODE
+               auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */
+#else
+               auth_packet->charset_no = greet_packet->charset_no;
+#endif
+       }
+
+       auth_packet->send_auth_data = TRUE;
+       auth_packet->user               = user;
+       auth_packet->password   = passwd;
+       auth_packet->db                 = db;
+       auth_packet->db_len             = db_len;
+
+       auth_packet->server_scramble_buf_len = greet_packet->scramble_buf_len;
+       conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(auth_packet->server_scramble_buf_len, conn->persistent);
+       if (!conn->scramble) {
+               SET_OOM_ERROR(conn->error_info);
+               goto end;
+       }
+       memcpy(auth_packet->server_scramble_buf, greet_packet->scramble_buf, greet_packet->scramble_buf_len);
+
+       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) {
+                       /* 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);
+                       }
+                       conn->error_info.error_no = ok_packet->error_no;
+                       strlcpy(conn->error_info.error, ok_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);
+       conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no);
+       ret = PASS;
+end:
+       PACKET_FREE(auth_packet);
+       PACKET_FREE(ok_packet);
+       DBG_RETURN(ret);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_native_auth_change_user */
+static enum_func_status
+mysqlnd_native_auth_change_user(MYSQLND * const conn,
+                                                               const char * const user,
+                                                               const size_t user_len,
+                                                               const char * const passwd,
+                                                               const char * const db,
+                                                               const size_t db_len,
+                                                               const zend_bool silent,
+                                                               char ** switch_to_auth_protocol
+                                                               TSRMLS_DC)
+{
+       /*
+         User could be max 16 * 3 (utf8), pass is 20 usually, db is up to 64*3
+         Stack space is not that expensive, so use a bit more to be protected against
+         buffer overflows.
+       */
+       enum_func_status ret = FAIL;
+       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);
+
+       DBG_ENTER("mysqlnd_native_auth_change_user");
+
+       if (!auth_packet || !chg_user_resp) {
+               SET_OOM_ERROR(conn->error_info);
+               goto end;
+       }
+       auth_packet->is_change_user_packet = TRUE;
+       auth_packet->user               = user;
+       auth_packet->password   = passwd;
+       auth_packet->db                 = db;
+       auth_packet->db_len             = db_len;
+       auth_packet->server_scramble_buf = conn->scramble;
+       auth_packet->silent             = silent;
+       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;
+       }
+
+       ret = PACKET_READ(chg_user_resp, conn);
+       conn->error_info = chg_user_resp->error_info;
+
+       if (conn->error_info.error_no) {
+               ret = FAIL;
+               /*
+                 COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
+                 bug#25371 mysql_change_user() triggers "packets out of sync"
+                 When it gets fixed, there should be one more check here
+               */
+               if (mysqlnd_get_server_version(conn) > 50113L && mysqlnd_get_server_version(conn) < 50118L) {
+                       MYSQLND_PACKET_OK * redundant_error_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC);
+                       if (redundant_error_packet) {
+                               PACKET_READ(redundant_error_packet, conn);
+                               PACKET_FREE(redundant_error_packet);
+                               DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", mysqlnd_get_server_version(conn));
+                       } else {
+                               SET_OOM_ERROR(conn->error_info);
+                       }
+               }
+       }
+       if (ret == PASS) {
+               char * tmp = NULL;
+               /* if we get conn->user as parameter and then we first free it, then estrndup it, we will crash */
+               tmp = mnd_pestrndup(user, user_len, conn->persistent);
+               if (conn->user) {
+                       mnd_pefree(conn->user, conn->persistent);
+               }
+               conn->user = tmp;
+
+               tmp = mnd_pestrdup(passwd, conn->persistent);
+               if (conn->passwd) {
+                       mnd_pefree(conn->passwd, conn->persistent);
+               }
+               conn->passwd = tmp;
+
+               if (conn->last_message) {
+                       mnd_pefree(conn->last_message, conn->persistent);
+                       conn->last_message = NULL;
+               }
+               memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
+               /* set charset for old servers */
+               if (mysqlnd_get_server_version(conn) < 50123) {
+                       ret = conn->m->set_charset(conn, old_cs->name TSRMLS_CC);
+               }
+       } else if (ret == FAIL && chg_user_resp->server_asked_323_auth == TRUE) {
+               /* old authentication with new server  !*/
+               DBG_ERR(mysqlnd_old_passwd);
+               SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
+       }
+end:
+       PACKET_FREE(auth_packet);
+       PACKET_FREE(chg_user_resp);
+       DBG_RETURN(ret);
+}
+/* }}} */
+
+static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin =
+{
+       {
+               MYSQLND_PLUGIN_API_VERSION,
+               "auth_plugin_mysql_native_password",
+               MYSQLND_VERSION_ID,
+               MYSQLND_VERSION,
+               "PHP License 3.01",
+               "Andrey Hristov <andrey@mysql.com>,  Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
+               {
+                       NULL, /* no statistics , will be filled later if there are some */
+                       NULL, /* no statistics */
+               },
+               {
+                       NULL /* plugin shutdown */
+               }
+       },
+       {/* methods */
+               mysqlnd_native_auth_handshake,
+               mysqlnd_native_auth_change_user
+       }
+};
+
+/* {{{ mysqlnd_native_authentication_plugin_register */
+void
+mysqlnd_native_authentication_plugin_register(TSRMLS_D)
+{
+       mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin TSRMLS_CC);
+}
+/* }}} */
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
index 07f85d3c786c98b4d6696bd75b8b3612eee86ddf..79fee3417622c19c9f8ada96fb1ac7f3ebc1260f 100644 (file)
@@ -35,6 +35,8 @@
 
 #define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1)
 
+#define MYSQLND_ASSEMBLED_PACKET_MAX_SIZE 3UL*1024UL*1024UL*1024UL
+
 #define MYSQLND_ERRMSG_SIZE                    512
 #define MYSQLND_SQLSTATE_LENGTH                5
 #define MYSQLND_SQLSTATE_NULL          "00000"
@@ -96,6 +98,9 @@
 
 #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30)
 
+#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)
 
 #define MYSQLND_NET_FLAG_USE_COMPRESSION 1
 
@@ -170,7 +175,9 @@ typedef enum mysqlnd_option
        MYSQLND_OPT_SSL_CA = 206,
        MYSQLND_OPT_SSL_CAPATH = 207,
        MYSQLND_OPT_SSL_CIPHER = 208,
-       MYSQLND_OPT_SSL_PASSPHRASE = 209
+       MYSQLND_OPT_SSL_PASSPHRASE = 209,
+       MYSQLND_OPT_MAX_ALLOWED_PACKET = 210,
+       MYSQLND_OPT_AUTH_PROTOCOL = 211
 } enum_mysqlnd_option;
 
 typedef enum mysqlnd_protocol_type
index 968e0b11c82457053933cdff0c7dc6788ddecaf1..f35862087da46f22cef85346897a18b801a7c647 100644 (file)
@@ -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_example_plugin_register(TSRMLS_D);
 
index 4a28f17ae26dbfbea01130b3f44bbaf79c774ef5..5ba2f4c68f1d4654e22d538315a9f92f0e6dfae6 100644 (file)
@@ -151,12 +151,12 @@ typedef struct st_mysqlnd_options
        char            *cfg_file;
        char            *cfg_section;
 
+       char            *auth_protocol;
        /*
          We need to keep these because otherwise st_mysqlnd_conn will be changed.
          The ABI will be broken and the methods structure will be somewhere else
          in the memory which can crash external code. Feel free to reuse these.
        */
-       char            * unused1;
        char            * unused2;
        char            * unused3;
        char            * unused4;
@@ -963,4 +963,21 @@ struct st_mysqlnd_typeii_plugin_example
        unsigned int counter;
 };
 
+struct st_mysqlnd_packet_greet;
+
+struct st_mysqlnd_authentication_plugin
+{
+       struct st_mysqlnd_plugin_header plugin_header;
+       struct {
+               enum_func_status (*auth_handshake)(MYSQLND * conn, const char * const user, const char * const passwd, const char * const db,
+                                                                                  const size_t db_len, const struct st_mysqlnd_packet_greet * const greet_packet,
+                                                                                  const MYSQLND_OPTIONS * const options, unsigned long mysql_flags,
+                                                                                  char ** switch_to_auth_protocol TSRMLS_DC);
+
+               enum_func_status (*auth_change_user)(MYSQLND * const conn, const char * const user, const size_t user_len, const char * const passwd,
+                                                                                        const char * const db, const size_t db_len, const zend_bool silent,
+                                                                                        char ** switch_to_auth_protocol TSRMLS_DC);
+       } methods;
+};
+
 #endif /* MYSQLND_STRUCTS_H */
index f3a4ba4c1e48a97ab216183f1e6a0886fc5f5202..2ee894f639cb9adbbd3292635743db8cd8231689 100644 (file)
@@ -306,6 +306,7 @@ php_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC)
        zend_uchar buf[2048];
        zend_uchar *p = buf;
        zend_uchar *begin = buf;
+       zend_uchar *pad_start = NULL;
        MYSQLND_PACKET_GREET *packet= (MYSQLND_PACKET_GREET *) _packet;
 
        DBG_ENTER("php_mysqlnd_greet_read");
@@ -313,6 +314,17 @@ php_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC)
        PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "greeting", PROT_GREET_PACKET);
        BAIL_IF_NO_MORE_DATA;
 
+       packet->scramble_buf = &packet->intern_scramble_buf;
+       packet->scramble_buf_len = sizeof(packet->intern_scramble_buf);
+
+       if (packet->header.size < sizeof(buf)) {
+               /*
+                 Null-terminate the string, so strdup can work even if the packets have a string at the end,
+                 which is not ASCIIZ
+               */
+               buf[packet->header.size] = '\0'; 
+       }
+
        packet->protocol_version = uint1korr(p);
        p++;
        BAIL_IF_NO_MORE_DATA;
@@ -342,7 +354,7 @@ php_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC)
        BAIL_IF_NO_MORE_DATA;
 
        memcpy(packet->scramble_buf, p, SCRAMBLE_LENGTH_323);
-       p+= 8;
+       p+= SCRAMBLE_LENGTH_323;
        BAIL_IF_NO_MORE_DATA;
 
        /* pad1 */
@@ -362,22 +374,56 @@ php_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC)
        BAIL_IF_NO_MORE_DATA;
 
        /* pad2 */
+       pad_start = p;
        p+= 13;
        BAIL_IF_NO_MORE_DATA;
 
        if ((size_t) (p - buf) < packet->header.size) {
                /* scramble_buf is split into two parts */
-               memcpy(packet->scramble_buf + SCRAMBLE_LENGTH_323,
-                               p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323);
+               memcpy(packet->scramble_buf + SCRAMBLE_LENGTH_323, p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323);
+               p+= SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323;
+               p++; /* 0x0 at the end of the scramble and thus last byte in the packet in 5.1 and previous */
        } else {
                packet->pre41 = TRUE;
        }
 
+       /* Is this a 5.5+ server ? */
+       if ((size_t) (p - buf) < packet->header.size) {
+                /* backtrack one byte, the 0x0 at the end of the scramble in 5.1 and previous */
+               p--;
+
+       /* Additional 16 bits for server capabilities */
+               packet->server_capabilities |= uint2korr(pad_start) << 16;
+               /* And a length of the server scramble in one byte */
+               packet->scramble_buf_len = uint1korr(pad_start + 2);
+               if (packet->scramble_buf_len > SCRAMBLE_LENGTH) {
+                       /* more data*/
+                       char * new_scramble_buf = emalloc(packet->scramble_buf_len);
+                       if (!new_scramble_buf) {
+                               goto premature_end;
+                       }
+                       /* copy what we already have */
+                       memcpy(new_scramble_buf, packet->scramble_buf, SCRAMBLE_LENGTH);
+                       /* add additional scramble data 5.5+ sent us */
+                       memcpy(new_scramble_buf + SCRAMBLE_LENGTH, p, packet->scramble_buf_len - SCRAMBLE_LENGTH);
+                       p+= (packet->scramble_buf_len - SCRAMBLE_LENGTH);
+                       packet->scramble_buf = new_scramble_buf;
+               }
+       }
+
+       if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) {
+               BAIL_IF_NO_MORE_DATA;
+               /* The server is 5.5.x and supports authentication plugins */
+               packet->auth_protocol = estrdup((char *)p);
+               p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */
+       }
+
        DBG_INF_FMT("proto=%u server=%s thread_id=%u",
                                packet->protocol_version, packet->server_version, packet->thread_id);
 
-       DBG_INF_FMT("server_capabilities=%u charset_no=%u server_status=%i",
-                               packet->server_capabilities, packet->charset_no, packet->server_status);
+       DBG_INF_FMT("server_capabilities=%u charset_no=%u server_status=%i auth_protocol=%s scramble_length=%u",
+                               packet->server_capabilities, packet->charset_no, packet->server_status,
+                               packet->auth_protocol? packet->auth_protocol:"n/a", packet->scramble_buf_len);
 
        DBG_RETURN(PASS);
 premature_end:
@@ -398,6 +444,14 @@ void php_mysqlnd_greet_free_mem(void *_packet, zend_bool stack_allocation TSRMLS
                efree(p->server_version);
                p->server_version = NULL;
        }
+       if (p->scramble_buf && p->scramble_buf != &p->intern_scramble_buf) {
+               efree(p->scramble_buf);
+               p->scramble_buf = NULL;
+       }
+       if (p->auth_protocol) {
+               efree(p->auth_protocol);
+               p->auth_protocol = NULL;
+       }
        if (!stack_allocation) {
                mnd_pefree(p, p->header.persistent);
        }
@@ -424,7 +478,6 @@ void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const sc
        zend_uchar sha1[SHA1_MAX_LENGTH];
        zend_uchar sha2[SHA1_MAX_LENGTH];
 
-
        /* Phase 1: hash password */
        PHP_SHA1Init(&context);
        PHP_SHA1Update(&context, password, strlen((char *)password));
index 39645cb08cffd264a3d1bbf388ae928d4d0f6774..0f3f73e788766db23560375e23205e4fb9eb7797 100644 (file)
@@ -70,17 +70,20 @@ typedef struct st_mysqlnd_packet_greet {
        uint8_t         protocol_version;
        char            *server_version;
        uint32_t        thread_id;
-       zend_uchar      scramble_buf[SCRAMBLE_LENGTH];
+       zend_uchar      intern_scramble_buf[SCRAMBLE_LENGTH];
+       zend_uchar      * scramble_buf;
+       size_t          scramble_buf_len;
        /* 1 byte pad */
-       uint16_t        server_capabilities;
+       uint32_t        server_capabilities;
        uint8_t         charset_no;
        uint16_t        server_status;
-       /* 13 byte pad*/
+       /* 13 byte pad, in 5.5 first 2 bytes are more capabilities followed by 1 byte scramble_length */
        zend_bool       pre41;
        /* If error packet, we use these */
        char            error[MYSQLND_ERRMSG_SIZE+1];
        char            sqlstate[MYSQLND_SQLSTATE_LENGTH + 1];
        unsigned int    error_no;
+       char            *auth_protocol;
 } MYSQLND_PACKET_GREET;
 
 
@@ -100,6 +103,7 @@ typedef struct st_mysqlnd_packet_auth {
        const char      *password;
        /* +1 for \0 because of scramble() */
        unsigned char   *server_scramble_buf;
+       size_t                  server_scramble_buf_len;
        size_t                  db_len;
        zend_bool               send_auth_data;
        zend_bool               is_change_user_packet;