andrey                                   Wed, 12 Jan 2011 21:40:05 +0000

Revision: http://svn.php.net/viewvc?view=revision&revision=307423

Log:
grok the MySQL 5.5 extended handshake.
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

Changed paths:
    U   php/php-src/trunk/ext/mysqlnd/config.w32
    U   php/php-src/trunk/ext/mysqlnd/config9.m4
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd.c
    A   php/php-src/trunk/ext/mysqlnd/mysqlnd_auth.c
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd_enum_n_def.h
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd_priv.h
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd_structs.h
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.c
    U   php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.h

Modified: php/php-src/trunk/ext/mysqlnd/config.w32
===================================================================
--- php/php-src/trunk/ext/mysqlnd/config.w32	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/config.w32	2011-01-12 21:40:05 UTC (rev 307423)
@@ -7,6 +7,7 @@
 	if (CHECK_LIB("ws2_32.lib", "mysqlnd")) {
 		mysqlnd_source =
 			"mysqlnd.c " +
+			"mysqlnd_auth.c" +
 			"mysqlnd_block_alloc.c " +
 			"mysqlnd_charset.c " +
 			"mysqlnd_debug.c " +

Modified: php/php-src/trunk/ext/mysqlnd/config9.m4
===================================================================
--- php/php-src/trunk/ext/mysqlnd/config9.m4	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/config9.m4	2011-01-12 21:40:05 UTC (rev 307423)
@@ -16,7 +16,7 @@
 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"


Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd.c
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd.c	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd.c	2011-01-12 21:40:05 UTC (rev 307423)
@@ -92,6 +92,10 @@
 		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 @@
 /* }}} */


-#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 @@
 			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 @@
 		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 @@
 			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;
-	}
+	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;

-	auth_packet = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC);
+		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 (ret == PASS) {
+			conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC);
+		}

-	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 (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 @@
 				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 @@
 			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 @@

 	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,27 +1919,14 @@
 	  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",
 				conn->thread_id, user?user:"", passwd?"***":"null", db?db:"", (silent == TRUE)?1:0 );

 	SET_ERROR_AFF_ROWS(conn);

-	if (!auth_packet) {
-		SET_OOM_ERROR(conn->error_info);
-		goto end;
-	}
-
 	if (!user) {
 		user = "";
 	}
@@ -1966,86 +1936,43 @@
 	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;
-	}
+	{
+		char * switch_to_auth_protocol = NULL;
+		const char * requested_protocol = NULL;

-	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;
+		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"
+														  );

-	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);
-			}
+			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);
 		}
-	}
-	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);
+		if (switch_to_auth_protocol) {
+			mnd_efree(switch_to_auth_protocol);
+			switch_to_auth_protocol = NULL;
 		}
-		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);
-
 	/*
 	  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 @@
 			/* 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 @@
 		}
 		mysqlnd_example_plugin_register(TSRMLS_C);
 		mysqlnd_debug_trace_plugin_register(TSRMLS_C);
+		mysqlnd_native_authentication_plugin_register(TSRMLS_C);
 	}
 }
 /* }}} */

Added: php/php-src/trunk/ext/mysqlnd/mysqlnd_auth.c
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_auth.c	                        (rev 0)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_auth.c	2011-01-12 21:40:05 UTC (rev 307423)
@@ -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          |
+  | lice...@php.net so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Authors: Georg Richter <ge...@mysql.com>                             |
+  |          Andrey Hristov <and...@mysql.com>                           |
+  |          Ulf Wendel <uwen...@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 <and...@mysql.com>,  Ulf Wendel <uwen...@mysql.com>, Georg Richter <ge...@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
+ */

Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_enum_n_def.h
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_enum_n_def.h	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_enum_n_def.h	2011-01-12 21:40:05 UTC (rev 307423)
@@ -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 @@
 	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

Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_priv.h
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_priv.h	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_priv.h	2011-01-12 21:40:05 UTC (rev 307423)
@@ -192,8 +192,8 @@
 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);

 #endif	/* MYSQLND_PRIV_H */

Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_structs.h
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_structs.h	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_structs.h	2011-01-12 21:40:05 UTC (rev 307423)
@@ -151,12 +151,12 @@
 	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 @@
 	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 */

Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.c
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.c	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.c	2011-01-12 21:40:05 UTC (rev 307423)
@@ -306,6 +306,7 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 		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 @@
 	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));

Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.h
===================================================================
--- php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.h	2011-01-12 21:14:47 UTC (rev 307422)
+++ php/php-src/trunk/ext/mysqlnd/mysqlnd_wireprotocol.h	2011-01-12 21:40:05 UTC (rev 307423)
@@ -70,17 +70,20 @@
 	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 @@
 	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;
-- 
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to