diff options
127 files changed, 8248 insertions, 1616 deletions
diff --git a/Makefile.am b/Makefile.am index 7ab9a9aa061..ef815238998 100644 --- a/Makefile.am +++ b/Makefile.am @@ -267,7 +267,9 @@ test-full-qa: API_PREPROCESSOR_HEADER = $(top_srcdir)/include/mysql/plugin.h \ $(top_srcdir)/include/mysql.h \ $(top_srcdir)/include/mysql/psi/psi_abi_v1.h \ - $(top_srcdir)/include/mysql/psi/psi_abi_v2.h + $(top_srcdir)/include/mysql/psi/psi_abi_v2.h \ + $(top_srcdir)/include/mysql/client_plugin.h \ + $(top_srcdir)/include/mysql/plugin_auth.h # # Rules for checking that the abi/api has not changed. diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d0bb8d43edd..5be39396edf 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -69,3 +69,5 @@ ADD_EXECUTABLE(echo echo.c) SET_TARGET_PROPERTIES (mysqlcheck mysqldump mysqlimport mysql_upgrade mysqlshow mysqlslap PROPERTIES HAS_CXX TRUE) +ADD_DEFINITIONS(-DHAVE_DLOPEN) + diff --git a/client/client_priv.h b/client/client_priv.h index e7911fc31f7..1a81768adc4 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -84,6 +84,8 @@ enum options_client OPT_DEBUG_INFO, OPT_DEBUG_CHECK, OPT_COLUMN_TYPES, OPT_ERROR_LOG_FILE, OPT_WRITE_BINLOG, OPT_DUMP_DATE, OPT_INIT_COMMAND, + OPT_PLUGIN_DIR, + OPT_DEFAULT_PLUGIN, OPT_MAX_CLIENT_OPTION }; diff --git a/client/mysql.cc b/client/mysql.cc index 01a786c13af..a985c135361 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -169,6 +169,7 @@ static int wait_time = 5; static STATUS status; static ulong select_limit,max_join_size,opt_connect_timeout=0; static char mysql_charsets_dir[FN_REFLEN+1]; +static char *opt_plugin_dir= 0, *opt_default_auth; static const char *xmlmeta[] = { "&", "&", "<", "<", @@ -1567,6 +1568,13 @@ static struct my_option my_long_options[] = {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.", &show_warnings, &show_warnings, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + (uchar**) &opt_plugin_dir, (uchar**) &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_PLUGIN_DIR, + "Default authentication client-side plugin to use.", + (uchar**) &opt_default_auth, (uchar**) &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; @@ -4298,9 +4306,15 @@ sql_real_connect(char *host,char *database,char *user,char *password, mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + if (!mysql_real_connect(&mysql, host, user, password, - database, opt_mysql_port, opt_mysql_unix_port, - connect_flag | CLIENT_MULTI_STATEMENTS)) + database, opt_mysql_port, opt_mysql_unix_port, + connect_flag | CLIENT_MULTI_STATEMENTS)) { if (!silent || (mysql_errno(&mysql) != CR_CONN_HOST_ERROR && diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 20f0f6d3164..fabe0e1ea3e 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -36,6 +36,7 @@ #include "client_priv.h" #include <mysql_version.h> #include <mysqld_error.h> +#include <sql_common.h> #include <m_ctype.h> #include <my_dir.h> #include <hash.h> @@ -190,6 +191,8 @@ static ulonglong timer_now(void); static ulong connection_retry_sleep= 100000; /* Microseconds */ +static char *opt_plugin_dir= 0; + /* Precompiled re's */ static my_regex_t ps_re; /* the query can be run using PS protocol */ static my_regex_t sp_re; /* the query can be run as a SP */ @@ -3728,13 +3731,15 @@ void do_change_user(struct st_command *command) } if (!ds_user.length) + { dynstr_set(&ds_user, mysql->user); - if (!ds_passwd.length) - dynstr_set(&ds_passwd, mysql->passwd); + if (!ds_passwd.length) + dynstr_set(&ds_passwd, mysql->passwd); - if (!ds_db.length) - dynstr_set(&ds_db, mysql->db); + if (!ds_db.length) + dynstr_set(&ds_db, mysql->db); + } DBUG_PRINT("info",("connection: '%s' user: '%s' password: '%s' database: '%s'", cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); @@ -5064,6 +5069,7 @@ void do_connect(struct st_command *command) static DYNAMIC_STRING ds_port; static DYNAMIC_STRING ds_sock; static DYNAMIC_STRING ds_options; + static DYNAMIC_STRING ds_default_auth; #ifdef HAVE_SMEM static DYNAMIC_STRING ds_shm; #endif @@ -5075,7 +5081,8 @@ void do_connect(struct st_command *command) { "database", ARG_STRING, FALSE, &ds_database, "Database to select after connect" }, { "port", ARG_STRING, FALSE, &ds_port, "Port to connect to" }, { "socket", ARG_STRING, FALSE, &ds_sock, "Socket to connect with" }, - { "options", ARG_STRING, FALSE, &ds_options, "Options to use while connecting" } + { "options", ARG_STRING, FALSE, &ds_options, "Options to use while connecting" }, + { "default_auth", ARG_STRING, FALSE, &ds_default_auth, "Default authentication to use" } }; DBUG_ENTER("do_connect"); @@ -5222,6 +5229,12 @@ void do_connect(struct st_command *command) if (ds_database.length == 0) dynstr_set(&ds_database, opt_db); + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (ds_default_auth.length) + mysql_options(&con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str); + /* Special database to allow one to connect without a database name */ if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*")) dynstr_set(&ds_database, ""); @@ -6000,6 +6013,9 @@ static struct my_option my_long_options[] = "Number of seconds before connection timeout.", &opt_connect_timeout, &opt_connect_timeout, 0, GET_UINT, REQUIRED_ARG, 120, 0, 3600 * 12, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + (uchar**) &opt_plugin_dir, (uchar**) &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; diff --git a/config.h.cmake b/config.h.cmake index c484edb65a5..40ea1fd4dff 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -603,6 +603,8 @@ #cmakedefine PLUGINDIR "@PLUGINDIR@" #cmakedefine DEFAULT_SYSCONFDIR "@DEFAULT_SYSCONFDIR@" +#cmakedefine SO_EXT "@CMAKE_SHARED_MODULE_SUFFIX@" + #define PACKAGE "mysql" #define PACKAGE_BUGREPORT "" #define PACKAGE_NAME "MySQL Server" diff --git a/configure.in b/configure.in index 7121a521f87..80c436940a8 100644 --- a/configure.in +++ b/configure.in @@ -1657,9 +1657,8 @@ case "$with_mysqld_ldflags " in ;; *) - # Check for dlopen, needed for user definable functions + # Check for dlopen, needed for user definable functions and plugins # This must be checked after threads on AIX - # We only need this for mysqld, not for the clients. my_save_LIBS="$LIBS" LIBS="" diff --git a/include/Makefile.am b/include/Makefile.am index e30588de065..9dbd818d592 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -24,7 +24,9 @@ HEADERS_ABI = mysql.h mysql_com.h mysql_time.h \ my_list.h my_alloc.h typelib.h mysql/plugin.h \ mysql/plugin_audit.h mysql/plugin_ftparser.h pkginclude_HEADERS = $(HEADERS_ABI) my_dbug.h m_string.h my_sys.h \ - my_xml.h mysql_embed.h mysql/services.h \ + my_xml.h mysql_embed.h mysql/plugin_auth.h \ + mysql/client_plugin.h mysql/plugin_auth_common.h \ + mysql/services.h \ mysql/service_my_snprintf.h mysql/service_thd_alloc.h \ my_pthread.h my_no_pthread.h \ decimal.h errmsg.h my_global.h my_net.h \ @@ -53,7 +55,8 @@ pkgpsiinclude_HEADERS = mysql/psi/psi.h mysql/psi/mysql_thread.h \ EXTRA_DIST = mysql.h.pp mysql/plugin.h.pp probes_mysql.d.base \ CMakeLists.txt \ mysql/psi/psi_abi_v1.h.pp \ - mysql/psi/psi_abi_v2.h.pp + mysql/psi/psi_abi_v2.h.pp \ + mysql/plugin_auth.h.pp mysql/client_plugin.h.pp # Remove built files and the symlinked directories CLEANFILES = $(BUILT_SOURCES) readline openssl probes_mysql.d probes_mysql_nodtrace.h diff --git a/include/errmsg.h b/include/errmsg.h index c55c94af169..f1d7dd65f97 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -101,7 +101,8 @@ extern const char *client_errors[]; /* Error messages */ #define CR_STMT_CLOSED 2056 #define CR_NEW_STMT_METADATA 2057 #define CR_ALREADY_CONNECTED 2058 -#define CR_ERROR_LAST /*Copy last error nr:*/ 2058 +#define CR_AUTH_PLUGIN_CANNOT_LOAD 2059 +#define CR_ERROR_LAST /*Copy last error nr:*/ 2059 /* Add error numbers before CR_ERROR_LAST and change it accordingly. */ #endif /* ERRMSG_INCLUDED */ diff --git a/include/my_global.h b/include/my_global.h index 1c615cc5ca2..378afe79904 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -452,6 +452,16 @@ extern "C" int madvise(void *addr, size_t len, int behav); #define LINT_INIT(var) #endif +#ifndef SO_EXT +#ifdef _WIN32 +#define SO_EXT ".dll" +#elif defined(__APPLE__) +#define SO_EXT ".dylib" +#else +#define SO_EXT ".so" +#endif +#endif + /* Suppress uninitialized variable warning without generating code. @@ -1365,7 +1375,7 @@ do { doubleget_union _tmp; \ #endif #ifndef HAVE_DLERROR -#define dlerror() "" +#define dlerror() "No support for dynamic loading (static build?)" #endif diff --git a/include/my_no_pthread.h b/include/my_no_pthread.h index a805a9151f8..633a5b94a6c 100644 --- a/include/my_no_pthread.h +++ b/include/my_no_pthread.h @@ -47,6 +47,7 @@ #define rw_wrlock(A) #define rw_unlock(A) #define rwlock_destroy(A) +#define safe_mutex_assert_owner(mp) #define mysql_mutex_init(A, B, C) do {} while (0) #define mysql_mutex_lock(A) do {} while (0) diff --git a/include/my_sys.h b/include/my_sys.h index 95689535be5..23c9b2da55f 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -197,7 +197,7 @@ extern void my_large_free(uchar *ptr); #define my_alloca(SZ) alloca((size_t) (SZ)) #define my_afree(PTR) {} #else -#define my_alloca(SZ) my_malloc(SZ,MYF(0)) +#define my_alloca(SZ) my_malloc(SZ,MYF(MY_FAE)) #define my_afree(PTR) my_free(PTR) #endif /* HAVE_ALLOCA */ @@ -824,6 +824,10 @@ extern void set_prealloc_root(MEM_ROOT *root, char *ptr); extern void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size, size_t prealloc_size); extern char *strdup_root(MEM_ROOT *root,const char *str); +static inline char *safe_strdup_root(MEM_ROOT *root, const char *str) +{ + return str ? strdup_root(root, str) : 0; +} extern char *strmake_root(MEM_ROOT *root,const char *str,size_t len); extern void *memdup_root(MEM_ROOT *root,const void *str, size_t len); extern int get_defaults_options(int argc, char **argv, diff --git a/include/mysql.h b/include/mysql.h index dc6e4eb19a6..7d949597702 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -167,9 +167,15 @@ enum mysql_option MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT + MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH }; +/** + @todo remove the "extension", move st_mysql_options completely + out of mysql.h +*/ +struct st_mysql_options_extention; + struct st_mysql_options { unsigned int connect_timeout, read_timeout, write_timeout; unsigned int port, protocol; @@ -203,7 +209,7 @@ struct st_mysql_options { void (*local_infile_end)(void *); int (*local_infile_error)(void *, char *, unsigned int); void *local_infile_userdata; - void *extension; + struct st_mysql_options_extention *extension; }; enum mysql_status @@ -638,38 +644,6 @@ enum enum_stmt_attr_type }; -typedef struct st_mysql_methods -{ - my_bool (*read_query_result)(MYSQL *mysql); - my_bool (*advanced_command)(MYSQL *mysql, - enum enum_server_command command, - const unsigned char *header, - unsigned long header_length, - const unsigned char *arg, - unsigned long arg_length, - my_bool skip_check, - MYSQL_STMT *stmt); - MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, - unsigned int fields); - MYSQL_RES * (*use_result)(MYSQL *mysql); - void (*fetch_lengths)(unsigned long *to, - MYSQL_ROW column, unsigned int field_count); - void (*flush_use_result)(MYSQL *mysql, my_bool flush_all_results); -#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) - MYSQL_FIELD * (*list_fields)(MYSQL *mysql); - my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); - int (*stmt_execute)(MYSQL_STMT *stmt); - int (*read_binary_rows)(MYSQL_STMT *stmt); - int (*unbuffered_fetch)(MYSQL *mysql, char **row); - void (*free_embedded_thd)(MYSQL *mysql); - const char *(*read_statistics)(MYSQL *mysql); - my_bool (*next_result)(MYSQL *mysql); - int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); - int (*read_rows_from_cursor)(MYSQL_STMT *stmt); -#endif -} MYSQL_METHODS; - - MYSQL_STMT * STDCALL mysql_stmt_init(MYSQL *mysql); int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length); @@ -732,18 +706,6 @@ int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); #endif #define HAVE_MYSQL_REAL_CONNECT -/* - The following functions are mainly exported because of mysqlbinlog; - They are not for general usage -*/ - -#define simple_command(mysql, command, arg, length, skip_check) \ - (*(mysql)->methods->advanced_command)(mysql, command, 0, \ - 0, arg, length, skip_check, NULL) -#define stmt_command(mysql, command, arg, length, stmt) \ - (*(mysql)->methods->advanced_command)(mysql, command, 0, \ - 0, arg, length, 1, stmt) - #ifdef __cplusplus } #endif diff --git a/include/mysql.h.pp b/include/mysql.h.pp index 531062aee80..7200a04d304 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -130,13 +130,13 @@ void create_random_string(char *to, unsigned int length, struct rand_struct *ran void hash_password(unsigned long *to, const char *password, unsigned int password_len); void make_scrambled_password_323(char *to, const char *password); void scramble_323(char *to, const char *message, const char *password); -my_bool check_scramble_323(const char *, const char *message, +my_bool check_scramble_323(const unsigned char *reply, const char *message, unsigned long *salt); void get_salt_from_password_323(unsigned long *res, const char *password); void make_password_from_salt_323(char *to, const unsigned long *salt); void make_scrambled_password(char *to, const char *password); void scramble(char *to, const char *message, const char *password); -my_bool check_scramble(const char *reply, const char *message, +my_bool check_scramble(const unsigned char *reply, const char *message, const unsigned char *hash_stage2); void get_salt_from_password(unsigned char *res, const char *password); void make_password_from_salt(char *to, const unsigned char *hash_stage2); @@ -262,8 +262,9 @@ enum mysql_option MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT + MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH }; +struct st_mysql_options_extention; struct st_mysql_options { unsigned int connect_timeout, read_timeout, write_timeout; unsigned int port, protocol; @@ -293,7 +294,7 @@ struct st_mysql_options { void (*local_infile_end)(void *); int (*local_infile_error)(void *, char *, unsigned int); void *local_infile_userdata; - void *extension; + struct st_mysql_options_extention *extension; }; enum mysql_status { @@ -547,34 +548,6 @@ enum enum_stmt_attr_type STMT_ATTR_CURSOR_TYPE, STMT_ATTR_PREFETCH_ROWS }; -typedef struct st_mysql_methods -{ - my_bool (*read_query_result)(MYSQL *mysql); - my_bool (*advanced_command)(MYSQL *mysql, - enum enum_server_command command, - const unsigned char *header, - unsigned long header_length, - const unsigned char *arg, - unsigned long arg_length, - my_bool skip_check, - MYSQL_STMT *stmt); - MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, - unsigned int fields); - MYSQL_RES * (*use_result)(MYSQL *mysql); - void (*fetch_lengths)(unsigned long *to, - MYSQL_ROW column, unsigned int field_count); - void (*flush_use_result)(MYSQL *mysql, my_bool flush_all_results); - MYSQL_FIELD * (*list_fields)(MYSQL *mysql); - my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); - int (*stmt_execute)(MYSQL_STMT *stmt); - int (*read_binary_rows)(MYSQL_STMT *stmt); - int (*unbuffered_fetch)(MYSQL *mysql, char **row); - void (*free_embedded_thd)(MYSQL *mysql); - const char *(*read_statistics)(MYSQL *mysql); - my_bool (*next_result)(MYSQL *mysql); - int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); - int (*read_rows_from_cursor)(MYSQL_STMT *stmt); -} MYSQL_METHODS; MYSQL_STMT * mysql_stmt_init(MYSQL *mysql); int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length); diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h new file mode 100644 index 00000000000..9631b090b14 --- /dev/null +++ b/include/mysql/client_plugin.h @@ -0,0 +1,146 @@ +#ifndef MYSQL_CLIENT_PLUGIN_INCLUDED +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + MySQL Client Plugin API + + This file defines the API for plugins that work on the client side +*/ +#define MYSQL_CLIENT_PLUGIN_INCLUDED + +#include <stdarg.h> +#include <stdlib.h> + +/* known plugin types */ +#define MYSQL_CLIENT_reserved1 0 +#define MYSQL_CLIENT_reserved2 1 +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 + +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 + +#define MYSQL_CLIENT_MAX_PLUGINS 3 + +#define mysql_declare_client_plugin(X) \ + MYSQL_PLUGIN_EXPORT struct st_mysql_client_plugin_ ## X \ + _mysql_client_plugin_declaration_ = { \ + MYSQL_CLIENT_ ## X ## _PLUGIN, \ + MYSQL_CLIENT_ ## X ## _PLUGIN_INTERFACE_VERSION, +#define mysql_end_client_plugin } + +/* generic plugin header structure */ +#define MYSQL_CLIENT_PLUGIN_HEADER \ + int type; \ + unsigned int interface_version; \ + const char *name; \ + const char *author; \ + const char *desc; \ + unsigned int version[3]; \ + int (*init)(char *, size_t, int, va_list); \ + int (*deinit)(); + +struct st_mysql_client_plugin +{ + MYSQL_CLIENT_PLUGIN_HEADER +}; + +struct st_mysql; + +/******** authentication plugin specific declarations *********/ +#include <mysql/plugin_auth_common.h> + +struct st_mysql_client_plugin_AUTHENTICATION +{ + MYSQL_CLIENT_PLUGIN_HEADER + int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql); +}; + +/******** using plugins ************/ + +/** + loads a plugin and initializes it + + @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, + and last_errno/last_error, for error reporting + @param name a name of the plugin to load + @param type type of plugin that should be loaded, -1 to disable type check + @param argc number of arguments to pass to the plugin initialization + function + @param ... arguments for the plugin initialization function + + @retval + a pointer to the loaded plugin, or NULL in case of a failure +*/ +struct st_mysql_client_plugin * +mysql_load_plugin(struct st_mysql *mysql, const char *name, int type, + int argc, ...); + +/** + loads a plugin and initializes it, taking va_list as an argument + + This is the same as mysql_load_plugin, but take va_list instead of + a list of arguments. + + @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, + and last_errno/last_error, for error reporting + @param name a name of the plugin to load + @param type type of plugin that should be loaded, -1 to disable type check + @param argc number of arguments to pass to the plugin initialization + function + @param args arguments for the plugin initialization function + + @retval + a pointer to the loaded plugin, or NULL in case of a failure +*/ +struct st_mysql_client_plugin * +mysql_load_plugin_v(struct st_mysql *mysql, const char *name, int type, + int argc, va_list args); + +/** + finds an already loaded plugin by name, or loads it, if necessary + + @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, + and last_errno/last_error, for error reporting + @param name a name of the plugin to load + @param type type of plugin that should be loaded + + @retval + a pointer to the plugin, or NULL in case of a failure +*/ +struct st_mysql_client_plugin * +mysql_client_find_plugin(struct st_mysql *mysql, const char *name, int type); + +/** + adds a plugin structure to the list of loaded plugins + + This is useful if an application has the necessary functionality + (for example, a special load data handler) statically linked into + the application binary. It can use this function to register the plugin + directly, avoiding the need to factor it out into a shared object. + + @param mysql MYSQL structure. It is only used for error reporting + @param plugin an st_mysql_client_plugin structure to register + + @retval + a pointer to the plugin, or NULL in case of a failure +*/ +struct st_mysql_client_plugin * +mysql_client_register_plugin(struct st_mysql *mysql, + struct st_mysql_client_plugin *plugin); + +#endif + diff --git a/include/mysql/client_plugin.h.pp b/include/mysql/client_plugin.h.pp new file mode 100644 index 00000000000..20d353422dd --- /dev/null +++ b/include/mysql/client_plugin.h.pp @@ -0,0 +1,41 @@ +#include <stdarg.h> +#include <stdlib.h> +struct st_mysql_client_plugin +{ + int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; int (*init)(char *, size_t, int, va_list); int (*deinit)(); +}; +struct st_mysql; +#include <mysql/plugin_auth_common.h> +typedef struct st_plugin_vio_info +{ + enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, + MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol; + int socket; +} MYSQL_PLUGIN_VIO_INFO; +typedef struct st_plugin_vio +{ + int (*read_packet)(struct st_plugin_vio *vio, + unsigned char **buf); + int (*write_packet)(struct st_plugin_vio *vio, + const unsigned char *packet, + int packet_len); + void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); +} MYSQL_PLUGIN_VIO; +struct st_mysql_client_plugin_AUTHENTICATION +{ + int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; int (*init)(char *, size_t, int, va_list); int (*deinit)(); + int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql); +}; +typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql, + int type, const char *prompt, char *buf, int buf_len); +struct st_mysql_client_plugin * +mysql_load_plugin(struct st_mysql *mysql, const char *name, int type, + int argc, ...); +struct st_mysql_client_plugin * +mysql_load_plugin_v(struct st_mysql *mysql, const char *name, int type, + int argc, va_list args); +struct st_mysql_client_plugin * +mysql_client_find_plugin(struct st_mysql *mysql, const char *name, int type); +struct st_mysql_client_plugin * +mysql_client_register_plugin(struct st_mysql *mysql, + struct st_mysql_client_plugin *plugin); diff --git a/include/mysql/plugin.h b/include/mysql/plugin.h index 19cf0ed050d..01ca76983a6 100644 --- a/include/mysql/plugin.h +++ b/include/mysql/plugin.h @@ -83,7 +83,8 @@ typedef struct st_mysql_xid MYSQL_XID; #define MYSQL_INFORMATION_SCHEMA_PLUGIN 4 /* The I_S plugin type */ #define MYSQL_AUDIT_PLUGIN 5 /* The Audit plugin type */ #define MYSQL_REPLICATION_PLUGIN 6 /* The replication plugin type */ -#define MYSQL_MAX_PLUGIN_TYPE_NUM 7 /* The number of plugin types */ +#define MYSQL_AUTHENTICATION_PLUGIN 7 /* The authentication plugin type */ +#define MYSQL_MAX_PLUGIN_TYPE_NUM 8 /* The number of plugin types */ /* We use the following strings to define licenses for plugins */ #define PLUGIN_LICENSE_PROPRIETARY 0 diff --git a/include/mysql/plugin_auth.h b/include/mysql/plugin_auth.h new file mode 100644 index 00000000000..8fed53c59fa --- /dev/null +++ b/include/mysql/plugin_auth.h @@ -0,0 +1,118 @@ +#ifndef MYSQL_PLUGIN_AUTH_INCLUDED +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + Authentication Plugin API. + + This file defines the API for server authentication plugins. +*/ + +#define MYSQL_PLUGIN_AUTH_INCLUDED + +#include <mysql/plugin.h> + +#define MYSQL_AUTHENTICATION_INTERFACE_VERSION 0x0100 + +#include <mysql/plugin_auth_common.h> + +/** + Provides server plugin access to authentication information +*/ +typedef struct st_mysql_server_auth_info +{ + /** + User name as sent by the client and shown in USER(). + NULL if the client packet with the user name was not received yet. + */ + char *user_name; + + /** + Length of user_name + */ + unsigned int user_name_length; + + /** + A corresponding column value from the mysql.user table for the + matching account name + */ + const char *auth_string; + + /** + Length of auth_string + */ + unsigned long auth_string_length; + + /** + Matching account name as found in the mysql.user table. + A plugin can override it with another name that will be + used by MySQL for authorization, and shown in CURRENT_USER() + */ + char authenticated_as[MYSQL_USERNAME_LENGTH+1]; + + + /** + The unique user name that was used by the plugin to authenticate. + Plugins should put null-terminated UTF-8 here. + Available through the @@EXTERNAL_USER variable. + */ + char external_user[512]; + + /** + This only affects the "Authentication failed. Password used: %s" + error message. has the following values : + 0 : %s will be NO. + 1 : %s will be YES. + 2 : there will be no %s. + Set it as appropriate or ignore at will. + */ + int password_used; + + /** + Set to the name of the connected client if it can be resolved, or to + the address otherwise + */ + const char *host_or_ip; + + /** + Length of host_or_ip + */ + unsigned int host_or_ip_length; + +} MYSQL_SERVER_AUTH_INFO; + +/** + Server authentication plugin descriptor +*/ +struct st_mysql_auth +{ + int interface_version; /**< version plugin uses */ + /** + A plugin that a client must use for authentication with this server + plugin. Can be NULL to mean "any plugin". + */ + const char *client_auth_plugin; + /** + Function provided by the plugin which should perform authentication (using + the vio functions if necessary) and return 0 if successful. The plugin can + also fill the info.authenticated_as field if a different username should be + used for authorization. + */ + int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info); +}; +#endif + diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp new file mode 100644 index 00000000000..550e2e852a7 --- /dev/null +++ b/include/mysql/plugin_auth.h.pp @@ -0,0 +1,205 @@ +#include <mysql/plugin.h> +#include <mysql/services.h> +#include <mysql/service_my_snprintf.h> +#include <stdarg.h> +#include <stdlib.h> +extern struct my_snprintf_service_st { + size_t (*my_snprintf_type)(char*, size_t, const char*, ...); + size_t (*my_vsnprintf_type)(char *, size_t, const char*, va_list); +} *my_snprintf_service; +size_t my_snprintf(char* to, size_t n, const char* fmt, ...); +size_t my_vsnprintf(char *to, size_t n, const char* fmt, va_list ap); +#include <mysql/service_thd_alloc.h> +#include <stdlib.h> +struct st_mysql_lex_string +{ + char *str; + size_t length; +}; +typedef struct st_mysql_lex_string MYSQL_LEX_STRING; +extern struct thd_alloc_service_st { + void *(*thd_alloc_func)(void*, unsigned int); + void *(*thd_calloc_func)(void*, unsigned int); + char *(*thd_strdup_func)(void*, const char *); + char *(*thd_strmake_func)(void*, const char *, unsigned int); + void *(*thd_memdup_func)(void*, const void*, unsigned int); + MYSQL_LEX_STRING *(*thd_make_lex_string_func)(void*, MYSQL_LEX_STRING *, + const char *, unsigned int, int); +} *thd_alloc_service; +void *thd_alloc(void* thd, unsigned int size); +void *thd_calloc(void* thd, unsigned int size); +char *thd_strdup(void* thd, const char *str); +char *thd_strmake(void* thd, const char *str, unsigned int size); +void *thd_memdup(void* thd, const void* str, unsigned int size); +MYSQL_LEX_STRING *thd_make_lex_string(void* thd, MYSQL_LEX_STRING *lex_str, + const char *str, unsigned int size, + int allocate_lex_string); +struct st_mysql_xid { + long formatID; + long gtrid_length; + long bqual_length; + char data[128]; +}; +typedef struct st_mysql_xid MYSQL_XID; +enum enum_mysql_show_type +{ + SHOW_UNDEF, SHOW_BOOL, SHOW_INT, SHOW_LONG, + SHOW_LONGLONG, SHOW_CHAR, SHOW_CHAR_PTR, + SHOW_ARRAY, SHOW_FUNC, SHOW_DOUBLE, + SHOW_always_last +}; +struct st_mysql_show_var { + const char *name; + char *value; + enum enum_mysql_show_type type; +}; +typedef int (*mysql_show_var_func)(void*, struct st_mysql_show_var*, char *); +struct st_mysql_sys_var; +struct st_mysql_value; +typedef int (*mysql_var_check_func)(void* thd, + struct st_mysql_sys_var *var, + void *save, struct st_mysql_value *value); +typedef void (*mysql_var_update_func)(void* thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +struct st_mysql_plugin +{ + int type; + void *info; + const char *name; + const char *author; + const char *descr; + int license; + int (*init)(void *); + int (*deinit)(void *); + unsigned int version; + struct st_mysql_show_var *status_vars; + struct st_mysql_sys_var **system_vars; + void * __reserved1; +}; +#include "plugin_ftparser.h" +#include "plugin.h" +enum enum_ftparser_mode +{ + MYSQL_FTPARSER_SIMPLE_MODE= 0, + MYSQL_FTPARSER_WITH_STOPWORDS= 1, + MYSQL_FTPARSER_FULL_BOOLEAN_INFO= 2 +}; +enum enum_ft_token_type +{ + FT_TOKEN_EOF= 0, + FT_TOKEN_WORD= 1, + FT_TOKEN_LEFT_PAREN= 2, + FT_TOKEN_RIGHT_PAREN= 3, + FT_TOKEN_STOPWORD= 4 +}; +typedef struct st_mysql_ftparser_boolean_info +{ + enum enum_ft_token_type type; + int yesno; + int weight_adjust; + char wasign; + char trunc; + char prev; + char *quot; +} MYSQL_FTPARSER_BOOLEAN_INFO; +typedef struct st_mysql_ftparser_param +{ + int (*mysql_parse)(struct st_mysql_ftparser_param *, + char *doc, int doc_len); + int (*mysql_add_word)(struct st_mysql_ftparser_param *, + char *word, int word_len, + MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info); + void *ftparser_state; + void *mysql_ftparam; + struct charset_info_st *cs; + char *doc; + int length; + int flags; + enum enum_ftparser_mode mode; +} MYSQL_FTPARSER_PARAM; +struct st_mysql_ftparser +{ + int interface_version; + int (*parse)(MYSQL_FTPARSER_PARAM *param); + int (*init)(MYSQL_FTPARSER_PARAM *param); + int (*deinit)(MYSQL_FTPARSER_PARAM *param); +}; +struct st_mysql_daemon +{ + int interface_version; +}; +struct st_mysql_information_schema +{ + int interface_version; +}; +struct st_mysql_storage_engine +{ + int interface_version; +}; +struct handlerton; + struct Mysql_replication { + int interface_version; + }; +struct st_mysql_value +{ + int (*value_type)(struct st_mysql_value *); + const char *(*val_str)(struct st_mysql_value *, char *buffer, int *length); + int (*val_real)(struct st_mysql_value *, double *realbuf); + int (*val_int)(struct st_mysql_value *, long long *intbuf); + int (*is_unsigned)(struct st_mysql_value *); +}; +int thd_in_lock_tables(const void* thd); +int thd_tablespace_op(const void* thd); +long long thd_test_options(const void* thd, long long test_options); +int thd_sql_command(const void* thd); +const char *thd_proc_info(void* thd, const char *info); +void **thd_ha_data(const void* thd, const struct handlerton *hton); +int thd_tx_isolation(const void* thd); +char *thd_security_context(void* thd, char *buffer, unsigned int length, + unsigned int max_query_len); +void thd_inc_row_count(void* thd); +int mysql_tmpfile(const char *prefix); +int thd_killed(const void* thd); +unsigned long thd_get_thread_id(const void* thd); +void thd_get_xid(const void* thd, MYSQL_XID *xid); +void mysql_query_cache_invalidate4(void* thd, + const char *key, unsigned int key_length, + int using_trx); +void *thd_get_ha_data(const void* thd, const struct handlerton *hton); +void thd_set_ha_data(void* thd, const struct handlerton *hton, + const void *ha_data); +#include <mysql/plugin_auth_common.h> +typedef struct st_plugin_vio_info +{ + enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, + MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol; + int socket; +} MYSQL_PLUGIN_VIO_INFO; +typedef struct st_plugin_vio +{ + int (*read_packet)(struct st_plugin_vio *vio, + unsigned char **buf); + int (*write_packet)(struct st_plugin_vio *vio, + const unsigned char *packet, + int packet_len); + void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); +} MYSQL_PLUGIN_VIO; +typedef struct st_mysql_server_auth_info +{ + const char *user_name; + unsigned int user_name_length; + const char *auth_string; + unsigned long auth_string_length; + char authenticated_as[48 +1]; + char external_user[512]; + int password_used; + const char *host_or_ip; + unsigned int host_or_ip_length; +} MYSQL_SERVER_AUTH_INFO; +struct st_mysql_auth +{ + int interface_version; + const char *client_auth_plugin; + int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info); +}; diff --git a/include/mysql/plugin_auth_common.h b/include/mysql/plugin_auth_common.h new file mode 100644 index 00000000000..4ad92d01bfb --- /dev/null +++ b/include/mysql/plugin_auth_common.h @@ -0,0 +1,105 @@ +#ifndef MYSQL_PLUGIN_AUTH_COMMON_INCLUDED +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + This file defines constants and data structures that are the same for + both client- and server-side authentication plugins. +*/ +#define MYSQL_PLUGIN_AUTH_COMMON_INCLUDED + +/** the max allowed length for a user name */ +#define MYSQL_USERNAME_LENGTH 48 + +/** + return values of the plugin authenticate_user() method. +*/ + +/** + Authentication failed. Additionally, all other CR_xxx values + (libmysql error code) can be used too. + + The client plugin may set the error code and the error message directly + in the MYSQL structure and return CR_ERROR. If a CR_xxx specific error + code was returned, an error message in the MYSQL structure will be + overwritten. If CR_ERROR is returned without setting the error in MYSQL, + CR_UNKNOWN_ERROR will be user. +*/ +#define CR_ERROR 0 +/** + Authentication (client part) was successful. It does not mean that the + authentication as a whole was successful, usually it only means + that the client was able to send the user name and the password to the + server. If CR_OK is returned, the libmysql reads the next packet expecting + it to be one of OK, ERROR, or CHANGE_PLUGIN packets. +*/ +#define CR_OK -1 +/** + Authentication was successful. + It means that the client has done its part successfully and also that + a plugin has read the last packet (one of OK, ERROR, CHANGE_PLUGIN). + In this case, libmysql will not read a packet from the server, + but it will use the data at mysql->net.read_pos. + + A plugin may return this value if the number of roundtrips in the + authentication protocol is not known in advance, and the client plugin + needs to read one packet more to determine if the authentication is finished + or not. +*/ +#define CR_OK_HANDSHAKE_COMPLETE -2 + +typedef struct st_plugin_vio_info +{ + enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, + MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol; + int socket; /**< it's set, if the protocol is SOCKET or TCP */ +#ifdef _WIN32 + HANDLE handle; /**< it's set, if the protocol is PIPE or MEMORY */ +#endif +} MYSQL_PLUGIN_VIO_INFO; + +/** + Provides plugin access to communication channel +*/ +typedef struct st_plugin_vio +{ + /** + Plugin provides a pointer reference and this function sets it to the + contents of any incoming packet. Returns the packet length, or -1 if + the plugin should terminate. + */ + int (*read_packet)(struct st_plugin_vio *vio, + unsigned char **buf); + + /** + Plugin provides a buffer with data and the length and this + function sends it as a packet. Returns 0 on success, 1 on failure. + */ + int (*write_packet)(struct st_plugin_vio *vio, + const unsigned char *packet, + int packet_len); + + /** + Fills in a st_plugin_vio_info structure, providing the information + about the connection. + */ + void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); + +} MYSQL_PLUGIN_VIO; + +#endif + diff --git a/include/mysql_com.h b/include/mysql_com.h index 90fe4ac1995..d4223211710 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -162,9 +162,17 @@ enum enum_server_command #define CLIENT_MULTI_RESULTS (1UL << 17) /* Enable/disable multi-results */ #define CLIENT_PS_MULTI_RESULTS (1UL << 18) /* Multi-results in PS-protocol */ +#define CLIENT_PLUGIN_AUTH (1UL << 19) /* Client supports plugin authentication */ + #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) #define CLIENT_REMEMBER_OPTIONS (1UL << 31) +#ifdef HAVE_COMPRESS +#define CAN_CLIENT_COMPRESS CLIENT_COMPRESS +#else +#define CAN_CLIENT_COMPRESS 0 +#endif + /* Gather all possible capabilites (flags) supported by the server */ #define CLIENT_ALL_FLAGS (CLIENT_LONG_PASSWORD | \ CLIENT_FOUND_ROWS | \ @@ -186,7 +194,8 @@ enum enum_server_command CLIENT_MULTI_RESULTS | \ CLIENT_PS_MULTI_RESULTS | \ CLIENT_SSL_VERIFY_SERVER_CERT | \ - CLIENT_REMEMBER_OPTIONS) + CLIENT_REMEMBER_OPTIONS | \ + CLIENT_PLUGIN_AUTH) /* Switch off the flags that are optional and depending on build flags @@ -518,14 +527,14 @@ void create_random_string(char *to, unsigned int length, struct rand_struct *ran void hash_password(unsigned long *to, const char *password, unsigned int password_len); void make_scrambled_password_323(char *to, const char *password); void scramble_323(char *to, const char *message, const char *password); -my_bool check_scramble_323(const char *, const char *message, +my_bool check_scramble_323(const unsigned char *reply, const char *message, unsigned long *salt); void get_salt_from_password_323(unsigned long *res, const char *password); void make_password_from_salt_323(char *to, const unsigned long *salt); void make_scrambled_password(char *to, const char *password); void scramble(char *to, const char *message, const char *password); -my_bool check_scramble(const char *reply, const char *message, +my_bool check_scramble(const unsigned char *reply, const char *message, const unsigned char *hash_stage2); void get_salt_from_password(unsigned char *res, const char *password); void make_password_from_salt(char *to, const unsigned char *hash_stage2); diff --git a/include/sql_common.h b/include/sql_common.h index 5fd8778d62b..a9a3168b691 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -16,14 +16,60 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define SQL_COMMON_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +#include <mysql.h> extern const char *unknown_sqlstate; extern const char *cant_connect_sqlstate; extern const char *not_error_sqlstate; -#ifdef __cplusplus -extern "C" { +struct st_mysql_options_extention { + char *plugin_dir; + char *default_auth; +}; + +typedef struct st_mysql_methods +{ + my_bool (*read_query_result)(MYSQL *mysql); + my_bool (*advanced_command)(MYSQL *mysql, + enum enum_server_command command, + const unsigned char *header, + unsigned long header_length, + const unsigned char *arg, + unsigned long arg_length, + my_bool skip_check, + MYSQL_STMT *stmt); + MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + unsigned int fields); + MYSQL_RES * (*use_result)(MYSQL *mysql); + void (*fetch_lengths)(unsigned long *to, + MYSQL_ROW column, unsigned int field_count); + void (*flush_use_result)(MYSQL *mysql, my_bool flush_all_results); + int (*read_change_user_result)(MYSQL *mysql); +#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) + MYSQL_FIELD * (*list_fields)(MYSQL *mysql); + my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); + int (*stmt_execute)(MYSQL_STMT *stmt); + int (*read_binary_rows)(MYSQL_STMT *stmt); + int (*unbuffered_fetch)(MYSQL *mysql, char **row); + void (*free_embedded_thd)(MYSQL *mysql); + const char *(*read_statistics)(MYSQL *mysql); + my_bool (*next_result)(MYSQL *mysql); + int (*read_rows_from_cursor)(MYSQL_STMT *stmt); #endif +} MYSQL_METHODS; + +#define simple_command(mysql, command, arg, length, skip_check) \ + (*(mysql)->methods->advanced_command)(mysql, command, 0, \ + 0, arg, length, skip_check, NULL) +#define stmt_command(mysql, command, arg, length, stmt) \ + (*(mysql)->methods->advanced_command)(mysql, command, 0, \ + 0, arg, length, 1, stmt) extern CHARSET_INFO *default_client_charset_info; MYSQL_FIELD *unpack_fields(MYSQL_DATA *data,MEM_ROOT *alloc,uint fields, @@ -45,6 +91,19 @@ void set_stmt_errmsg(MYSQL_STMT *stmt, NET *net); void set_stmt_error(MYSQL_STMT *stmt, int errcode, const char *sqlstate, const char *err); void set_mysql_error(MYSQL *mysql, int errcode, const char *sqlstate); +void set_mysql_extended_error(MYSQL *mysql, int errcode, const char *sqlstate, + const char *format, ...); + +/* client side of the pluggable authentication */ +struct st_plugin_vio_info; +void mpvio_info(Vio *vio, struct st_plugin_vio_info *info); +int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, + const char *data_plugin, const char *db); +int mysql_client_plugin_init(); +void mysql_client_plugin_deinit(); +struct st_mysql_client_plugin; +extern struct st_mysql_client_plugin *mysql_client_builtins[]; + #ifdef __cplusplus } #endif diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt index 2ae09c1707a..f5fa1f7a009 100644 --- a/libmysql/CMakeLists.txt +++ b/libmysql/CMakeLists.txt @@ -140,6 +140,7 @@ SET(CLIENT_SOURCES errmsg.c ../sql-common/client.c ../sql-common/my_time.c + ../sql-common/client_plugin.c ../sql/net_serv.cc ../sql-common/pack.c ../sql/password.c @@ -148,7 +149,7 @@ ADD_CONVENIENCE_LIBRARY(clientlib ${CLIENT_SOURCES}) DTRACE_INSTRUMENT(clientlib) ADD_DEPENDENCIES(clientlib GenError) -SET(LIBS clientlib dbug strings vio mysys ${ZLIB_LIBRARY} ${SSL_LIBRARIES}) +SET(LIBS clientlib dbug strings vio mysys ${ZLIB_LIBRARY} ${SSL_LIBRARIES} ${LIBDL}) # Merge several convenience libraries into one big mysqlclient # and link them together into shared library. diff --git a/libmysql/Makefile.shared b/libmysql/Makefile.shared index 887af62229a..5a7236f1e6d 100644 --- a/libmysql/Makefile.shared +++ b/libmysql/Makefile.shared @@ -23,6 +23,7 @@ MYSQLDATAdir = $(localstatedir) MYSQLSHAREdir = $(pkgdatadir) MYSQLBASEdir= $(prefix) +pkgplugindir = $(pkglibdir)/plugin ## We'll use CLIENT_EXTRA_LDFLAGS for threaded and non-threaded ## until someone complains that they need separate options. LDADD = @CLIENT_EXTRA_LDFLAGS@ $(target) @@ -70,26 +71,27 @@ mysysobjects1 = my_init.lo my_static.lo my_malloc.lo \ my_rename.lo my_chsize.lo my_sync.lo \ my_getsystime.lo my_symlink2.lo mf_same.lo sqlobjects = net.lo -sql_cmn_objects = pack.lo client.lo my_time.lo +sql_cmn_objects = pack.lo client.lo my_time.lo client_plugin.lo # Not needed in the minimum library mysysobjects2 = my_lib.lo mf_qsort.lo mysysobjects = $(mysysobjects1) $(mysysobjects2) target_libadd = $(mysysobjects) $(mystringsobjects) $(dbugobjects) \ $(sql_cmn_objects) $(vio_objects) $(sqlobjects) -target_ldflags = -version-info @SHARED_LIB_VERSION@ @LD_VERSION_SCRIPT@ +target_ldflags = -version-info @SHARED_LIB_VERSION@ @LD_VERSION_SCRIPT@ @LIBDL@ vio_objects= vio.lo viosocket.lo viossl.lo viosslfactories.lo BUILT_SOURCES = link_sources CLEANFILES = $(target_libadd) $(SHLIBOBJS) \ $(target) $(BUILT_SOURCES) -DEFS = -DDEFAULT_CHARSET_HOME="\"$(MYSQLBASEdir)\"" \ - -DMYSQL_DATADIR="\"$(MYSQLDATAdir)\"" \ +DEFS = -DDEFAULT_CHARSET_HOME='"$(MYSQLBASEdir)"' \ + -DMYSQL_DATADIR='"$(MYSQLDATAdir)"' \ -DDEFAULT_HOME_ENV=MYSQL_HOME \ + -DPLUGINDIR='"$(pkgplugindir)"' \ -DDEFAULT_GROUP_SUFFIX_ENV=MYSQL_GROUP_SUFFIX \ - -DDEFAULT_SYSCONFDIR="\"$(sysconfdir)\"" \ - -DSHAREDIR="\"$(MYSQLSHAREdir)\"" -DDISABLE_DTRACE \ + -DDEFAULT_SYSCONFDIR='"$(sysconfdir)"' \ + -DSHAREDIR='"$(MYSQLSHAREdir)"' -DDISABLE_DTRACE \ $(target_defs) if HAVE_YASSL diff --git a/libmysql/client_settings.h b/libmysql/client_settings.h index aaec08d1b1e..5204d03e5af 100644 --- a/libmysql/client_settings.h +++ b/libmysql/client_settings.h @@ -28,7 +28,8 @@ extern char * mysql_unix_port; CLIENT_PROTOCOL_41 | \ CLIENT_SECURE_CONNECTION | \ CLIENT_MULTI_RESULTS | \ - CLIENT_PS_MULTI_RESULTS) + CLIENT_PS_MULTI_RESULTS | \ + CLIENT_PLUGIN_AUTH) sig_handler my_pipe_sig_handler(int sig); void read_user_name(char *name); @@ -67,7 +68,7 @@ int cli_stmt_execute(MYSQL_STMT *stmt); int cli_read_binary_rows(MYSQL_STMT *stmt); int cli_unbuffered_fetch(MYSQL *mysql, char **row); const char * cli_read_statistics(MYSQL *mysql); -int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd); +int cli_read_change_user_result(MYSQL *mysql); #ifdef EMBEDDED_LIBRARY int init_embedded_server(int argc, char **argv, char **groups); diff --git a/libmysql/errmsg.c b/libmysql/errmsg.c index febbded6af2..823f83026c9 100644 --- a/libmysql/errmsg.c +++ b/libmysql/errmsg.c @@ -84,7 +84,8 @@ const char *client_errors[]= "Lost connection to MySQL server at '%s', system error: %d", "Statement closed indirectly because of a preceeding %s() call", "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again", - "This handle is already connected. Use a separate handle for each connection." + "This handle is already connected. Use a separate handle for each connection.", + "Authentication plugin '%s' cannot be loaded: %s", "" }; diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 550b1b7b107..c90af040c5f 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -126,6 +126,8 @@ int STDCALL mysql_server_init(int argc __attribute__((unused)), if (my_init()) /* Will init threads */ return 1; init_client_errs(); + if (mysql_client_plugin_init()) + return 1; if (!mysql_port) { char *env; @@ -196,6 +198,8 @@ void STDCALL mysql_server_end() if (!mysql_client_init) return; + mysql_client_plugin_deinit(); + #ifdef EMBEDDED_LIBRARY end_embedded_server(); #endif @@ -345,44 +349,14 @@ mysql_connect(MYSQL *mysql,const char *host, Change user and database **************************************************************************/ -int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) -{ - NET *net= &mysql->net; - ulong pkt_length; - - pkt_length= cli_safe_read(mysql); - - if (pkt_length == packet_error) - return 1; - - if (pkt_length == 1 && net->read_pos[0] == 254 && - mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - /* - By sending this very specific reply server asks us to send scrambled - password in old format. The reply contains scramble_323. - */ - scramble_323(buff, mysql->scramble, passwd); - if (my_net_write(net, (uchar*) buff, SCRAMBLE_LENGTH_323 + 1) || - net_flush(net)) - { - set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); - return 1; - } - /* Read what server thinks about out new auth message report */ - if (cli_safe_read(mysql) == packet_error) - return 1; - } - return 0; -} - my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, const char *db) { - char buff[USERNAME_LENGTH+SCRAMBLED_PASSWORD_CHAR_LENGTH+NAME_LEN+2]; - char *end= buff; int rc; CHARSET_INFO *saved_cs= mysql->charset; + char *saved_user= mysql->user; + char *saved_passwd= mysql->passwd; + char *saved_db= mysql->db; DBUG_ENTER("mysql_change_user"); @@ -396,49 +370,11 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, /* Use an empty string instead of NULL. */ - if (!user) - user=""; - if (!passwd) - passwd=""; - - /* - Store user into the buffer. - Advance position as strmake returns a pointer to the closing NUL. - */ - end= strmake(end, user, USERNAME_LENGTH) + 1; - - /* write scrambled password according to server capabilities */ - if (passwd[0]) - { - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - *end++= SCRAMBLE_LENGTH; - scramble(end, mysql->scramble, passwd); - end+= SCRAMBLE_LENGTH; - } - else - { - scramble_323(end, mysql->scramble, passwd); - end+= SCRAMBLE_LENGTH_323 + 1; - } - } - else - *end++= '\0'; /* empty password */ - /* Add database if needed */ - end= strmake(end, db ? db : "", NAME_LEN) + 1; - - /* Add character set number. */ - - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - int2store(end, (ushort) mysql->charset->number); - end+= 2; - } - - /* Write authentication package */ - simple_command(mysql,COM_CHANGE_USER, (uchar*) buff, (ulong) (end-buff), 1); + mysql->user= (char*)(user ? user : ""); + mysql->passwd= (char*)(passwd ? passwd : ""); + mysql->db= 0; - rc= (*mysql->methods->read_change_user_result)(mysql, buff, passwd); + rc= run_plugin_auth(mysql, 0, 0, 0, db); /* The server will close all statements no matter was the attempt @@ -448,18 +384,21 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, if (rc == 0) { /* Free old connect information */ - my_free(mysql->user); - my_free(mysql->passwd); - my_free(mysql->db); + my_free(saved_user); + my_free(saved_passwd); + my_free(saved_db); /* alloc new connect information */ - mysql->user= my_strdup(user,MYF(MY_WME)); - mysql->passwd=my_strdup(passwd,MYF(MY_WME)); - mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0; + mysql->user= my_strdup(mysql->user, MYF(MY_WME)); + mysql->passwd= my_strdup(mysql->passwd, MYF(MY_WME)); + mysql->db= db ? my_strdup(db, MYF(MY_WME)) : 0; } else { mysql->charset= saved_cs; + mysql->user= saved_user; + mysql->passwd= saved_passwd; + mysql->db= saved_db; } DBUG_RETURN(rc); diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index a7efcb024ec..a658ee9d55e 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -42,6 +42,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../libmysql/libmysql.c ../libmysql/errmsg.c ../client/get_password.c ../sql-common/client.c ../sql-common/my_time.c ../sql-common/my_user.c ../sql-common/pack.c + ../sql-common/client_plugin.c ../sql/password.c ../sql/discover.cc ../sql/derror.cc ../sql/field.cc ../sql/field_conv.cc ../sql/filesort.cc ../sql/gstream.cc diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index 1ffa349bcfe..e2521bfcac7 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -43,7 +43,7 @@ pkglib_LIBRARIES = libmysqld.a SUBDIRS = . examples libmysqld_sources= libmysqld.c lib_sql.cc emb_qcache.cc libmysqlsources = errmsg.c get_password.c libmysql.c client.c pack.c \ - my_time.c + my_time.c client_plugin.c noinst_HEADERS = embedded_priv.h emb_qcache.h diff --git a/libmysqld/embedded_priv.h b/libmysqld/embedded_priv.h index 369b344d4bd..c246693594b 100644 --- a/libmysqld/embedded_priv.h +++ b/libmysqld/embedded_priv.h @@ -15,6 +15,8 @@ /* Prototypes for the embedded version of MySQL */ +#include <sql_common.h> + C_MODE_START void lib_connection_phase(NET *net, int phase); void init_embedded_mysql(MYSQL *mysql, int client_flag); diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index 94927c590cf..a1a57e70499 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -35,7 +35,6 @@ C_MODE_START #include <mysql.h> #undef ER #include "errmsg.h" -#include <sql_common.h> #include "embedded_priv.h" extern unsigned int mysql_server_last_errno; @@ -414,11 +413,10 @@ static MYSQL_RES * emb_store_result(MYSQL *mysql) return mysql_store_result(mysql); } -int emb_read_change_user_result(MYSQL *mysql, - char *buff __attribute__((unused)), - const char *passwd __attribute__((unused))) +int emb_read_change_user_result(MYSQL *mysql) { - return mysql_errno(mysql); + mysql->net.read_pos= (uchar*)""; // fake an OK packet + return mysql_errno(mysql) ? packet_error : 1 /* length of the OK packet */; } MYSQL_METHODS embedded_methods= @@ -429,6 +427,7 @@ MYSQL_METHODS embedded_methods= emb_store_result, emb_fetch_lengths, emb_flush_use_result, + emb_read_change_user_result, emb_list_fields, emb_read_prepare_result, emb_stmt_execute, @@ -437,7 +436,6 @@ MYSQL_METHODS embedded_methods= emb_free_embedded_thd, emb_read_statistics, emb_read_query_result, - emb_read_change_user_result, emb_read_rows_from_cursor }; @@ -601,6 +599,7 @@ void init_embedded_mysql(MYSQL *mysql, int client_flag) THD *thd = (THD *)mysql->thd; thd->mysql= mysql; mysql->server_version= server_version; + mysql->client_flag= client_flag; init_alloc_root(&mysql->field_alloc, 8192, 0); } @@ -664,14 +663,20 @@ err: int check_embedded_connection(MYSQL *mysql, const char *db) { int result; + LEX_STRING db_str = { (char*)db, db ? strlen(db) : 0 }; THD *thd= (THD*)mysql->thd; thd_init_client_charset(thd, mysql->charset->number); thd->update_charset(); Security_context *sctx= thd->security_ctx; sctx->host_or_ip= sctx->host= (char*) my_localhost; strmake(sctx->priv_host, (char*) my_localhost, MAX_HOSTNAME-1); - sctx->priv_user= sctx->user= my_strdup(mysql->user, MYF(0)); - result= check_user(thd, COM_CONNECT, NULL, 0, db, true); + strmake(sctx->priv_user, mysql->user, USERNAME_LENGTH-1); + sctx->user= my_strdup(mysql->user, MYF(0)); + sctx->proxy_user[0]= 0; + sctx->master_access= GLOBAL_ACLS; // Full rights + /* Change database if necessary */ + if (!(result= (db && db[0] && mysql_change_db(thd, &db_str, FALSE)))) + my_ok(thd); thd->protocol->end_statement(); emb_read_query_result(mysql); return result; @@ -680,14 +685,15 @@ int check_embedded_connection(MYSQL *mysql, const char *db) #else int check_embedded_connection(MYSQL *mysql, const char *db) { + /* + we emulate a COM_CHANGE_USER user here, + it's easier than to emulate the complete 3-way handshake + */ + char buf[USERNAME_LENGTH + SCRAMBLE_LENGTH + 1 + 2*NAME_LEN + 2], *end; + NET *net= &mysql->net; THD *thd= (THD*)mysql->thd; Security_context *sctx= thd->security_ctx; - int result; - char scramble_buff[SCRAMBLE_LENGTH]; - int passwd_len; - thd_init_client_charset(thd, mysql->charset->number); - thd->update_charset(); if (mysql->options.client_ip) { sctx->host= my_strdup(mysql->options.client_ip, MYF(0)); @@ -698,37 +704,43 @@ int check_embedded_connection(MYSQL *mysql, const char *db) sctx->host_or_ip= sctx->host; if (acl_check_host(sctx->host, sctx->ip)) - { - result= ER_HOST_NOT_PRIVILEGED; goto err; - } - sctx->user= my_strdup(mysql->user, MYF(0)); + /* construct a COM_CHANGE_USER packet */ + end= strmake(buf, mysql->user, USERNAME_LENGTH) + 1; + + memset(thd->scramble, 55, SCRAMBLE_LENGTH); // dummy scramble + thd->scramble[SCRAMBLE_LENGTH]= 0; + if (mysql->passwd && mysql->passwd[0]) { - memset(thd->scramble, 55, SCRAMBLE_LENGTH); // dummy scramble - thd->scramble[SCRAMBLE_LENGTH]= 0; - scramble(scramble_buff, thd->scramble, mysql->passwd); - passwd_len= SCRAMBLE_LENGTH; + *end++= SCRAMBLE_LENGTH; + scramble(end, thd->scramble, mysql->passwd); + end+= SCRAMBLE_LENGTH; } else - passwd_len= 0; + *end++= 0; - if((result= check_user(thd, COM_CONNECT, - scramble_buff, passwd_len, db, true))) - goto err; + end= strmake(end, db ? db : "", NAME_LEN) + 1; - return 0; -err: + int2store(end, (ushort) mysql->charset->number); + end+= 2; + + /* acl_authenticate() takes the data from thd->net->read_pos */ + thd->net.read_pos= (uchar*)buf; + + if (acl_authenticate(thd, 0, end - buf)) { - NET *net= &mysql->net; - strmake(net->last_error, thd->stmt_da->message(), - sizeof(net->last_error)-1); - memcpy(net->sqlstate, - mysql_errno_to_sqlstate(thd->stmt_da->sql_errno()), - sizeof(net->sqlstate)-1); + x_free(thd->security_ctx->user); + goto err; } - return result; + return 0; +err: + strmake(net->last_error, thd->main_da.message(), sizeof(net->last_error)-1); + memcpy(net->sqlstate, + mysql_errno_to_sqlstate(thd->main_da.sql_errno()), + sizeof(net->sqlstate)-1); + return 1; } #endif diff --git a/libmysqld/libmysqld.c b/libmysqld/libmysqld.c index 603fc3bc2f0..f6f0e1ba3db 100644 --- a/libmysqld/libmysqld.c +++ b/libmysqld/libmysqld.c @@ -17,7 +17,6 @@ #include <mysql.h> #include <mysqld_error.h> #include <my_pthread.h> -#include "embedded_priv.h" #include <my_sys.h> #include <mysys_err.h> #include <m_string.h> @@ -28,6 +27,7 @@ #include <signal.h> #include <time.h> #include <sql_common.h> +#include "embedded_priv.h" #include "client_settings.h" #ifdef HAVE_PWD_H #include <pwd.h> @@ -165,7 +165,11 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, client_flag|=CLIENT_CAPABILITIES; if (client_flag & CLIENT_MULTI_STATEMENTS) client_flag|= CLIENT_MULTI_RESULTS; - client_flag&= ~CLIENT_COMPRESS; + /* + no compression in embedded as we don't send any data, + and no pluggable auth, as we cannot do a client-server dialog + */ + client_flag&= ~(CLIENT_COMPRESS | CLIENT_PLUGIN_AUTH); if (db) client_flag|=CLIENT_CONNECT_WITH_DB; diff --git a/mysql-test/include/have_plugin_auth.inc b/mysql-test/include/have_plugin_auth.inc new file mode 100644 index 00000000000..41f11419c64 --- /dev/null +++ b/mysql-test/include/have_plugin_auth.inc @@ -0,0 +1,4 @@ +disable_query_log; +--require r/true.require +select (PLUGIN_LIBRARY LIKE 'auth_test_plugin%') as `TRUE` FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME='test_plugin_server'; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 1a574fe6e6b..a23a8ee7261 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -126,6 +126,9 @@ my $path_vardir_trace; # unix formatted opt_vardir for trace files my $opt_tmpdir; # Path to use for tmp/ dir my $opt_tmpdir_pid; +my $auth_filename; # the name of the authentication test plugin +my $auth_plugin; # the path to the authentication test plugin + END { if ( defined $opt_tmpdir_pid and $opt_tmpdir_pid == $$ ) { @@ -1025,6 +1028,22 @@ sub command_line_setup { "$basedir/sql/share/charsets", "$basedir/share/charsets"); + # Look for client test plugin + if (IS_WINDOWS) + { + $auth_filename = "auth_test_plugin.dll"; + } + else + { + $auth_filename = "auth_test_plugin.so"; + } + $auth_plugin= + mtr_file_exists(vs_config_dirs('plugin/auth/',$auth_filename), + "$basedir/plugin/auth/.libs/" . $auth_filename, + "$basedir/lib/mysql/plugin/" . $auth_filename, + "$basedir/lib/plugin/" . $auth_filename); + + if (using_extern()) { # Connect to the running mysqld and find out what it supports @@ -1897,6 +1916,24 @@ sub environment_setup { ($lib_udf_example ? dirname($lib_udf_example) : ""); # -------------------------------------------------------------------------- + # Add the path where mysqld will find the auth test plugin (dialog.so/dll) + # -------------------------------------------------------------------------- + if ($auth_plugin) + { + $ENV{'PLUGIN_AUTH'}= basename($auth_plugin); + $ENV{'PLUGIN_AUTH_OPT'}= "--plugin-dir=".dirname($auth_plugin); + + $ENV{'PLUGIN_AUTH_LOAD'}="--plugin_load=test_plugin_server=".$auth_filename; + } + else + { + $ENV{'PLUGIN_AUTH'}= ""; + $ENV{'PLUGIN_AUTH_OPT'}="--plugin-dir="; + $ENV{'PLUGIN_AUTH_LOAD'}=""; + } + + + # -------------------------------------------------------------------------- # Add the path where mysqld will find ha_example.so # -------------------------------------------------------------------------- if ($mysql_version_id >= 50100) { @@ -4918,6 +4955,10 @@ sub start_mysqltest ($) { mtr_add_arg($args, "--tmpdir=%s", $opt_tmpdir); mtr_add_arg($args, "--character-sets-dir=%s", $path_charsetsdir); mtr_add_arg($args, "--logdir=%s/log", $opt_vardir); + if ($auth_plugin) + { + mtr_add_arg($args, "--plugin_dir=%s", dirname($auth_plugin)); + } # Log line number and time for each line in .test file mtr_add_arg($args, "--mark-progress") diff --git a/mysql-test/r/1st.result b/mysql-test/r/1st.result index ae9989ce563..e8562662bfd 100644 --- a/mysql-test/r/1st.result +++ b/mysql-test/r/1st.result @@ -21,6 +21,7 @@ ndb_binlog_index plugin proc procs_priv +proxy_priv servers slow_log tables_priv diff --git a/mysql-test/r/change_user.result b/mysql-test/r/change_user.result index 1ed7fcbb8fa..3021b5472bf 100644 --- a/mysql-test/r/change_user.result +++ b/mysql-test/r/change_user.result @@ -1,3 +1,39 @@ +grant select on test.* to test_nopw; +grant select on test.* to test_oldpw identified by password "09301740536db389"; +grant select on test.* to test_newpw identified by "newpw"; +select user(), current_user(), database(); +user() current_user() database() +root@localhost root@localhost test +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<root@localhost> <root@localhost> test +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_nopw@localhost> <test_nopw@%> NULL +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_oldpw@localhost> <test_oldpw@%> NULL +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_newpw@localhost> <test_newpw@%> NULL +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<root@localhost> <root@localhost> NULL +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_nopw@localhost> <test_nopw@%> test +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_oldpw@localhost> <test_oldpw@%> test +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<test_newpw@localhost> <test_newpw@%> test +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +concat('<', user(), '>') concat('<', current_user(), '>') database() +<root@localhost> <root@localhost> test +drop user test_nopw; +drop user test_oldpw; +drop user test_newpw; Bug#20023 SELECT @@session.sql_big_selects; @@session.sql_big_selects diff --git a/mysql-test/r/connect.result b/mysql-test/r/connect.result index 5e6c013bb38..bbd0273c1c6 100644 --- a/mysql-test/r/connect.result +++ b/mysql-test/r/connect.result @@ -15,6 +15,7 @@ ndb_binlog_index plugin proc procs_priv +proxy_priv servers slow_log tables_priv @@ -48,6 +49,7 @@ ndb_binlog_index plugin proc procs_priv +proxy_priv servers slow_log tables_priv @@ -89,6 +91,7 @@ ndb_binlog_index plugin proc procs_priv +proxy_priv servers slow_log tables_priv diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index a5003c936e8..a27c7dc18ee 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -568,6 +568,7 @@ USE test; SHOW GRANTS FOR CURRENT_USER; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION SET GLOBAL event_scheduler = ON; CREATE TABLE events_test.event_log (id int KEY AUTO_INCREMENT, ev_nm char(40), ev_cnt int, ev_tm timestamp) diff --git a/mysql-test/r/grant.result b/mysql-test/r/grant.result index 65ebbd71c39..84cac386b57 100644 --- a/mysql-test/r/grant.result +++ b/mysql-test/r/grant.result @@ -13,8 +13,48 @@ GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3 GRANT SELECT ON `mysqltest`.* TO 'mysqltest_1'@'localhost' grant delete on mysqltest.* to mysqltest_1@localhost; select * from mysql.user where user="mysqltest_1"; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost mysqltest_1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N SPECIFIED EDH-RSA-DES-CBC3-SHA 0 0 0 0 +Host localhost +User mysqltest_1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type SPECIFIED +ssl_cipher EDH-RSA-DES-CBC3-SHA +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string show grants for mysqltest_1@localhost; Grants for mysqltest_1@localhost GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3-SHA' @@ -44,15 +84,95 @@ delete from mysql.user where user='mysqltest_1'; flush privileges; grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 10; select * from mysql.user where user="mysqltest_1"; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost mysqltest_1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 10 0 0 0 +Host localhost +User mysqltest_1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 10 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string show grants for mysqltest_1@localhost; Grants for mysqltest_1@localhost GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' WITH MAX_QUERIES_PER_HOUR 10 grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 20 max_connections_per_hour 30; select * from mysql.user where user="mysqltest_1"; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost mysqltest_1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 10 20 30 0 +Host localhost +User mysqltest_1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 10 +max_updates 20 +max_connections 30 +max_user_connections 0 +plugin +authentication_string show grants for mysqltest_1@localhost; Grants for mysqltest_1@localhost GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' WITH MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 20 MAX_CONNECTIONS_PER_HOUR 30 @@ -164,6 +284,7 @@ Warnings: Warning 1364 Field 'ssl_cipher' doesn't have a default value Warning 1364 Field 'x509_issuer' doesn't have a default value Warning 1364 Field 'x509_subject' doesn't have a default value +Warning 1364 Field 'authentication_string' doesn't have a default value insert into mysql.db (host, db, user, select_priv) values ('localhost', 'a%', 'test11', 'Y'), ('localhost', 'ab%', 'test11', 'Y'); alter table mysql.db order by db asc; @@ -625,16 +746,19 @@ show grants for root@localhost; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION GRANT SELECT ON `ÂÄ`.* TO 'root'@'localhost' +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION flush privileges; show grants for root@localhost; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION GRANT SELECT ON `ÂÄ`.* TO 'root'@'localhost' +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop database ÂÄ; revoke all privileges on ÂÄ.* from root@localhost; show grants for root@localhost; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION set names latin1; create user mysqltest_7@; set password for mysqltest_7@ = password('systpass'); diff --git a/mysql-test/r/grant2.result b/mysql-test/r/grant2.result index 3032ca854bd..b5e82794658 100644 --- a/mysql-test/r/grant2.result +++ b/mysql-test/r/grant2.result @@ -11,7 +11,7 @@ grant create user on *.* to mysqltest_1@localhost; create user mysqltest_2@localhost; grant select on `my\_1`.* to mysqltest_2@localhost; grant select on `my\_1`.* to mysqltest_2@localhost identified by 'pass'; -ERROR 42000: You must have privileges to update tables in the mysql database to be able to change passwords for others +ERROR 42000: Access denied for user 'mysqltest_1'@'localhost' to database 'mysql' grant update on mysql.* to mysqltest_1@localhost; grant select on `my\_1`.* to mysqltest_2@localhost identified by 'pass'; grant select on `my\_1`.* to mysqltest_3@localhost; @@ -287,6 +287,7 @@ Warnings: Warning 1364 Field 'ssl_cipher' doesn't have a default value Warning 1364 Field 'x509_issuer' doesn't have a default value Warning 1364 Field 'x509_subject' doesn't have a default value +Warning 1364 Field 'authentication_string' doesn't have a default value create user mysqltest_A@'%'; rename user mysqltest_B@'%' to mysqltest_C@'%'; drop user mysqltest_C@'%'; @@ -334,7 +335,7 @@ delete from mysql.user where user like 'mysqltest\_1'; flush privileges; drop database mysqltest_1; set password = password("changed"); -ERROR 42000: Access denied for user ''@'localhost' to database 'mysql' +ERROR 42000: Can't find any matching row in the user table lock table mysql.user write; flush privileges; grant all on *.* to 'mysqltest_1'@'localhost'; @@ -354,6 +355,7 @@ Warnings: Warning 1364 Field 'ssl_cipher' doesn't have a default value Warning 1364 Field 'x509_issuer' doesn't have a default value Warning 1364 Field 'x509_subject' doesn't have a default value +Warning 1364 Field 'authentication_string' doesn't have a default value INSERT INTO mysql.db (host, db, user, select_priv) VALUES ('%','TESTDB','mysqltest_1','Y'); FLUSH PRIVILEGES; diff --git a/mysql-test/r/grant_cache_no_prot.result b/mysql-test/r/grant_cache_no_prot.result index 32bb9cce90e..019edb72086 100644 --- a/mysql-test/r/grant_cache_no_prot.result +++ b/mysql-test/r/grant_cache_no_prot.result @@ -7,9 +7,11 @@ flush status; show grants for current_user; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION create database if not exists mysqltest; create table mysqltest.t1 (a int,b int,c int); create table mysqltest.t2 (a int,b int,c int); diff --git a/mysql-test/r/grant_cache_ps_prot.result b/mysql-test/r/grant_cache_ps_prot.result index 281468ee2e1..e95a858fd9a 100644 --- a/mysql-test/r/grant_cache_ps_prot.result +++ b/mysql-test/r/grant_cache_ps_prot.result @@ -7,9 +7,11 @@ flush status; show grants for current_user; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION create database if not exists mysqltest; create table mysqltest.t1 (a int,b int,c int); create table mysqltest.t2 (a int,b int,c int); diff --git a/mysql-test/r/information_schema.result b/mysql-test/r/information_schema.result index 21b42577a11..aa47b8c437e 100644 --- a/mysql-test/r/information_schema.result +++ b/mysql-test/r/information_schema.result @@ -88,6 +88,7 @@ host plugin proc procs_priv +proxy_priv servers slow_log tables_priv @@ -684,6 +685,7 @@ Alter_routine_priv select,insert,update,references max_questions select,insert,update,references max_connections select,insert,update,references max_user_connections select,insert,update,references +authentication_string select,insert,update,references use test; create function sub1(i int) returns int return i+1; @@ -870,7 +872,7 @@ AND table_name not like 'ndb%' AND table_name not like 'innodb_%' GROUP BY TABLE_SCHEMA; table_schema count(*) information_schema 30 -mysql 22 +mysql 23 create table t1 (i int, j int); create trigger trg1 before insert on t1 for each row begin diff --git a/mysql-test/r/log_tables_upgrade.result b/mysql-test/r/log_tables_upgrade.result index 5d9be85a48a..10cc6d71eae 100644 --- a/mysql-test/r/log_tables_upgrade.result +++ b/mysql-test/r/log_tables_upgrade.result @@ -29,6 +29,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.renamed_general_log OK mysql.servers OK mysql.slow_log diff --git a/mysql-test/r/mysql_upgrade.result b/mysql-test/r/mysql_upgrade.result index 58f6ffd4040..3d5a6cfebdb 100644 --- a/mysql-test/r/mysql_upgrade.result +++ b/mysql-test/r/mysql_upgrade.result @@ -17,6 +17,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. @@ -49,6 +50,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. @@ -81,6 +83,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. @@ -115,6 +118,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. @@ -153,6 +157,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. @@ -194,6 +199,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log Error : You can't use locks with log tables. diff --git a/mysql-test/r/mysqlcheck.result b/mysql-test/r/mysqlcheck.result index 06175955d7f..c51d71510f4 100644 --- a/mysql-test/r/mysqlcheck.result +++ b/mysql-test/r/mysqlcheck.result @@ -18,6 +18,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log note : The storage engine for the table doesn't support optimize @@ -43,6 +44,7 @@ mysql.ndb_binlog_index OK mysql.plugin OK mysql.proc OK mysql.procs_priv OK +mysql.proxy_priv OK mysql.servers OK mysql.slow_log note : The storage engine for the table doesn't support optimize diff --git a/mysql-test/r/plugin_auth.result b/mysql-test/r/plugin_auth.result new file mode 100644 index 00000000000..8a851af0ecb --- /dev/null +++ b/mysql-test/r/plugin_auth.result @@ -0,0 +1,212 @@ +SELECT PLUGIN_STATUS, PLUGIN_TYPE, PLUGIN_DESCRIPTION +FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='test_plugin_server'; +PLUGIN_STATUS ACTIVE +PLUGIN_TYPE AUTHENTICATION +PLUGIN_DESCRIPTION plugin API test plugin +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; +SELECT plugin,authentication_string FROM mysql.user WHERE User='plug'; +plugin authentication_string +test_plugin_server plug_dest +## test plugin auth +ERROR 28000: Access denied for user 'plug'@'localhost' (using password: YES) +GRANT PROXY ON plug_dest TO plug; +select USER(),CURRENT_USER(); +USER() CURRENT_USER() +plug@localhost plug_dest@% +## test SET PASSWORD +SET PASSWORD = PASSWORD('plug_dest'); +Warnings: +Note 1698 SET PASSWORD has no significance for users authenticating via plugins +## test bad credentials +ERROR 28000: Access denied for user 'plug'@'localhost' (using password: YES) +## test bad default plugin : should get CR_AUTH_PLUGIN_CANNOT_LOAD +## test correct default plugin +select USER(),CURRENT_USER(); +USER() CURRENT_USER() +plug@localhost plug@% +## test no_auto_create_user sql mode with plugin users +SET @@sql_mode=no_auto_create_user; +GRANT INSERT ON TEST.* TO grant_user IDENTIFIED WITH 'test_plugin_server'; +SET @@sql_mode=default; +DROP USER grant_user; +## test utf-8 user name +CREATE USER `Ÿ` IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +GRANT PROXY ON plug_dest TO `Ÿ`; +select USER(),CURRENT_USER(); +USER() CURRENT_USER() +Ÿ@localhost plug_dest@% +DROP USER `Ÿ`; +## test GRANT ... IDENTIFIED WITH/BY ... +CREATE DATABASE test_grant_db; +# create new user via GRANT WITH +GRANT ALL PRIVILEGES ON test_grant_db.* TO new_grant_user +IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +GRANT PROXY ON plug_dest TO new_grant_user; +select USER(),CURRENT_USER(); +USER() CURRENT_USER() +new_grant_user@localhost plug_dest@% +USE test_grant_db; +CREATE TABLE t1 (a INT); +DROP TABLE t1; +REVOKE ALL PRIVILEGES ON test_grant_db.* FROM new_grant_user; +# try re-create existing user via GRANT IDENTIFIED BY +GRANT ALL PRIVILEGES ON test_grant_db.* TO new_grant_user +IDENTIFIED BY 'unused_password'; +# make sure password doesn't take precendence +ERROR 28000: Access denied for user 'new_grant_user'@'localhost' (using password: YES) +#make sure plugin auth still available +select USER(),CURRENT_USER(); +USER() CURRENT_USER() +new_grant_user@localhost plug_dest@% +USE test_grant_db; +CREATE TABLE t1 (a INT); +DROP TABLE t1; +DROP USER new_grant_user; +# try re-create existing user via GRANT IDENTIFIED WITH +GRANT ALL PRIVILEGES ON test_grant_db.* TO plug +IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +ERROR HY000: GRANT with IDENTIFIED WITH is illegal because the user plug already exists +GRANT ALL PRIVILEGES ON test_grant_db.* TO plug_dest +IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +ERROR HY000: GRANT with IDENTIFIED WITH is illegal because the user plug_dest already exists +REVOKE SELECT on test_grant_db.* FROM joro +INDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'' at line 2 +REVOKE SELECT on test_grant_db.* FROM joro +INDENTIFIED BY 'plug_dest_passwd'; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDENTIFIED BY 'plug_dest_passwd'' at line 2 +REVOKE SELECT on test_grant_db.* FROM joro +INDENTIFIED BY PASSWORD 'plug_dest_passwd'; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDENTIFIED BY PASSWORD 'plug_dest_passwd'' at line 2 +DROP DATABASE test_grant_db; +## GRANT PROXY tests +CREATE USER grant_plug IDENTIFIED WITH 'test_plugin_server' +AS 'grant_plug_dest'; +CREATE USER grant_plug_dest IDENTIFIED BY 'grant_plug_dest_passwd'; +CREATE USER grant_plug_dest2 IDENTIFIED BY 'grant_plug_dest_passwd2'; +# ALL PRIVILEGES doesn't include PROXY +GRANT ALL PRIVILEGES ON *.* TO grant_plug; +ERROR 28000: Access denied for user 'grant_plug'@'localhost' (using password: YES) +GRANT ALL PRIVILEGES,PROXY ON grant_plug_dest TO grant_plug; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PROXY ON grant_plug_dest TO grant_plug' at line 1 +this should fail : can't combine PROXY +GRANT ALL SELECT,PROXY ON grant_plug_dest TO grant_plug; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT,PROXY ON grant_plug_dest TO grant_plug' at line 1 +# this should fail : no such grant +REVOKE PROXY ON grant_plug_dest FROM grant_plug; +ERROR 42000: There is no such grant defined for user 'grant_plug' on host '%' +in grant_plug_dest_con +## testing what an ordinary user can grant +this should fail : no rights to grant all +GRANT PROXY ON ''@'' TO grant_plug; +ERROR 28000: Access denied for user 'grant_plug_dest'@'localhost' +this should fail : not the same user +GRANT PROXY ON grant_plug TO grant_plug_dest; +ERROR 28000: Access denied for user 'grant_plug_dest'@'localhost' +this should fail : same user, but on a different host +GRANT PROXY ON grant_plug_dest TO grant_plug; +ERROR 28000: Access denied for user 'grant_plug_dest'@'localhost' +this should work : same user +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug_dest2; +REVOKE PROXY ON grant_plug_dest@localhost FROM grant_plug_dest2; +this should work : same user +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug WITH GRANT OPTION; +REVOKE PROXY ON grant_plug_dest@localhost FROM grant_plug; +this should fail : can't create users +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug@localhost; +ERROR 42000: You are not allowed to create a user with GRANT +in default connection +# test what root can grant +should work : root has PROXY to all users +GRANT PROXY ON ''@'' TO grant_plug; +REVOKE PROXY ON ''@'' FROM grant_plug; +should work : root has PROXY to all users +GRANT PROXY ON ''@'' TO proxy_admin IDENTIFIED BY 'test' +WITH GRANT OPTION; +need USAGE : PROXY doesn't contain it. +GRANT USAGE on *.* TO proxy_admin; +in proxy_admin_con; +should work : proxy_admin has proxy to ''@'' +GRANT PROXY ON future_user TO grant_plug; +in default connection +SHOW GRANTS FOR grant_plug; +Grants for grant_plug@% +GRANT ALL PRIVILEGES ON *.* TO 'grant_plug'@'%' WITH GRANT OPTION +GRANT PROXY ON 'future_user'@'%' TO 'grant_plug'@'%' +REVOKE PROXY ON future_user FROM grant_plug; +SHOW GRANTS FOR grant_plug; +Grants for grant_plug@% +GRANT ALL PRIVILEGES ON *.* TO 'grant_plug'@'%' WITH GRANT OPTION +## testing drop user +CREATE USER test_drop@localhost; +GRANT PROXY ON future_user TO test_drop@localhost; +SHOW GRANTS FOR test_drop@localhost; +Grants for test_drop@localhost +GRANT USAGE ON *.* TO 'test_drop'@'localhost' +GRANT PROXY ON 'future_user'@'%' TO 'test_drop'@'localhost' +DROP USER test_drop@localhost; +SELECT * FROM mysql.proxy_priv WHERE Host = 'test_drop' AND User = 'localhost'; +Host User Proxied_Host Proxied_User With_Grant +DROP USER proxy_admin; +DROP USER grant_plug,grant_plug_dest,grant_plug_dest2; +## END GRANT PROXY tests +## cleanup +DROP USER plug; +DROP USER plug_dest; +## @@proxy_user tests +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; +GRANT PROXY ON plug_dest TO plug; +SELECT USER(),CURRENT_USER(),@@LOCAL.proxy_user; +USER() CURRENT_USER() @@LOCAL.proxy_user +root@localhost root@localhost NULL +SELECT @@GLOBAL.proxy_user; +ERROR HY000: Variable 'proxy_user' is a SESSION variable +SELECT @@LOCAL.proxy_user; +@@LOCAL.proxy_user +NULL +SET GLOBAL proxy_user = 'test'; +ERROR HY000: Variable 'proxy_user' is a read only variable +SET LOCAL proxy_user = 'test'; +ERROR HY000: Variable 'proxy_user' is a read only variable +SELECT @@LOCAL.proxy_user; +@@LOCAL.proxy_user +NULL +# in connection plug_con +SELECT @@LOCAL.proxy_user; +@@LOCAL.proxy_user +'plug'@'%' +# in connection default +## cleanup +DROP USER plug; +DROP USER plug_dest; +## END @@proxy_user tests +## @@external_user tests +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; +GRANT PROXY ON plug_dest TO plug; +SELECT USER(),CURRENT_USER(),@@LOCAL.external_user; +USER() CURRENT_USER() @@LOCAL.external_user +root@localhost root@localhost NULL +SELECT @@GLOBAL.external_user; +ERROR HY000: Variable 'external_user' is a SESSION variable +SELECT @@LOCAL.external_user; +@@LOCAL.external_user +NULL +SET GLOBAL external_user = 'test'; +ERROR HY000: Variable 'external_user' is a read only variable +SET LOCAL external_user = 'test'; +ERROR HY000: Variable 'external_user' is a read only variable +SELECT @@LOCAL.external_user; +@@LOCAL.external_user +NULL +# in connection plug_con +SELECT @@LOCAL.external_user; +@@LOCAL.external_user +'plug'@'%' +# in connection default +## cleanup +DROP USER plug; +DROP USER plug_dest; +## END @@external_user tests diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 0e75ebd57ec..cf3594f32a8 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -1194,13 +1194,13 @@ SET @aux= "SELECT COUNT(*) prepare my_stmt from @aux; execute my_stmt; COUNT(*) -40 +42 execute my_stmt; COUNT(*) -40 +42 execute my_stmt; COUNT(*) -40 +42 deallocate prepare my_stmt; drop procedure if exists p1| drop table if exists t1| diff --git a/mysql-test/r/sp_notembedded.result b/mysql-test/r/sp_notembedded.result index e4170cc3a49..7da95416e29 100644 --- a/mysql-test/r/sp_notembedded.result +++ b/mysql-test/r/sp_notembedded.result @@ -9,9 +9,11 @@ end| call bug4902()| Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION call bug4902()| Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop procedure bug4902| drop procedure if exists bug4902_2| create procedure bug4902_2() @@ -206,9 +208,11 @@ create procedure 15298_2 () sql security definer show grants; call 15298_1(); Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION call 15298_2(); Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop user mysqltest_1@localhost; drop procedure 15298_1; drop procedure 15298_2; @@ -245,6 +249,8 @@ max_updates, max_connections, max_user_connections) VALUES('%', 'mysqltest_1', password(''), 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'N', 'N', 'N', 'N', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'N', 'N', 'N', 'N', 'N', 'Y', 'Y', 'N', '', '', '', '', '0', '0', '0', '0'); +Warnings: +Warning 1364 Field 'authentication_string' doesn't have a default value FLUSH PRIVILEGES; CREATE PROCEDURE p1(i INT) BEGIN END; DROP PROCEDURE p1; diff --git a/mysql-test/r/system_mysql_db.result b/mysql-test/r/system_mysql_db.result index 679e50fcad9..e82cf229912 100644 --- a/mysql-test/r/system_mysql_db.result +++ b/mysql-test/r/system_mysql_db.result @@ -14,6 +14,7 @@ ndb_binlog_index plugin proc procs_priv +proxy_priv servers slow_log tables_priv @@ -119,6 +120,8 @@ user CREATE TABLE `user` ( `max_updates` int(11) unsigned NOT NULL DEFAULT '0', `max_connections` int(11) unsigned NOT NULL DEFAULT '0', `max_user_connections` int(11) unsigned NOT NULL DEFAULT '0', + `plugin` char(60) COLLATE utf8_bin NOT NULL DEFAULT '', + `authentication_string` text COLLATE utf8_bin NOT NULL, PRIMARY KEY (`Host`,`User`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges' show create table func; diff --git a/mysql-test/suite/federated/federated.result b/mysql-test/suite/federated/federated.result index db4ffc38213..4cd7b416621 100644 --- a/mysql-test/suite/federated/federated.result +++ b/mysql-test/suite/federated/federated.result @@ -60,7 +60,7 @@ CREATE TABLE federated.t1 ( ENGINE="FEDERATED" DEFAULT CHARSET=latin1 CONNECTION='mysql://user:pass@127.0.0.1:SLAVE_PORT/federated/t1'; SELECT * FROM federated.t1; -ERROR HY000: Unable to connect to foreign data source: Access denied for user 'user'@'localhost' (using password: YES) +ERROR HY000: Unable to connect to foreign data source: Access denied for user 'user'@'localhost' (using password: NO) DROP TABLE federated.t1; CREATE TABLE federated.t1 ( `id` int(20) NOT NULL, diff --git a/mysql-test/suite/funcs_1/r/innodb_trig_03e.result b/mysql-test/suite/funcs_1/r/innodb_trig_03e.result index bc3f7daf5bc..3c77ca3dedc 100644 --- a/mysql-test/suite/funcs_1/r/innodb_trig_03e.result +++ b/mysql-test/suite/funcs_1/r/innodb_trig_03e.result @@ -580,6 +580,7 @@ root@localhost show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop trigger trg1_1; use priv_db; diff --git a/mysql-test/suite/funcs_1/r/is_columns_mysql.result b/mysql-test/suite/funcs_1/r/is_columns_mysql.result index 1ab2b3513f0..767f9e47f13 100644 --- a/mysql-test/suite/funcs_1/r/is_columns_mysql.result +++ b/mysql-test/suite/funcs_1/r/is_columns_mysql.result @@ -134,6 +134,11 @@ def mysql procs_priv Routine_name 4 NO char 64 192 NULL NULL utf8 utf8_general_ def mysql procs_priv Routine_type 5 NULL NO enum 9 27 NULL NULL utf8 utf8_bin enum('FUNCTION','PROCEDURE') PRI select,insert,update,references def mysql procs_priv Timestamp 8 CURRENT_TIMESTAMP NO timestamp NULL NULL NULL NULL NULL NULL timestamp on update CURRENT_TIMESTAMP select,insert,update,references def mysql procs_priv User 3 NO char 16 48 NULL NULL utf8 utf8_bin char(16) PRI select,insert,update,references +def mysql proxy_priv Host 1 NO char 60 180 NULL NULL utf8 utf8_bin char(60) PRI select,insert,update,references +def mysql proxy_priv Proxied_Host 3 NO char 16 48 NULL NULL utf8 utf8_bin char(16) PRI select,insert,update,references +def mysql proxy_priv Proxied_User 4 NO char 60 180 NULL NULL utf8 utf8_bin char(60) PRI select,insert,update,references +def mysql proxy_priv User 2 NO char 16 48 NULL NULL utf8 utf8_bin char(16) PRI select,insert,update,references +def mysql proxy_priv With_Grant 5 0 NO tinyint NULL NULL 3 0 NULL NULL tinyint(1) select,insert,update,references def mysql servers Db 3 NO char 64 192 NULL NULL utf8 utf8_general_ci char(64) select,insert,update,references def mysql servers Host 2 NO char 64 192 NULL NULL utf8 utf8_general_ci char(64) select,insert,update,references def mysql servers Owner 9 NO char 64 192 NULL NULL utf8 utf8_general_ci char(64) select,insert,update,references @@ -178,6 +183,7 @@ def mysql time_zone_transition_type Time_zone_id 1 NULL NO int NULL NULL 10 0 NU def mysql time_zone_transition_type Transition_type_id 2 NULL NO int NULL NULL 10 0 NULL NULL int(10) unsigned PRI select,insert,update,references def mysql user Alter_priv 17 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references def mysql user Alter_routine_priv 28 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references +def mysql user authentication_string 42 NULL NO text 65535 65535 NULL NULL utf8 utf8_bin text select,insert,update,references def mysql user Create_priv 8 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references def mysql user Create_routine_priv 27 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references def mysql user Create_tablespace_priv 32 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references @@ -199,6 +205,7 @@ def mysql user max_questions 37 0 NO int NULL NULL 10 0 NULL NULL int(11) unsign def mysql user max_updates 38 0 NO int NULL NULL 10 0 NULL NULL int(11) unsigned select,insert,update,references def mysql user max_user_connections 40 0 NO int NULL NULL 10 0 NULL NULL int(11) unsigned select,insert,update,references def mysql user Password 3 NO char 41 41 NULL NULL latin1 latin1_bin char(41) select,insert,update,references +def mysql user plugin 41 NO char 60 180 NULL NULL utf8 utf8_bin char(60) select,insert,update,references def mysql user Process_priv 12 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references def mysql user References_priv 15 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references def mysql user Reload_priv 10 N NO enum 1 3 NULL NULL utf8 utf8_general_ci enum('N','Y') select,insert,update,references @@ -418,6 +425,11 @@ NULL mysql proc modified timestamp NULL NULL NULL NULL timestamp 3.0000 mysql procs_priv Grantor char 77 231 utf8 utf8_bin char(77) 3.0000 mysql procs_priv Proc_priv set 27 81 utf8 utf8_general_ci set('Execute','Alter Routine','Grant') NULL mysql procs_priv Timestamp timestamp NULL NULL NULL NULL timestamp +3.0000 mysql proxy_priv Host char 60 180 utf8 utf8_bin char(60) +3.0000 mysql proxy_priv User char 16 48 utf8 utf8_bin char(16) +3.0000 mysql proxy_priv Proxied_Host char 16 48 utf8 utf8_bin char(16) +3.0000 mysql proxy_priv Proxied_User char 60 180 utf8 utf8_bin char(60) +NULL mysql proxy_priv With_Grant tinyint NULL NULL NULL NULL tinyint(1) 3.0000 mysql servers Server_name char 64 192 utf8 utf8_general_ci char(64) 3.0000 mysql servers Host char 64 192 utf8 utf8_general_ci char(64) 3.0000 mysql servers Db char 64 192 utf8 utf8_general_ci char(64) @@ -500,3 +512,5 @@ NULL mysql user max_questions int NULL NULL NULL NULL int(11) unsigned NULL mysql user max_updates int NULL NULL NULL NULL int(11) unsigned NULL mysql user max_connections int NULL NULL NULL NULL int(11) unsigned NULL mysql user max_user_connections int NULL NULL NULL NULL int(11) unsigned +3.0000 mysql user plugin char 60 180 utf8 utf8_bin char(60) +1.0000 mysql user authentication_string text 65535 65535 utf8 utf8_bin text diff --git a/mysql-test/suite/funcs_1/r/is_key_column_usage.result b/mysql-test/suite/funcs_1/r/is_key_column_usage.result index a81452b7927..2e50a0c36bf 100644 --- a/mysql-test/suite/funcs_1/r/is_key_column_usage.result +++ b/mysql-test/suite/funcs_1/r/is_key_column_usage.result @@ -106,6 +106,10 @@ def mysql PRIMARY def mysql procs_priv Db def mysql PRIMARY def mysql procs_priv User def mysql PRIMARY def mysql procs_priv Routine_name def mysql PRIMARY def mysql procs_priv Routine_type +def mysql PRIMARY def mysql proxy_priv Host +def mysql PRIMARY def mysql proxy_priv User +def mysql PRIMARY def mysql proxy_priv Proxied_Host +def mysql PRIMARY def mysql proxy_priv Proxied_User def mysql PRIMARY def mysql servers Server_name def mysql PRIMARY def mysql tables_priv Host def mysql PRIMARY def mysql tables_priv Db diff --git a/mysql-test/suite/funcs_1/r/is_statistics.result b/mysql-test/suite/funcs_1/r/is_statistics.result index 873b328dc2d..0c43883789b 100644 --- a/mysql-test/suite/funcs_1/r/is_statistics.result +++ b/mysql-test/suite/funcs_1/r/is_statistics.result @@ -118,6 +118,10 @@ def mysql procs_priv mysql PRIMARY def mysql procs_priv mysql PRIMARY def mysql procs_priv mysql PRIMARY def mysql procs_priv mysql Grantor +def mysql proxy_priv mysql PRIMARY +def mysql proxy_priv mysql PRIMARY +def mysql proxy_priv mysql PRIMARY +def mysql proxy_priv mysql PRIMARY def mysql servers mysql PRIMARY def mysql tables_priv mysql PRIMARY def mysql tables_priv mysql PRIMARY diff --git a/mysql-test/suite/funcs_1/r/is_statistics_mysql.result b/mysql-test/suite/funcs_1/r/is_statistics_mysql.result index a1fa0cac9f2..584bbeb7af5 100644 --- a/mysql-test/suite/funcs_1/r/is_statistics_mysql.result +++ b/mysql-test/suite/funcs_1/r/is_statistics_mysql.result @@ -40,6 +40,10 @@ def mysql procs_priv 0 mysql PRIMARY 2 Db A #CARD# NULL NULL BTREE def mysql procs_priv 0 mysql PRIMARY 3 User A #CARD# NULL NULL BTREE def mysql procs_priv 0 mysql PRIMARY 4 Routine_name A #CARD# NULL NULL BTREE def mysql procs_priv 0 mysql PRIMARY 5 Routine_type A #CARD# NULL NULL BTREE +def mysql proxy_priv 0 mysql PRIMARY 1 Host A #CARD# NULL NULL BTREE +def mysql proxy_priv 0 mysql PRIMARY 2 User A #CARD# NULL NULL BTREE +def mysql proxy_priv 0 mysql PRIMARY 3 Proxied_Host A #CARD# NULL NULL BTREE +def mysql proxy_priv 0 mysql PRIMARY 4 Proxied_User A #CARD# NULL NULL BTREE def mysql servers 0 mysql PRIMARY 1 Server_name A #CARD# NULL NULL BTREE def mysql tables_priv 1 mysql Grantor 1 Grantor A #CARD# NULL NULL BTREE def mysql tables_priv 0 mysql PRIMARY 1 Host A #CARD# NULL NULL BTREE diff --git a/mysql-test/suite/funcs_1/r/is_table_constraints.result b/mysql-test/suite/funcs_1/r/is_table_constraints.result index 7f1c83a8ea5..d4d2c38c9ba 100644 --- a/mysql-test/suite/funcs_1/r/is_table_constraints.result +++ b/mysql-test/suite/funcs_1/r/is_table_constraints.result @@ -73,6 +73,7 @@ def mysql PRIMARY mysql ndb_binlog_index def mysql PRIMARY mysql plugin def mysql PRIMARY mysql proc def mysql PRIMARY mysql procs_priv +def mysql PRIMARY mysql proxy_priv def mysql PRIMARY mysql servers def mysql PRIMARY mysql tables_priv def mysql PRIMARY mysql time_zone diff --git a/mysql-test/suite/funcs_1/r/is_table_constraints_mysql.result b/mysql-test/suite/funcs_1/r/is_table_constraints_mysql.result index 8b7ac6994c1..38e9c9034c9 100644 --- a/mysql-test/suite/funcs_1/r/is_table_constraints_mysql.result +++ b/mysql-test/suite/funcs_1/r/is_table_constraints_mysql.result @@ -23,6 +23,7 @@ def mysql PRIMARY mysql ndb_binlog_index PRIMARY KEY def mysql PRIMARY mysql plugin PRIMARY KEY def mysql PRIMARY mysql proc PRIMARY KEY def mysql PRIMARY mysql procs_priv PRIMARY KEY +def mysql PRIMARY mysql proxy_priv PRIMARY KEY def mysql PRIMARY mysql servers PRIMARY KEY def mysql PRIMARY mysql tables_priv PRIMARY KEY def mysql PRIMARY mysql time_zone PRIMARY KEY diff --git a/mysql-test/suite/funcs_1/r/is_tables_mysql.result b/mysql-test/suite/funcs_1/r/is_tables_mysql.result index 0945401ba43..ae512327807 100644 --- a/mysql-test/suite/funcs_1/r/is_tables_mysql.result +++ b/mysql-test/suite/funcs_1/r/is_tables_mysql.result @@ -336,6 +336,29 @@ user_comment Procedure privileges Separator ----------------------------------------------------- TABLE_CATALOG def TABLE_SCHEMA mysql +TABLE_NAME proxy_priv +TABLE_TYPE BASE TABLE +ENGINE MyISAM +VERSION 10 +ROW_FORMAT Fixed +TABLE_ROWS #TBLR# +AVG_ROW_LENGTH #ARL# +DATA_LENGTH #DL# +MAX_DATA_LENGTH #MDL# +INDEX_LENGTH #IL# +DATA_FREE #DF# +AUTO_INCREMENT NULL +CREATE_TIME #CRT# +UPDATE_TIME #UT# +CHECK_TIME #CT# +TABLE_COLLATION utf8_bin +CHECKSUM NULL +CREATE_OPTIONS #CO# +TABLE_COMMENT #TC# +user_comment User proxy privileges +Separator ----------------------------------------------------- +TABLE_CATALOG def +TABLE_SCHEMA mysql TABLE_NAME servers TABLE_TYPE BASE TABLE ENGINE MyISAM diff --git a/mysql-test/suite/funcs_1/r/is_user_privileges.result b/mysql-test/suite/funcs_1/r/is_user_privileges.result index 8f68f8c802d..1ec1ffc4ce1 100644 --- a/mysql-test/suite/funcs_1/r/is_user_privileges.result +++ b/mysql-test/suite/funcs_1/r/is_user_privileges.result @@ -69,46 +69,436 @@ GRANT UPDATE ON *.* TO 'testuser2'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # # Add GRANT OPTION db_datadict.* to testuser1; GRANT UPDATE ON db_datadict.* TO 'testuser1'@'localhost' WITH GRANT OPTION; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # Establish connection testuser1 (user=testuser1) SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string SHOW GRANTS; Grants for testuser1@localhost GRANT USAGE ON *.* TO 'testuser1'@'localhost' @@ -123,46 +513,436 @@ GRANT SELECT ON *.* TO 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def SELECT NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE SELECT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 Y N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv Y +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string GRANT SELECT ON *.* TO 'testuser1'@'localhost' WITH GRANT OPTION; # # Here <SELECT YES> is shown correctly for testuser1; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def SELECT YES -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE SELECT +IS_GRANTABLE YES +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 Y N N N N N N N N N Y N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv Y +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv Y +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # Switch to connection testuser1 SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def SELECT YES +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE SELECT +IS_GRANTABLE YES SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 Y N N N N N N N N N Y N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv Y +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv Y +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string SHOW GRANTS; Grants for testuser1@localhost GRANT SELECT ON *.* TO 'testuser1'@'localhost' WITH GRANT OPTION @@ -172,9 +952,14 @@ GRANT SELECT ON `mysql`.`user` TO 'testuser1'@'localhost' SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; ERROR 42000: SELECT command denied to user 'testuser2'@'localhost' for table 'user' @@ -185,8 +970,10 @@ GRANT INSERT, UPDATE ON *.* TO 'testuser2'@'localhost' SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; ERROR 42000: SELECT command denied to user 'testuser3'@'localhost' for table 'user' @@ -200,23 +987,158 @@ REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # Switch to connection testuser1 SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; ERROR 42000: SELECT command denied to user 'testuser1'@'localhost' for table 'user' @@ -228,8 +1150,10 @@ ERROR 42000: CREATE command denied to user 'testuser1'@'localhost' for table 'tb SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; ERROR 42000: SELECT command denied to user 'testuser1'@'localhost' for table 'user' @@ -246,29 +1170,286 @@ GRANT SELECT ON mysql.user TO 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # Switch to connection testuser1 SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string SHOW GRANTS; Grants for testuser1@localhost GRANT USAGE ON *.* TO 'testuser1'@'localhost' @@ -280,14 +1461,138 @@ USE db_datadict; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string SHOW GRANTS; Grants for testuser1@localhost GRANT USAGE ON *.* TO 'testuser1'@'localhost' @@ -302,23 +1607,158 @@ REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO -'testuser2'@'localhost' def INSERT NO -'testuser2'@'localhost' def UPDATE NO -'testuser3'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE INSERT +IS_GRANTABLE NO +GRANTEE 'testuser2'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE UPDATE +IS_GRANTABLE NO +GRANTEE 'testuser3'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; -Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv Create_routine_priv Alter_routine_priv Create_user_priv Event_priv Trigger_priv Create_tablespace_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections -localhost testuser1 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser2 N Y Y N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 -localhost testuser3 N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 0 0 0 0 +Host localhost +User testuser1 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser2 +Password +Select_priv N +Insert_priv Y +Update_priv Y +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string +Host localhost +User testuser3 +Password +Select_priv N +Insert_priv N +Update_priv N +Delete_priv N +Create_priv N +Drop_priv N +Reload_priv N +Shutdown_priv N +Process_priv N +File_priv N +Grant_priv N +References_priv N +Index_priv N +Alter_priv N +Show_db_priv N +Super_priv N +Create_tmp_table_priv N +Lock_tables_priv N +Execute_priv N +Repl_slave_priv N +Repl_client_priv N +Create_view_priv N +Show_view_priv N +Create_routine_priv N +Alter_routine_priv N +Create_user_priv N +Event_priv N +Trigger_priv N +Create_tablespace_priv N +ssl_type +ssl_cipher +x509_issuer +x509_subject +max_questions 0 +max_updates 0 +max_connections 0 +max_user_connections 0 +plugin +authentication_string # Switch to connection testuser1 SELECT * FROM information_schema.user_privileges WHERE grantee LIKE '''testuser%''' ORDER BY grantee, table_catalog, privilege_type; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; ERROR 42000: SELECT command denied to user 'testuser1'@'localhost' for table 'user' @@ -341,31 +1781,36 @@ DROP DATABASE IF EXISTS db_datadict; ######################################################################################## SELECT * FROM information_schema.user_privileges WHERE grantee = '''testuser1''@''localhost'''; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE SHOW GRANTS FOR 'testuser1'@'localhost'; ERROR 42000: There is no such grant defined for user 'testuser1' on host 'localhost' DROP USER 'testuser1'@'localhost'; CREATE USER 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee = '''testuser1''@''localhost'''; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def USAGE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE USAGE +IS_GRANTABLE NO SHOW GRANTS FOR 'testuser1'@'localhost'; Grants for testuser1@localhost GRANT USAGE ON *.* TO 'testuser1'@'localhost' GRANT SELECT, FILE ON *.* TO 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee = '''testuser1''@''localhost'''; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE -'testuser1'@'localhost' def SELECT NO -'testuser1'@'localhost' def FILE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE SELECT +IS_GRANTABLE NO +GRANTEE 'testuser1'@'localhost' +TABLE_CATALOG def +PRIVILEGE_TYPE FILE +IS_GRANTABLE NO SHOW GRANTS FOR 'testuser1'@'localhost'; Grants for testuser1@localhost GRANT SELECT, FILE ON *.* TO 'testuser1'@'localhost' DROP USER 'testuser1'@'localhost'; SELECT * FROM information_schema.user_privileges WHERE grantee = '''testuser1''@''localhost'''; -GRANTEE TABLE_CATALOG PRIVILEGE_TYPE IS_GRANTABLE SHOW GRANTS FOR 'testuser1'@'localhost'; ERROR 42000: There is no such grant defined for user 'testuser1' on host 'localhost' ######################################################################## diff --git a/mysql-test/suite/funcs_1/r/memory_trig_03e.result b/mysql-test/suite/funcs_1/r/memory_trig_03e.result index cfbee5f27b7..e8261c93238 100644 --- a/mysql-test/suite/funcs_1/r/memory_trig_03e.result +++ b/mysql-test/suite/funcs_1/r/memory_trig_03e.result @@ -581,6 +581,7 @@ root@localhost show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop trigger trg1_1; use priv_db; diff --git a/mysql-test/suite/funcs_1/r/myisam_trig_03e.result b/mysql-test/suite/funcs_1/r/myisam_trig_03e.result index d21bd176810..8f9b5e6c174 100644 --- a/mysql-test/suite/funcs_1/r/myisam_trig_03e.result +++ b/mysql-test/suite/funcs_1/r/myisam_trig_03e.result @@ -581,6 +581,7 @@ root@localhost show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION drop trigger trg1_1; use priv_db; diff --git a/mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result b/mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result index ed9e9cce054..ce22b9ca8e0 100644 --- a/mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result +++ b/mysql-test/suite/funcs_1/r/processlist_priv_no_prot.result @@ -65,6 +65,7 @@ ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_ SHOW GRANTS; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION CREATE INDEX i_processlist ON processlist (user); ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' DROP TABLE processlist; diff --git a/mysql-test/suite/funcs_1/r/processlist_priv_ps.result b/mysql-test/suite/funcs_1/r/processlist_priv_ps.result index db1b385513c..891071269f9 100644 --- a/mysql-test/suite/funcs_1/r/processlist_priv_ps.result +++ b/mysql-test/suite/funcs_1/r/processlist_priv_ps.result @@ -65,6 +65,7 @@ ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_ SHOW GRANTS; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION CREATE INDEX i_processlist ON processlist (user); ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' DROP TABLE processlist; diff --git a/mysql-test/suite/funcs_1/t/is_user_privileges.test b/mysql-test/suite/funcs_1/t/is_user_privileges.test index 253323af9a7..0f0e398d75b 100644 --- a/mysql-test/suite/funcs_1/t/is_user_privileges.test +++ b/mysql-test/suite/funcs_1/t/is_user_privileges.test @@ -104,20 +104,26 @@ ORDER BY grantee, table_catalog, privilege_type; let $my_select2= SELECT * FROM mysql.user WHERE user LIKE 'testuser%' ORDER BY host, user; let $my_show= SHOW GRANTS; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # --echo # Add GRANT OPTION db_datadict.* to testuser1; GRANT UPDATE ON db_datadict.* TO 'testuser1'@'localhost' WITH GRANT OPTION; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # Establish connection testuser1 (user=testuser1) --replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK connect (testuser1, localhost, testuser1, , db_datadict); +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results eval $my_show; --echo @@ -128,36 +134,46 @@ connection default; GRANT SELECT ON *.* TO 'testuser1'@'localhost'; --echo # --echo # Here <SELECT NO> is shown correctly for testuser1; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results GRANT SELECT ON *.* TO 'testuser1'@'localhost' WITH GRANT OPTION; --echo # --echo # Here <SELECT YES> is shown correctly for testuser1; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # Switch to connection testuser1 # check that this appears connection testuser1; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results eval $my_show; --echo # Establish connection testuser2 (user=testuser2) --replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK connect (testuser2, localhost, testuser2, , db_datadict); +--vertical_results eval $my_select1; --error ER_TABLEACCESS_DENIED_ERROR eval $my_select2; +--horizontal_results eval $my_show; --echo # Establish connection testuser3 (user=testuser3) --replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK connect (testuser3, localhost, testuser3, , test); +--vertical_results eval $my_select1; --error ER_TABLEACCESS_DENIED_ERROR eval $my_select2; +--horizontal_results eval $my_show; --echo @@ -165,23 +181,29 @@ eval $my_show; --echo # Switch to connection default connection default; REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'testuser1'@'localhost'; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # Switch to connection testuser1 # check for changes connection testuser1; +--vertical_results eval $my_select1; --error ER_TABLEACCESS_DENIED_ERROR eval $my_select2; +--horizontal_results eval $my_show; # OK, testuser1 has no privs here --error ER_TABLEACCESS_DENIED_ERROR CREATE TABLE db_datadict.tb_55 ( c1 TEXT ); +--vertical_results eval $my_select1; --error ER_TABLEACCESS_DENIED_ERROR eval $my_select2; +--horizontal_results eval $my_show; # OK, testuser1 has no privs here --error ER_TABLEACCESS_DENIED_ERROR @@ -193,13 +215,17 @@ CREATE TABLE db_datadict.tb_66 ( c1 TEXT ); connection default; GRANT ALL ON db_datadict.* TO 'testuser1'@'localhost' WITH GRANT OPTION; GRANT SELECT ON mysql.user TO 'testuser1'@'localhost'; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # Switch to connection testuser1 connection testuser1; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results eval $my_show; # OK, testuser1 has no privs here @@ -208,8 +234,10 @@ CREATE TABLE db_datadict.tb_56 ( c1 TEXT ); # using 'USE' lets the server read the privileges new, so now the CREATE works USE db_datadict; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results eval $my_show; --replace_result $other_engine_type <other_engine_type> eval @@ -221,15 +249,19 @@ ENGINE = $other_engine_type; --echo # Switch to connection default connection default; REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'testuser1'@'localhost'; +--vertical_results eval $my_select1; eval $my_select2; +--horizontal_results --echo # Switch to connection testuser1 # check for changes connection testuser1; +--vertical_results eval $my_select1; --error ER_TABLEACCESS_DENIED_ERROR eval $my_select2; +--horizontal_results eval $my_show; # WORKS, as the existing old privileges are used! --replace_result $other_engine_type <other_engine_type> @@ -273,19 +305,27 @@ DROP DATABASE IF EXISTS db_datadict; let $my_select = SELECT * FROM information_schema.user_privileges WHERE grantee = '''testuser1''@''localhost'''; let $my_show = SHOW GRANTS FOR 'testuser1'@'localhost'; +--vertical_results eval $my_select; +--horizontal_results --error ER_NONEXISTING_GRANT eval $my_show; --error 0,ER_CANNOT_USER DROP USER 'testuser1'@'localhost'; CREATE USER 'testuser1'@'localhost'; +--vertical_results eval $my_select; +--horizontal_results eval $my_show; GRANT SELECT, FILE ON *.* TO 'testuser1'@'localhost'; +--vertical_results eval $my_select; +--horizontal_results eval $my_show; DROP USER 'testuser1'@'localhost'; +--vertical_results eval $my_select; +--horizontal_results --error ER_NONEXISTING_GRANT eval $my_show; diff --git a/mysql-test/suite/perfschema/r/column_privilege.result b/mysql-test/suite/perfschema/r/column_privilege.result index 7bbc59ac452..ac6030690dd 100644 --- a/mysql-test/suite/perfschema/r/column_privilege.result +++ b/mysql-test/suite/perfschema/r/column_privilege.result @@ -1,6 +1,7 @@ show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION grant usage on *.* to 'pfs_user_5'@localhost with GRANT OPTION; grant SELECT(thread_id, event_id) on performance_schema.EVENTS_WAITS_CURRENT to 'pfs_user_5'@localhost; diff --git a/mysql-test/suite/perfschema/r/pfs_upgrade.result b/mysql-test/suite/perfschema/r/pfs_upgrade.result index 2d0d82ef19c..9e2018f5c11 100644 --- a/mysql-test/suite/perfschema/r/pfs_upgrade.result +++ b/mysql-test/suite/perfschema/r/pfs_upgrade.result @@ -26,7 +26,7 @@ ERROR 1050 (42S01) at line 445: Table 'SETUP_CONSUMERS' already exists ERROR 1050 (42S01) at line 462: Table 'SETUP_INSTRUMENTS' already exists ERROR 1050 (42S01) at line 482: Table 'SETUP_OBJECTS' already exists ERROR 1050 (42S01) at line 498: Table 'SETUP_TIMERS' already exists -ERROR 1644 (HY000) at line 1138: Unexpected content found in the performance_schema database. +ERROR 1644 (HY000) at line 1142: Unexpected content found in the performance_schema database. FATAL ERROR: Upgrade failed show tables like "user_table"; Tables_in_performance_schema (user_table) @@ -57,7 +57,7 @@ ERROR 1050 (42S01) at line 445: Table 'SETUP_CONSUMERS' already exists ERROR 1050 (42S01) at line 462: Table 'SETUP_INSTRUMENTS' already exists ERROR 1050 (42S01) at line 482: Table 'SETUP_OBJECTS' already exists ERROR 1050 (42S01) at line 498: Table 'SETUP_TIMERS' already exists -ERROR 1644 (HY000) at line 1138: Unexpected content found in the performance_schema database. +ERROR 1644 (HY000) at line 1142: Unexpected content found in the performance_schema database. FATAL ERROR: Upgrade failed show tables like "user_view"; Tables_in_performance_schema (user_view) @@ -86,7 +86,7 @@ ERROR 1050 (42S01) at line 445: Table 'SETUP_CONSUMERS' already exists ERROR 1050 (42S01) at line 462: Table 'SETUP_INSTRUMENTS' already exists ERROR 1050 (42S01) at line 482: Table 'SETUP_OBJECTS' already exists ERROR 1050 (42S01) at line 498: Table 'SETUP_TIMERS' already exists -ERROR 1644 (HY000) at line 1138: Unexpected content found in the performance_schema database. +ERROR 1644 (HY000) at line 1142: Unexpected content found in the performance_schema database. FATAL ERROR: Upgrade failed select name from mysql.proc where db='performance_schema'; name @@ -115,7 +115,7 @@ ERROR 1050 (42S01) at line 445: Table 'SETUP_CONSUMERS' already exists ERROR 1050 (42S01) at line 462: Table 'SETUP_INSTRUMENTS' already exists ERROR 1050 (42S01) at line 482: Table 'SETUP_OBJECTS' already exists ERROR 1050 (42S01) at line 498: Table 'SETUP_TIMERS' already exists -ERROR 1644 (HY000) at line 1138: Unexpected content found in the performance_schema database. +ERROR 1644 (HY000) at line 1142: Unexpected content found in the performance_schema database. FATAL ERROR: Upgrade failed select name from mysql.proc where db='performance_schema'; name @@ -144,7 +144,7 @@ ERROR 1050 (42S01) at line 445: Table 'SETUP_CONSUMERS' already exists ERROR 1050 (42S01) at line 462: Table 'SETUP_INSTRUMENTS' already exists ERROR 1050 (42S01) at line 482: Table 'SETUP_OBJECTS' already exists ERROR 1050 (42S01) at line 498: Table 'SETUP_TIMERS' already exists -ERROR 1644 (HY000) at line 1138: Unexpected content found in the performance_schema database. +ERROR 1644 (HY000) at line 1142: Unexpected content found in the performance_schema database. FATAL ERROR: Upgrade failed select name from mysql.event where db='performance_schema'; name diff --git a/mysql-test/suite/perfschema/r/privilege.result b/mysql-test/suite/perfschema/r/privilege.result index ddbc150a72a..4283b250cee 100644 --- a/mysql-test/suite/perfschema/r/privilege.result +++ b/mysql-test/suite/perfschema/r/privilege.result @@ -1,6 +1,7 @@ show grants; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION grant ALL on *.* to 'pfs_user_1'@localhost with GRANT OPTION; grant ALL on performance_schema.* to 'pfs_user_2'@localhost with GRANT OPTION; diff --git a/mysql-test/suite/rpl/r/rpl_do_grant.result b/mysql-test/suite/rpl/r/rpl_do_grant.result index 1cea2cfa9ad..ce0417cce07 100644 --- a/mysql-test/suite/rpl/r/rpl_do_grant.result +++ b/mysql-test/suite/rpl/r/rpl_do_grant.result @@ -207,6 +207,7 @@ GRANT EXECUTE ON PROCEDURE `test`.`p1` TO 'user49119'@'localhost' SHOW GRANTS FOR CURRENT_USER; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION ############################################################## ############################################################## ### Showing grants for both users: root and user49119 (master) @@ -217,6 +218,7 @@ GRANT EXECUTE ON PROCEDURE `test`.`p1` TO 'user49119'@'localhost' SHOW GRANTS FOR CURRENT_USER; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION ############################################################## ## This statement will make the revoke fail because root has no ## execute grant. However, it will still revoke the grant for @@ -232,6 +234,7 @@ GRANT USAGE ON *.* TO 'user49119'@'localhost' SHOW GRANTS FOR CURRENT_USER; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION ############################################################## ############################################################# ### Showing grants for both users: root and user49119 (slave) @@ -242,6 +245,7 @@ GRANT USAGE ON *.* TO 'user49119'@'localhost' SHOW GRANTS FOR CURRENT_USER; Grants for root@localhost GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION +GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION ############################################################## DROP TABLE t1; DROP PROCEDURE p1; diff --git a/mysql-test/suite/rpl/r/rpl_ignore_table.result b/mysql-test/suite/rpl/r/rpl_ignore_table.result index e77be425270..a6c59918b93 100644 --- a/mysql-test/suite/rpl/r/rpl_ignore_table.result +++ b/mysql-test/suite/rpl/r/rpl_ignore_table.result @@ -34,6 +34,7 @@ Warnings: Warning 1364 Field 'ssl_cipher' doesn't have a default value Warning 1364 Field 'x509_issuer' doesn't have a default value Warning 1364 Field 'x509_subject' doesn't have a default value +Warning 1364 Field 'authentication_string' doesn't have a default value GRANT SELECT ON *.* TO mysqltest6@localhost; GRANT INSERT ON *.* TO mysqltest6@localhost; GRANT INSERT ON test.* TO mysqltest6@localhost; diff --git a/mysql-test/suite/rpl/r/rpl_stm_000001.result b/mysql-test/suite/rpl/r/rpl_stm_000001.result index ba05d114a60..e1761da2338 100644 --- a/mysql-test/suite/rpl/r/rpl_stm_000001.result +++ b/mysql-test/suite/rpl/r/rpl_stm_000001.result @@ -66,6 +66,7 @@ Warnings: Warning 1364 Field 'ssl_cipher' doesn't have a default value Warning 1364 Field 'x509_issuer' doesn't have a default value Warning 1364 Field 'x509_subject' doesn't have a default value +Warning 1364 Field 'authentication_string' doesn't have a default value select select_priv,user from mysql.user where user = _binary'blafasel2'; select_priv user N blafasel2 diff --git a/mysql-test/suite/sys_vars/r/external_user_basic.result b/mysql-test/suite/sys_vars/r/external_user_basic.result new file mode 100644 index 00000000000..7cb85fb612d --- /dev/null +++ b/mysql-test/suite/sys_vars/r/external_user_basic.result @@ -0,0 +1,3 @@ +SELECT @@SESSION.EXTERNAL_USER FROM DUAL; +@@SESSION.EXTERNAL_USER +NULL diff --git a/mysql-test/suite/sys_vars/r/proxy_user_basic.result b/mysql-test/suite/sys_vars/r/proxy_user_basic.result new file mode 100644 index 00000000000..7cc62bd9665 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/proxy_user_basic.result @@ -0,0 +1,3 @@ +SELECT @@SESSION.PROXY_USER FROM DUAL; +@@SESSION.PROXY_USER +NULL diff --git a/mysql-test/suite/sys_vars/t/external_user_basic.test b/mysql-test/suite/sys_vars/t/external_user_basic.test new file mode 100644 index 00000000000..3ba4c2d65a3 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/external_user_basic.test @@ -0,0 +1 @@ +SELECT @@SESSION.EXTERNAL_USER FROM DUAL; diff --git a/mysql-test/suite/sys_vars/t/proxy_user_basic.test b/mysql-test/suite/sys_vars/t/proxy_user_basic.test new file mode 100644 index 00000000000..8c7e2d0d8a3 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/proxy_user_basic.test @@ -0,0 +1 @@ +SELECT @@SESSION.PROXY_USER FROM DUAL; diff --git a/mysql-test/t/change_user.test b/mysql-test/t/change_user.test index 3ed798e8d36..ed2e1d05f86 100644 --- a/mysql-test/t/change_user.test +++ b/mysql-test/t/change_user.test @@ -1,4 +1,52 @@ # +# functional change user tests +# + +grant select on test.* to test_nopw; +grant select on test.* to test_oldpw identified by password "09301740536db389"; +grant select on test.* to test_newpw identified by "newpw"; + +select user(), current_user(), database(); +# +# massaging the data for tests to pass in the embedded server, +# that has authentication completely disabled. +# + +--replace_result <@> <test_nopw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); + + +change_user test_nopw; +--replace_result <@> <test_nopw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user test_oldpw, oldpw; +--replace_result <@> <test_oldpw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user test_newpw, newpw; +--replace_result <@> <test_newpw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user root; +--replace_result <@> <root@localhost> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); + +change_user test_nopw,,test; +--replace_result <@> <test_nopw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user test_oldpw,oldpw,test; +--replace_result <@> <test_oldpw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user test_newpw,newpw,test; +--replace_result <@> <test_newpw@%> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); +change_user root,,test; +--replace_result <@> <root@localhost> @> @localhost> +select concat('<', user(), '>'), concat('<', current_user(), '>'), database(); + +drop user test_nopw; +drop user test_oldpw; +drop user test_newpw; + +# # Bug#20023 mysql_change_user() resets the value of SQL_BIG_SELECTS # diff --git a/mysql-test/t/grant.test b/mysql-test/t/grant.test index de43d6a74b4..e73f45a6c53 100644 --- a/mysql-test/t/grant.test +++ b/mysql-test/t/grant.test @@ -29,7 +29,7 @@ flush privileges; grant select on mysqltest.* to mysqltest_1@localhost require cipher "EDH-RSA-DES-CBC3-SHA"; show grants for mysqltest_1@localhost; grant delete on mysqltest.* to mysqltest_1@localhost; -select * from mysql.user where user="mysqltest_1"; +query_vertical select * from mysql.user where user="mysqltest_1"; show grants for mysqltest_1@localhost; revoke delete on mysqltest.* from mysqltest_1@localhost; show grants for mysqltest_1@localhost; @@ -48,10 +48,10 @@ flush privileges; delete from mysql.user where user='mysqltest_1'; flush privileges; grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 10; -select * from mysql.user where user="mysqltest_1"; +query_vertical select * from mysql.user where user="mysqltest_1"; show grants for mysqltest_1@localhost; grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 20 max_connections_per_hour 30; -select * from mysql.user where user="mysqltest_1"; +query_vertical select * from mysql.user where user="mysqltest_1"; show grants for mysqltest_1@localhost; # This is just to double check that one won't ignore results of selects flush privileges; diff --git a/mysql-test/t/grant2.test b/mysql-test/t/grant2.test index 447848013f9..6c2ba0dd6fc 100644 --- a/mysql-test/t/grant2.test +++ b/mysql-test/t/grant2.test @@ -31,7 +31,7 @@ create user mysqltest_2@localhost; connect (user_a,localhost,mysqltest_1,,); connection user_a; grant select on `my\_1`.* to mysqltest_2@localhost; ---error ER_PASSWORD_NOT_ALLOWED +--error ER_DBACCESS_DENIED_ERROR grant select on `my\_1`.* to mysqltest_2@localhost identified by 'pass'; disconnect user_a; connection default; @@ -405,7 +405,7 @@ drop database mysqltest_1; # But anonymous users can't change their password connect (n5,localhost,test,,test,$MASTER_MYPORT,$MASTER_MYSOCK); connection n5; ---error ER_DBACCESS_DENIED_ERROR +--error ER_PASSWORD_NO_MATCH set password = password("changed"); disconnect n5; connection default; diff --git a/mysql-test/t/plugin_auth-master.opt b/mysql-test/t/plugin_auth-master.opt new file mode 100644 index 00000000000..3536d102387 --- /dev/null +++ b/mysql-test/t/plugin_auth-master.opt @@ -0,0 +1,2 @@ +$PLUGIN_AUTH_OPT +$PLUGIN_AUTH_LOAD diff --git a/mysql-test/t/plugin_auth.test b/mysql-test/t/plugin_auth.test new file mode 100644 index 00000000000..f5a8bd416a0 --- /dev/null +++ b/mysql-test/t/plugin_auth.test @@ -0,0 +1,298 @@ +--source include/have_plugin_auth.inc +--source include/not_embedded.inc + +query_vertical SELECT PLUGIN_STATUS, PLUGIN_TYPE, PLUGIN_DESCRIPTION + FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='test_plugin_server'; + +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; + +SELECT plugin,authentication_string FROM mysql.user WHERE User='plug'; + +--echo ## test plugin auth +--disable_query_log +--error ER_ACCESS_DENIED_ERROR : this should fail : no grant +connect(plug_con,localhost,plug,plug_dest); +--enable_query_log + +GRANT PROXY ON plug_dest TO plug; + +connect(plug_con,localhost,plug,plug_dest); + +connection plug_con; +select USER(),CURRENT_USER(); + +--echo ## test SET PASSWORD +#--error ER_SET_PASSWORD_AUTH_PLUGIN +SET PASSWORD = PASSWORD('plug_dest'); + +connection default; +disconnect plug_con; + +--echo ## test bad credentials +--disable_query_log +--error ER_ACCESS_DENIED_ERROR +connect(plug_con,localhost,plug,bad_credentials); +--enable_query_log + +--echo ## test bad default plugin : should get CR_AUTH_PLUGIN_CANNOT_LOAD +--disable_result_log +--disable_query_log +--error 2059 +connect(plug_con_wrongp,localhost,plug,plug_dest,,,,,wrong_plugin_name); +--enable_query_log +--enable_result_log + +--echo ## test correct default plugin +connect(plug_con_rightp,localhost,plug,plug_dest,,,,,auth_test_plugin); +connection plug_con_rightp; +select USER(),CURRENT_USER(); +connection default; +disconnect plug_con_rightp; + +--echo ## test no_auto_create_user sql mode with plugin users +SET @@sql_mode=no_auto_create_user; +GRANT INSERT ON TEST.* TO grant_user IDENTIFIED WITH 'test_plugin_server'; +SET @@sql_mode=default; +DROP USER grant_user; + +--echo ## test utf-8 user name +CREATE USER `Ÿ` IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; + +GRANT PROXY ON plug_dest TO `Ÿ`; + +connect(non_ascii,localhost,Ÿ,plug_dest); +connection non_ascii; +select USER(),CURRENT_USER(); + +connection default; +disconnect non_ascii; +DROP USER `Ÿ`; + +--echo ## test GRANT ... IDENTIFIED WITH/BY ... + +CREATE DATABASE test_grant_db; + +--echo # create new user via GRANT WITH +GRANT ALL PRIVILEGES ON test_grant_db.* TO new_grant_user + IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; + +GRANT PROXY ON plug_dest TO new_grant_user; + +connect(plug_con_grant,localhost,new_grant_user,plug_dest); +connection plug_con_grant; +select USER(),CURRENT_USER(); +USE test_grant_db; +CREATE TABLE t1 (a INT); +DROP TABLE t1; +connection default; +disconnect plug_con_grant; +REVOKE ALL PRIVILEGES ON test_grant_db.* FROM new_grant_user; + +--echo # try re-create existing user via GRANT IDENTIFIED BY +GRANT ALL PRIVILEGES ON test_grant_db.* TO new_grant_user + IDENTIFIED BY 'unused_password'; + +--echo # make sure password doesn't take precendence +--disable_query_log +--error ER_ACCESS_DENIED_ERROR +connect(plug_con_grant_deny,localhost,new_grant_user,unused_password); +--enable_query_log + +--echo #make sure plugin auth still available +connect(plug_con_grant,localhost,new_grant_user,plug_dest); +connection plug_con_grant; +select USER(),CURRENT_USER(); +USE test_grant_db; +CREATE TABLE t1 (a INT); +DROP TABLE t1; +connection default; +disconnect plug_con_grant; + +DROP USER new_grant_user; + +--echo # try re-create existing user via GRANT IDENTIFIED WITH + +--error ER_GRANT_PLUGIN_USER_EXISTS +GRANT ALL PRIVILEGES ON test_grant_db.* TO plug + IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; + +--error ER_GRANT_PLUGIN_USER_EXISTS +GRANT ALL PRIVILEGES ON test_grant_db.* TO plug_dest + IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; + +--error ER_PARSE_ERROR +REVOKE SELECT on test_grant_db.* FROM joro + INDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; + +--error ER_PARSE_ERROR +REVOKE SELECT on test_grant_db.* FROM joro + INDENTIFIED BY 'plug_dest_passwd'; + +--error ER_PARSE_ERROR +REVOKE SELECT on test_grant_db.* FROM joro + INDENTIFIED BY PASSWORD 'plug_dest_passwd'; + +DROP DATABASE test_grant_db; + +--echo ## GRANT PROXY tests + +CREATE USER grant_plug IDENTIFIED WITH 'test_plugin_server' + AS 'grant_plug_dest'; +CREATE USER grant_plug_dest IDENTIFIED BY 'grant_plug_dest_passwd'; +CREATE USER grant_plug_dest2 IDENTIFIED BY 'grant_plug_dest_passwd2'; + +--echo # ALL PRIVILEGES doesn't include PROXY +GRANT ALL PRIVILEGES ON *.* TO grant_plug; +--disable_query_log +--error ER_ACCESS_DENIED_ERROR : this should fail : no grant +connect(grant_plug_con,localhost,grant_plug,grant_plug_dest); +--enable_query_log + +--error ER_PARSE_ERROR : this should fail : can't combine PROXY +GRANT ALL PRIVILEGES,PROXY ON grant_plug_dest TO grant_plug; + +--echo this should fail : can't combine PROXY +--error ER_PARSE_ERROR +GRANT ALL SELECT,PROXY ON grant_plug_dest TO grant_plug; + +--echo # this should fail : no such grant +--error ER_NONEXISTING_GRANT +REVOKE PROXY ON grant_plug_dest FROM grant_plug; + +connect(grant_plug_dest_con,localhost,grant_plug_dest,grant_plug_dest_passwd); +connection grant_plug_dest_con; +--echo in grant_plug_dest_con + +--echo ## testing what an ordinary user can grant +--echo this should fail : no rights to grant all +--error ER_ACCESS_DENIED_NO_PASSWORD_ERROR +GRANT PROXY ON ''@'' TO grant_plug; + +--echo this should fail : not the same user +--error ER_ACCESS_DENIED_NO_PASSWORD_ERROR +GRANT PROXY ON grant_plug TO grant_plug_dest; + +--echo this should fail : same user, but on a different host +--error ER_ACCESS_DENIED_NO_PASSWORD_ERROR +GRANT PROXY ON grant_plug_dest TO grant_plug; + +--echo this should work : same user +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug_dest2; +REVOKE PROXY ON grant_plug_dest@localhost FROM grant_plug_dest2; + +--echo this should work : same user +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug WITH GRANT OPTION; +REVOKE PROXY ON grant_plug_dest@localhost FROM grant_plug; + +--echo this should fail : can't create users +--error ER_CANT_CREATE_USER_WITH_GRANT +GRANT PROXY ON grant_plug_dest@localhost TO grant_plug@localhost; + +connection default; +--echo in default connection +disconnect grant_plug_dest_con; + +--echo # test what root can grant + +--echo should work : root has PROXY to all users +GRANT PROXY ON ''@'' TO grant_plug; +REVOKE PROXY ON ''@'' FROM grant_plug; + +--echo should work : root has PROXY to all users +GRANT PROXY ON ''@'' TO proxy_admin IDENTIFIED BY 'test' + WITH GRANT OPTION; + +--echo need USAGE : PROXY doesn't contain it. +GRANT USAGE on *.* TO proxy_admin; + +connect (proxy_admin_con,localhost,proxy_admin,test); +connection proxy_admin_con; +--echo in proxy_admin_con; + +--echo should work : proxy_admin has proxy to ''@'' +GRANT PROXY ON future_user TO grant_plug; + +connection default; +--echo in default connection +disconnect proxy_admin_con; + +SHOW GRANTS FOR grant_plug; +REVOKE PROXY ON future_user FROM grant_plug; +SHOW GRANTS FOR grant_plug; + +--echo ## testing drop user +CREATE USER test_drop@localhost; +GRANT PROXY ON future_user TO test_drop@localhost; +SHOW GRANTS FOR test_drop@localhost; +DROP USER test_drop@localhost; +SELECT * FROM mysql.proxy_priv WHERE Host = 'test_drop' AND User = 'localhost'; + +DROP USER proxy_admin; + +DROP USER grant_plug,grant_plug_dest,grant_plug_dest2; + +--echo ## END GRANT PROXY tests + +--echo ## cleanup +DROP USER plug; +DROP USER plug_dest; + +--echo ## @@proxy_user tests +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; +GRANT PROXY ON plug_dest TO plug; + +SELECT USER(),CURRENT_USER(),@@LOCAL.proxy_user; + +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@GLOBAL.proxy_user; +SELECT @@LOCAL.proxy_user; + +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET GLOBAL proxy_user = 'test'; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET LOCAL proxy_user = 'test'; +SELECT @@LOCAL.proxy_user; + +connect(plug_con,localhost,plug,plug_dest); +connection plug_con; +--echo # in connection plug_con +SELECT @@LOCAL.proxy_user; +connection default; +--echo # in connection default +disconnect plug_con; + +--echo ## cleanup +DROP USER plug; +DROP USER plug_dest; +--echo ## END @@proxy_user tests + +--echo ## @@external_user tests +CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; +CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; +GRANT PROXY ON plug_dest TO plug; +SELECT USER(),CURRENT_USER(),@@LOCAL.external_user; + +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@GLOBAL.external_user; +SELECT @@LOCAL.external_user; + +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET GLOBAL external_user = 'test'; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET LOCAL external_user = 'test'; +SELECT @@LOCAL.external_user; + +connect(plug_con,localhost,plug,plug_dest); +connection plug_con; +--echo # in connection plug_con +SELECT @@LOCAL.external_user; +connection default; +--echo # in connection default +disconnect plug_con; + +--echo ## cleanup +DROP USER plug; +DROP USER plug_dest; +--echo ## END @@external_user tests diff --git a/mysql-test/t/system_mysql_db_fix40123.test b/mysql-test/t/system_mysql_db_fix40123.test index 818993e5f27..d069271a02e 100644 --- a/mysql-test/t/system_mysql_db_fix40123.test +++ b/mysql-test/t/system_mysql_db_fix40123.test @@ -72,7 +72,7 @@ CREATE TABLE time_zone_leap_second ( Transition_time bigint signed NOT NULL, -- disable_query_log # Drop all tables created by this test -DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index; +DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index, proxy_priv; -- enable_query_log diff --git a/mysql-test/t/system_mysql_db_fix50030.test b/mysql-test/t/system_mysql_db_fix50030.test index 45084177570..53166919f1c 100644 --- a/mysql-test/t/system_mysql_db_fix50030.test +++ b/mysql-test/t/system_mysql_db_fix50030.test @@ -78,7 +78,7 @@ INSERT INTO servers VALUES ('test','localhost','test','root','', 0,'','mysql','r -- disable_query_log # Drop all tables created by this test -DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index; +DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index, proxy_priv; -- enable_query_log diff --git a/mysql-test/t/system_mysql_db_fix50117.test b/mysql-test/t/system_mysql_db_fix50117.test index bed00239081..872829ae79d 100644 --- a/mysql-test/t/system_mysql_db_fix50117.test +++ b/mysql-test/t/system_mysql_db_fix50117.test @@ -97,7 +97,7 @@ CREATE TABLE IF NOT EXISTS ndb_binlog_index (Position BIGINT UNSIGNED NOT NULL, -- disable_query_log # Drop all tables created by this test -DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index; +DROP TABLE db, host, user, func, plugin, tables_priv, columns_priv, procs_priv, servers, help_category, help_keyword, help_relation, help_topic, proc, time_zone, time_zone_leap_second, time_zone_name, time_zone_transition, time_zone_transition_type, general_log, slow_log, event, ndb_binlog_index, proxy_priv; -- enable_query_log diff --git a/plugin/auth/CMakeLists.txt b/plugin/auth/CMakeLists.txt new file mode 100644 index 00000000000..d616d91bc47 --- /dev/null +++ b/plugin/auth/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2010 Sun Microsystems, Inc +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +MYSQL_ADD_PLUGIN(auth dialog.c + MODULE_ONLY) +MYSQL_ADD_PLUGIN(auth_test_plugin test_plugin.c + MODULE_ONLY) + +CHECK_CXX_SOURCE_COMPILES( +"#define _GNU_SOURCE +#include <sys/socket.h> +int main() { + struct ucred cred; + getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cred, 0); +}" HAVE_PEERCRED) + +IF(HAVE_PEERCRED) + MYSQL_ADD_PLUGIN(auth_socket auth_socket.c + MODULE_ONLY) +ENDIF() diff --git a/plugin/auth/Makefile.am b/plugin/auth/Makefile.am new file mode 100644 index 00000000000..ed459b7b2b1 --- /dev/null +++ b/plugin/auth/Makefile.am @@ -0,0 +1,16 @@ +pkgplugindir=$(pkglibdir)/plugin + +AM_LDFLAGS=-module -rpath $(pkgplugindir) +AM_CPPFLAGS=-DMYSQL_DYNAMIC_PLUGIN -Wno-pointer-sign -I$(top_srcdir)/include + +pkgplugin_LTLIBRARIES= auth.la auth_test_plugin.la +auth_la_SOURCES= dialog.c +auth_test_plugin_la_SOURCES= test_plugin.c + +if HAVE_PEERCRED +pkgplugin_LTLIBRARIES+= auth_socket.la +auth_socket_la_SOURCES= auth_socket.c +endif + +EXTRA_DIST= plug.in + diff --git a/plugin/auth/auth_socket.c b/plugin/auth/auth_socket.c new file mode 100644 index 00000000000..2e584e760c7 --- /dev/null +++ b/plugin/auth/auth_socket.c @@ -0,0 +1,93 @@ +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + socket_peercred authentication plugin. + + Authentication is successful if the connection is done via a unix socket and + the owner of the client process matches the user name that was used when + connecting to mysqld. +*/ +#define _GNU_SOURCE /* for struct ucred */ + +#include <mysql/plugin_auth.h> +#include <sys/socket.h> +#include <pwd.h> +#include <string.h> + +static int socket_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + unsigned char *pkt; + MYSQL_PLUGIN_VIO_INFO vio_info; + struct ucred cred; + socklen_t cred_len= sizeof(cred); + struct passwd pwd_buf, *pwd; + char buf[1024]; + + /* no user name yet ? read the client handshake packet with the user name */ + if (info->user_name == 0) + { + if (vio->read_packet(vio, &pkt) < 0) + return CR_ERROR; + } + + info->password_used = 2; + + vio->info(vio, &vio_info); + if (vio_info.protocol != MYSQL_VIO_SOCKET) + return CR_ERROR; + + /* get the UID of the client process */ + if (getsockopt(vio_info.socket, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len)) + return CR_ERROR; + + if (cred_len != sizeof(cred)) + return CR_ERROR; + + /* and find the username for this uid */ + getpwuid_r(cred.uid, &pwd_buf, buf, sizeof(buf), &pwd); + if (pwd == NULL) + return CR_ERROR; + + /* now it's simple as that */ + return strcmp(pwd->pw_name, info->user_name) ? CR_ERROR : CR_OK; +} + +static struct st_mysql_auth socket_auth_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + 0, + socket_auth +}; + +mysql_declare_plugin(socket_auth) +{ + MYSQL_AUTHENTICATION_PLUGIN, + &socket_auth_handler, + "socket_peercred", + "Sergei Golubchik", + "Unix Socket based authentication", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + NULL +} +mysql_declare_plugin_end; + diff --git a/plugin/auth/dialog.c b/plugin/auth/dialog.c new file mode 100644 index 00000000000..157158abb2a --- /dev/null +++ b/plugin/auth/dialog.c @@ -0,0 +1,325 @@ +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + dialog client authentication plugin with examples + + dialog is a general purpose client authentication plugin, it simply + asks the user the question, as provided by the server and reports + the answer back to the server. No encryption is involved, + the answers are sent in clear text. + + Two examples are provided: two_questions server plugin, that asks + the password and an "Are you sure?" question with a reply "yes, of course". + It demonstrates the usage of "password" (input is hidden) and "ordinary" + (input can be echoed) questions, and how to mark the last question, + to avoid an extra roundtrip. + + And three_attempts plugin that gives the user three attempts to enter + a correct password. It shows the situation when a number of questions + is not known in advance. +*/ +#if defined (WIN32) && !defined (RTLD_DEFAULT) +# define RTLD_DEFAULT GetModuleHandle(NULL) +#endif + +#include <my_global.h> +#include <mysql.h> +#include <mysql/plugin_auth.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#if !defined (_GNU_SOURCE) +# define _GNU_SOURCE /* for RTLD_DEFAULT */ +#endif + +/** + first byte of the question string is the question "type". + It can be a "ordinary" or a "password" question. + The last bit set marks a last question in the authentication exchange. +*/ +#define ORDINARY_QUESTION "\2" +#define LAST_QUESTION "\3" +#define PASSWORD_QUESTION "\4" +#define LAST_PASSWORD "\5" + +/********************* SERVER SIDE ****************************************/ + +/** + dialog demo with two questions, one password and one, the last, ordinary. +*/ +static int two_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + unsigned char *pkt; + int pkt_len; + + /* send a password question */ + if (vio->write_packet(vio, (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18)) + return CR_ERROR; + + /* read the answer */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + info->password_used = 1; + + /* fail if the password is wrong */ + if (strcmp((const char *)pkt, info->auth_string)) + return CR_ERROR; + + /* send the last, ordinary, question */ + if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Are you sure ?", 15)) + return CR_ERROR; + + /* read the answer */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + /* check the reply */ + return strcmp((const char *)pkt, "yes, of course") ? CR_ERROR : CR_OK; +} + +static struct st_mysql_auth two_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "dialog", /* requires dialog client plugin */ + two_questions +}; + +/* dialog demo where the number of questions is not known in advance */ +static int three_attempts(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + unsigned char *pkt; + int pkt_len, i; + + for (i= 0; i < 3; i++) + { + /* send the prompt */ + if (vio->write_packet(vio, + (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18)) + return CR_ERROR; + + /* read the password */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + info->password_used = 1; + + /* + finish, if the password is correct. + note, that we did not mark the prompt packet as "last" + */ + if (strcmp((const char *) pkt, info->auth_string) == 0) + return CR_OK; + } + + return CR_ERROR; +} + +static struct st_mysql_auth three_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "dialog", /* requires dialog client plugin */ + three_attempts +}; + +mysql_declare_plugin(dialog) +{ + MYSQL_AUTHENTICATION_PLUGIN, + &two_handler, + "two_questions", + "Sergei Golubchik", + "Dialog plugin demo 1", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + NULL +}, +{ + MYSQL_AUTHENTICATION_PLUGIN, + &three_handler, + "three_attempts", + "Sergei Golubchik", + "Dialog plugin demo 2", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + NULL +} +mysql_declare_plugin_end; + +/********************* CLIENT SIDE ***************************************/ +/* + This plugin performs a dialog with the user, asking questions and + reading answers. Depending on the client it may be desirable to do it + using GUI, or console, with or without curses, or read answers + from a smardcard, for example. + + To support all this variety, the dialog plugin has a callback function + "authentication_dialog_ask". If the client has a function of this name + dialog plugin will use it for communication with the user. Otherwise + a default gets() based implementation will be used. +*/ + +/** + type of the mysql_authentication_dialog_ask function + + @param mysql mysql + @param type type of the input + 1 - ordinary string input + 2 - password string + @param prompt prompt + @param buf a buffer to store the use input + @param buf_len the length of the buffer + + @retval a pointer to the user input string. + It may be equal to 'buf' or to 'mysql->password'. + In all other cases it is assumed to be an allocated + string, and the "dialog" plugin will free() it. +*/ +typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql, + int type, const char *prompt, char *buf, int buf_len); + +static mysql_authentication_dialog_ask_t ask; + +static char *builtin_ask(MYSQL *mysql __attribute__((unused)), + int type __attribute__((unused)), + const char *prompt, + char *buf, int buf_len __attribute__((unused))) +{ + fputs(prompt, stdout); + fputc(' ', stdout); + if (gets(buf) == 0) + return 0; + + return buf; +} + +/** + The main function of the dialog plugin. + + Read the prompt, ask the question, send the reply, repeat until + the server is satisfied. + + @note + 1. this plugin shows how a client authentication plugin + may read a MySQL protocol OK packet internally - which is important + where a number of packets is not known in advance. + 2. the first byte of the prompt is special. it is not + shown to the user, but signals whether it is the last question + (prompt[0] & 1 == 1) or not last (prompt[0] & 1 == 0), + and whether the input is a password (not echoed). + 3. the prompt is expected to be sent zero-terminated +*/ +static int perform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + unsigned char *pkt, cmd= 0; + int pkt_len, res; + char reply_buf[1024], *reply; + + do + { + /* read the prompt */ + pkt_len= vio->read_packet(vio, &pkt); + if (pkt_len < 0) + return CR_ERROR; + + if (pkt == 0) + { + /* + in mysql_change_user() the client sends the first packet, so + the first vio->read_packet() does nothing (pkt == 0). + + We send the "password", assuming the client knows what its doing. + (in other words, the dialog plugin should be only set as a default + authentication plugin on the client if the first question + asks for a password - which will be sent in clear text, by the way) + */ + reply= mysql->passwd; + } + else + { + cmd= *pkt++; + + /* is it MySQL protocol packet ? */ + if (cmd == 0 || cmd == 254) + return CR_OK_HANDSHAKE_COMPLETE; /* yes. we're done */ + + /* + asking for a password with an empty prompt means mysql->password + otherwise we ask the user and read the reply + */ + if ((cmd >> 1) == 2 && *pkt == 0) + reply= mysql->passwd; + else + reply= ask(mysql, cmd >> 1, (const char *) pkt, + reply_buf, sizeof(reply_buf)); + if (!reply) + return CR_ERROR; + } + /* send the reply to the server */ + res= vio->write_packet(vio, (const unsigned char *) reply, + strlen(reply)+1); + + if (reply != mysql->passwd && reply != reply_buf) + free(reply); + + if (res) + return CR_ERROR; + + /* repeat unless it was the last question */ + } while ((cmd & 1) != 1); + + /* the job of reading the ok/error packet is left to the server */ + return CR_OK; +} + +/** + initialization function of the dialog plugin + + Pick up the client's authentication_dialog_ask() function, if exists, + or fall back to the default implementation. +*/ + +static int init_dialog(char *unused1 __attribute__((unused)), + size_t unused2 __attribute__((unused)), + int unused3 __attribute__((unused)), + va_list unused4 __attribute__((unused))) +{ + void *sym= dlsym(RTLD_DEFAULT, "mysql_authentication_dialog_ask"); + ask= sym ? (mysql_authentication_dialog_ask_t)sym : builtin_ask; + return 0; +} + +mysql_declare_client_plugin(AUTHENTICATION) + "dialog", + "Sergei Golubchik", + "Dialog Client Authentication Plugin", + {0,1,0}, + init_dialog, + NULL, + perform_dialog +mysql_end_client_plugin; + diff --git a/plugin/auth/plug.in b/plugin/auth/plug.in new file mode 100644 index 00000000000..776367652ab --- /dev/null +++ b/plugin/auth/plug.in @@ -0,0 +1,12 @@ +MYSQL_PLUGIN(auth, [Collection of Authentication Plugins], + [Collection of Authentication Plugins]) +MYSQL_PLUGIN_DYNAMIC(auth, [dialog.la auth_test_plugin.la]) +AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ +#define _GNU_SOURCE +#include <sys/socket.h> +]],[ + struct ucred cred; + getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cred, 0); +])],have_peercred=yes) +AM_CONDITIONAL(HAVE_PEERCRED, test x$have_peercred = xyes) diff --git a/plugin/auth/test_plugin.c b/plugin/auth/test_plugin.c new file mode 100644 index 00000000000..ad0fcaca2e1 --- /dev/null +++ b/plugin/auth/test_plugin.c @@ -0,0 +1,200 @@ +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + dialog client authentication plugin with examples + + dialog is a general purpose client authentication plugin, it simply + asks the user the question, as provided by the server and reports + the answer back to the server. No encryption is involved, + the answers are sent in clear text. + + Two examples are provided: two_questions server plugin, that asks + the password and an "Are you sure?" question with a reply "yes, of course". + It demonstrates the usage of "password" (input is hidden) and "ordinary" + (input can be echoed) questions, and how to mark the last question, + to avoid an extra roundtrip. + + And three_attempts plugin that gives the user three attempts to enter + a correct password. It shows the situation when a number of questions + is not known in advance. +*/ + +#include <my_global.h> +#include <mysql/plugin_auth.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +/** + first byte of the question string is the question "type". + It can be a "ordinary" or a "password" question. + The last bit set marks a last question in the authentication exchange. +*/ +#define ORDINARY_QUESTION "\2" +#define LAST_QUESTION "\3" +#define LAST_PASSWORD "\4" +#define PASSWORD_QUESTION "\5" + +/********************* SERVER SIDE ****************************************/ + +/** + dialog test plugin mimicing the ordinary auth mechanism. Used to test the auth plugin API +*/ +static int auth_test_plugin(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + unsigned char *pkt; + int pkt_len; + + /* send a password question */ + if (vio->write_packet(vio, (const unsigned char *) PASSWORD_QUESTION, 1)) + return CR_ERROR; + + /* read the answer */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + info->password_used = 1; + + /* fail if the password is wrong */ + if (strcmp((const char *) pkt, info->auth_string)) + return CR_ERROR; + + /* copy auth string as a destination name to check it */ + strcpy (info->authenticated_as, info->auth_string); + + /* copy something into the external user name */ + strcpy (info->external_user, info->auth_string); + + return CR_OK; +} + +static struct st_mysql_auth auth_test_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "auth_test_plugin", /* requires test_plugin client's plugin */ + auth_test_plugin +}; + +mysql_declare_plugin(test_plugin) +{ + MYSQL_AUTHENTICATION_PLUGIN, + &auth_test_handler, + "test_plugin_server", + "Georgi Kodinov", + "plugin API test plugin", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + NULL +} +mysql_declare_plugin_end; + +/********************* CLIENT SIDE ***************************************/ +/* + client plugin used for testing the plugin API +*/ +#include <mysql.h> + +/** + The main function of the test plugin. + + Reads the prompt, check if the handshake is done and if the prompt is a + password request and returns the password. Otherwise return error. + + @note + 1. this plugin shows how a client authentication plugin + may read a MySQL protocol OK packet internally - which is important + where a number of packets is not known in advance. + 2. the first byte of the prompt is special. it is not + shown to the user, but signals whether it is the last question + (prompt[0] & 1 == 1) or not last (prompt[0] & 1 == 0), + and whether the input is a password (not echoed). + 3. the prompt is expected to be sent zero-terminated +*/ +static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + unsigned char *pkt, cmd= 0; + int pkt_len, res; + char *reply; + + do + { + /* read the prompt */ + pkt_len= vio->read_packet(vio, &pkt); + if (pkt_len < 0) + return CR_ERROR; + + if (pkt == 0) + { + /* + in mysql_change_user() the client sends the first packet, so + the first vio->read_packet() does nothing (pkt == 0). + + We send the "password", assuming the client knows what its doing. + (in other words, the dialog plugin should be only set as a default + authentication plugin on the client if the first question + asks for a password - which will be sent in cleat text, by the way) + */ + reply= mysql->passwd; + } + else + { + cmd= *pkt++; + + /* is it MySQL protocol (0=OK or 254=need old password) packet ? */ + if (cmd == 0 || cmd == 254) + return CR_OK_HANDSHAKE_COMPLETE; /* yes. we're done */ + + /* + asking for a password with an empty prompt means mysql->password + otherwise return an error + */ + if ((cmd == LAST_PASSWORD[0] || cmd == PASSWORD_QUESTION[0]) && *pkt == 0) + reply= mysql->passwd; + else + return CR_ERROR; + } + if (!reply) + return CR_ERROR; + /* send the reply to the server */ + res= vio->write_packet(vio, (const unsigned char *)reply, strlen(reply)+1); + + if (res) + return CR_ERROR; + + /* repeat unless it was the last question */ + } while (cmd != LAST_QUESTION[0] && cmd != PASSWORD_QUESTION[0]); + + /* the job of reading the ok/error packet is left to the server */ + return CR_OK; +} + + +mysql_declare_client_plugin(AUTHENTICATION) + "auth_test_plugin", + "Georgi Kodinov", + "Dialog Client Authentication Plugin", + {0,1,0}, + NULL, + NULL, + test_plugin_client +mysql_end_client_plugin; diff --git a/scripts/mysql_system_tables.sql b/scripts/mysql_system_tables.sql index b5ee4d6dd41..c7abf3d82a1 100644 --- a/scripts/mysql_system_tables.sql +++ b/scripts/mysql_system_tables.sql @@ -28,7 +28,7 @@ set @had_db_table= @@warning_count != 0; CREATE TABLE IF NOT EXISTS host ( Host char(60) binary DEFAULT '' NOT NULL, Db char(64) binary DEFAULT '' NOT NULL, Select_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Insert_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Update_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Delete_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Drop_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Grant_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, References_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Index_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_tmp_table_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Lock_tables_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Show_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Execute_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Trigger_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, PRIMARY KEY Host (Host,Db) ) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='Host privileges; Merged with database privileges'; -CREATE TABLE IF NOT EXISTS user ( Host char(60) binary DEFAULT '' NOT NULL, User char(16) binary DEFAULT '' NOT NULL, Password char(41) character set latin1 collate latin1_bin DEFAULT '' NOT NULL, Select_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Insert_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Update_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Delete_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Drop_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Reload_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Shutdown_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Process_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, File_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Grant_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, References_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Index_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Show_db_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Super_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_tmp_table_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Lock_tables_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Execute_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Repl_slave_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Repl_client_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Show_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_user_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Trigger_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_tablespace_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, ssl_type enum('','ANY','X509', 'SPECIFIED') COLLATE utf8_general_ci DEFAULT '' NOT NULL, ssl_cipher BLOB NOT NULL, x509_issuer BLOB NOT NULL, x509_subject BLOB NOT NULL, max_questions int(11) unsigned DEFAULT 0 NOT NULL, max_updates int(11) unsigned DEFAULT 0 NOT NULL, max_connections int(11) unsigned DEFAULT 0 NOT NULL, max_user_connections int(11) unsigned DEFAULT 0 NOT NULL, PRIMARY KEY Host (Host,User) ) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='Users and global privileges'; +CREATE TABLE IF NOT EXISTS user ( Host char(60) binary DEFAULT '' NOT NULL, User char(16) binary DEFAULT '' NOT NULL, Password char(41) character set latin1 collate latin1_bin DEFAULT '' NOT NULL, Select_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Insert_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Update_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Delete_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Drop_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Reload_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Shutdown_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Process_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, File_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Grant_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, References_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Index_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Show_db_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Super_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_tmp_table_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Lock_tables_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Execute_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Repl_slave_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Repl_client_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Show_view_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Alter_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_user_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Trigger_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, Create_tablespace_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL, ssl_type enum('','ANY','X509', 'SPECIFIED') COLLATE utf8_general_ci DEFAULT '' NOT NULL, ssl_cipher BLOB NOT NULL, x509_issuer BLOB NOT NULL, x509_subject BLOB NOT NULL, max_questions int(11) unsigned DEFAULT 0 NOT NULL, max_updates int(11) unsigned DEFAULT 0 NOT NULL, max_connections int(11) unsigned DEFAULT 0 NOT NULL, max_user_connections int(11) unsigned DEFAULT 0 NOT NULL, plugin char(60) DEFAULT '' NOT NULL, authentication_string TEXT NOT NULL, PRIMARY KEY Host (Host,User) ) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='Users and global privileges'; -- Remember for later if user table already existed set @had_user_table= @@warning_count != 0; @@ -498,3 +498,7 @@ PREPARE stmt FROM @str; EXECUTE stmt; DROP PREPARE stmt; +CREATE TABLE IF NOT EXISTS proxy_priv (Host char(60) binary DEFAULT '' NOT NULL, User char(16) binary DEFAULT '' NOT NULL, Proxied_Host char(16) binary DEFAULT '' NOT NULL, Proxied_User char(60) binary DEFAULT '' NOT NULL, With_Grant BOOL DEFAULT 0 NOT NULL, PRIMARY KEY Host (Host,User,Proxied_Host,Proxied_User) ) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='User proxy privileges'; + +-- Remember for later if proxy_priv table already existed +set @had_proxy_priv_table= @@warning_count != 0; diff --git a/scripts/mysql_system_tables_data.sql b/scripts/mysql_system_tables_data.sql index a7988b5198e..293baa46523 100644 --- a/scripts/mysql_system_tables_data.sql +++ b/scripts/mysql_system_tables_data.sql @@ -21,11 +21,17 @@ DROP TABLE tmp_db; -- from local machine if "users" table didn't exist before CREATE TEMPORARY TABLE tmp_user LIKE user; set @current_hostname= @@hostname; -INSERT INTO tmp_user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); -REPLACE INTO tmp_user SELECT @current_hostname,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0 FROM dual WHERE LOWER( @current_hostname) != 'localhost'; -REPLACE INTO tmp_user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); -REPLACE INTO tmp_user VALUES ('::1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); +INSERT INTO tmp_user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0,'',''); +REPLACE INTO tmp_user SELECT @current_hostname,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0,'','' FROM dual WHERE LOWER( @current_hostname) != 'localhost'; +REPLACE INTO tmp_user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0,'',''); +REPLACE INTO tmp_user VALUES ('::1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0,'',''); INSERT INTO tmp_user (host,user) VALUES ('localhost',''); INSERT INTO tmp_user (host,user) SELECT @current_hostname,'' FROM dual WHERE LOWER(@current_hostname ) != 'localhost'; INSERT INTO user SELECT * FROM tmp_user WHERE @had_user_table=0; DROP TABLE tmp_user; + +CREATE TEMPORARY TABLE tmp_proxy_priv LIKE proxy_priv; +INSERT INTO tmp_proxy_priv VALUES ('localhost', 'root', '', '', TRUE); +REPLACE INTO tmp_proxy_priv SELECT @current_hostname, 'root', '', '', TRUE FROM DUAL WHERE LOWER (@current_hostname) != 'localhost'; +INSERT INTO proxy_priv SELECT * FROM tmp_proxy_priv WHERE @had_proxy_priv_table=0; +DROP TABLE tmp_proxy_priv; diff --git a/scripts/mysql_system_tables_fix.sql b/scripts/mysql_system_tables_fix.sql index 2020971fb1a..ceb910676ab 100644 --- a/scripts/mysql_system_tables_fix.sql +++ b/scripts/mysql_system_tables_fix.sql @@ -640,6 +640,11 @@ DROP PREPARE stmt; drop procedure mysql.die; +ALTER TABLE user ADD plugin char(60) DEFAULT '' NOT NULL, ADD authentication_string TEXT NOT NULL; +ALTER TABLE user MODIFY plugin char(60) DEFAULT '' NOT NULL; + +CREATE TABLE IF NOT EXISTS proxy_priv (Host char(60) binary DEFAULT '' NOT NULL, User char(16) binary DEFAULT '' NOT NULL, Proxied_User char(60) binary DEFAULT '' NOT NULL, Proxied_Host char(16) binary DEFAULT '' NOT NULL, With_Grant BOOL DEFAULT 0 NOT NULL, PRIMARY KEY Host (Host,User,Proxied_Host,Proxied_User) ) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='Users and global privileges'; + # Activate the new, possible modified privilege tables # This should not be needed, but gives us some extra testing that the above # changes was correct diff --git a/sql-common/Makefile.am b/sql-common/Makefile.am index 3193efee754..2f5a049085f 100644 --- a/sql-common/Makefile.am +++ b/sql-common/Makefile.am @@ -14,4 +14,4 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## Process this file with automake to create Makefile.in -EXTRA_DIST = client.c pack.c my_time.c my_user.c +EXTRA_DIST = client.c pack.c my_time.c my_user.c client_plugin.c diff --git a/sql-common/client.c b/sql-common/client.c index f1bdcdc158f..3897e44fdaf 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -111,6 +111,10 @@ my_bool net_flush(NET *net); #include "client_settings.h" #include <sql_common.h> +#include <mysql/client_plugin.h> +#define native_password_plugin_name "mysql_native_password" +#define old_password_plugin_name "mysql_old_password" + uint mysql_port=0; char *mysql_unix_port= 0; @@ -373,7 +377,7 @@ void net_clear_error(NET *net) @param ... variable number of arguments */ -static void set_mysql_extended_error(MYSQL *mysql, int errcode, +void set_mysql_extended_error(MYSQL *mysql, int errcode, const char *sqlstate, const char *format, ...) { @@ -1145,9 +1149,20 @@ static const char *default_options[]= "connect-timeout", "local-infile", "disable-local-infile", "ssl-cipher", "max-allowed-packet", "protocol", "shared-memory-base-name", "multi-results", "multi-statements", "multi-queries", "secure-auth", - "report-data-truncation", + "report-data-truncation", "plugin-dir", "default-auth", NullS }; +enum option_id { + OPT_port=1, OPT_socket, OPT_compress, OPT_password, OPT_pipe, OPT_timeout, OPT_user, + OPT_init_command, OPT_host, OPT_database, OPT_debug, OPT_return_found_rows, + OPT_ssl_key, OPT_ssl_cert, OPT_ssl_ca, OPT_ssl_capath, + OPT_character_sets_dir, OPT_default_character_set, OPT_interactive_timeout, + OPT_connect_timeout, OPT_local_infile, OPT_disable_local_infile, + OPT_replication_probe, OPT_enable_reads_from_master, OPT_repl_parse_query, + OPT_ssl_cipher, OPT_max_allowed_packet, OPT_protocol, OPT_shared_memory_base_name, + OPT_multi_results, OPT_multi_statements, OPT_multi_queries, OPT_secure_auth, + OPT_report_data_truncation, OPT_plugin_dir, OPT_default_auth, +}; static TYPELIB option_types={array_elements(default_options)-1, "options",default_options, NULL}; @@ -1178,6 +1193,15 @@ static int add_init_command(struct st_mysql_options *options, const char *cmd) return 0; } +#define EXTENSION_SET_STRING(OPTS, X, STR) \ + if ((OPTS)->extension) \ + my_free((OPTS)->extension->X); \ + else \ + (OPTS)->extension= (struct st_mysql_options_extention *) \ + my_malloc(sizeof(struct st_mysql_options_extention), \ + MYF(MY_WME | MY_ZEROFILL)); \ + (OPTS)->extension->X= my_strdup((STR), MYF(MY_WME)); + void mysql_read_default_options(struct st_mysql_options *options, const char *filename,const char *group) { @@ -1212,121 +1236,121 @@ void mysql_read_default_options(struct st_mysql_options *options, for (end= *option ; *(end= strcend(end,'_')) ; ) *end= '-'; switch (find_type(*option+2,&option_types,2)) { - case 1: /* port */ + case OPT_port: if (opt_arg) options->port=atoi(opt_arg); break; - case 2: /* socket */ + case OPT_socket: if (opt_arg) { my_free(options->unix_socket); options->unix_socket=my_strdup(opt_arg,MYF(MY_WME)); } break; - case 3: /* compress */ + case OPT_compress: options->compress=1; options->client_flag|= CLIENT_COMPRESS; break; - case 4: /* password */ + case OPT_password: if (opt_arg) { my_free(options->password); options->password=my_strdup(opt_arg,MYF(MY_WME)); } break; - case 5: + case OPT_pipe: options->protocol = MYSQL_PROTOCOL_PIPE; - case 20: /* connect_timeout */ - case 6: /* timeout */ + case OPT_connect_timeout: + case OPT_timeout: if (opt_arg) options->connect_timeout=atoi(opt_arg); break; - case 7: /* user */ + case OPT_user: if (opt_arg) { my_free(options->user); options->user=my_strdup(opt_arg,MYF(MY_WME)); } break; - case 8: /* init-command */ + case OPT_init_command: add_init_command(options,opt_arg); break; - case 9: /* host */ + case OPT_host: if (opt_arg) { my_free(options->host); options->host=my_strdup(opt_arg,MYF(MY_WME)); } break; - case 10: /* database */ + case OPT_database: if (opt_arg) { my_free(options->db); options->db=my_strdup(opt_arg,MYF(MY_WME)); } break; - case 11: /* debug */ + case OPT_debug: #ifdef MYSQL_CLIENT mysql_debug(opt_arg ? opt_arg : "d:t:o,/tmp/client.trace"); break; #endif - case 12: /* return-found-rows */ + case OPT_return_found_rows: options->client_flag|=CLIENT_FOUND_ROWS; break; #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - case 13: /* ssl_key */ + case OPT_ssl_key: my_free(options->ssl_key); options->ssl_key = my_strdup(opt_arg, MYF(MY_WME)); break; - case 14: /* ssl_cert */ + case OPT_ssl_cert: my_free(options->ssl_cert); options->ssl_cert = my_strdup(opt_arg, MYF(MY_WME)); break; - case 15: /* ssl_ca */ + case OPT_ssl_ca: my_free(options->ssl_ca); options->ssl_ca = my_strdup(opt_arg, MYF(MY_WME)); break; - case 16: /* ssl_capath */ + case OPT_ssl_capath: my_free(options->ssl_capath); options->ssl_capath = my_strdup(opt_arg, MYF(MY_WME)); break; - case 23: /* ssl_cipher */ + case OPT_ssl_cipher: my_free(options->ssl_cipher); options->ssl_cipher= my_strdup(opt_arg, MYF(MY_WME)); break; #else - case 13: /* Ignore SSL options */ - case 14: - case 15: - case 16: - case 23: + case OPT_ssl_key: + case OPT_ssl_cert: + case OPT_ssl_ca: + case OPT_ssl_capath: + case OPT_ssl_cipher: break; #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ - case 17: /* charset-lib */ + case OPT_character_sets_dir: my_free(options->charset_dir); options->charset_dir = my_strdup(opt_arg, MYF(MY_WME)); break; - case 18: + case OPT_default_character_set: my_free(options->charset_name); options->charset_name = my_strdup(opt_arg, MYF(MY_WME)); break; - case 19: /* Interactive-timeout */ + case OPT_interactive_timeout: options->client_flag|= CLIENT_INTERACTIVE; break; - case 21: + case OPT_local_infile: if (!opt_arg || atoi(opt_arg) != 0) options->client_flag|= CLIENT_LOCAL_FILES; else options->client_flag&= ~CLIENT_LOCAL_FILES; break; - case 22: + case OPT_disable_local_infile: options->client_flag&= ~CLIENT_LOCAL_FILES; break; - case 24: /* max-allowed-packet */ + case OPT_max_allowed_packet: if (opt_arg) options->max_allowed_packet= atoi(opt_arg); break; - case 25: /* protocol */ + case OPT_protocol: if ((options->protocol= find_type(opt_arg, &sql_protocol_typelib,0)) <= 0) { @@ -1334,26 +1358,44 @@ void mysql_read_default_options(struct st_mysql_options *options, exit(1); } break; - case 26: /* shared_memory_base_name */ + case OPT_shared_memory_base_name: #ifdef HAVE_SMEM if (options->shared_memory_base_name != def_shared_memory_base_name) my_free(options->shared_memory_base_name); options->shared_memory_base_name=my_strdup(opt_arg,MYF(MY_WME)); #endif break; - case 27: /* multi-results */ + case OPT_multi_results: options->client_flag|= CLIENT_MULTI_RESULTS; break; - case 28: /* multi-statements */ - case 29: /* multi-queries */ + case OPT_multi_statements: + case OPT_multi_queries: options->client_flag|= CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS; break; - case 30: /* secure-auth */ + case OPT_secure_auth: options->secure_auth= TRUE; break; - case 31: /* report-data-truncation */ + case OPT_report_data_truncation: options->report_data_truncation= opt_arg ? test(atoi(opt_arg)) : 1; break; + case OPT_plugin_dir: + { + char buff[FN_REFLEN], buff2[FN_REFLEN]; + if (strlen(opt_arg) >= FN_REFLEN) + opt_arg[FN_REFLEN]= '\0'; + if (my_realpath(buff, opt_arg, 0)) + { + DBUG_PRINT("warning",("failed to normalize the plugin path: %s", + opt_arg)); + break; + } + convert_dirname(buff, buff2, NULL); + EXTENSION_SET_STRING(options, plugin_dir, buff2); + } + break; + case OPT_default_auth: + EXTENSION_SET_STRING(options, default_auth, opt_arg); + break; default: DBUG_PRINT("warning",("unknown option: %s",option[0])); } @@ -1884,6 +1926,11 @@ static int ssl_verify_server_cert(Vio *vio, const char* server_hostname) static my_bool cli_read_query_result(MYSQL *mysql); static MYSQL_RES *cli_use_result(MYSQL *mysql); +int cli_read_change_user_result(MYSQL *mysql) +{ + return cli_safe_read(mysql); +} + static MYSQL_METHODS client_methods= { cli_read_query_result, /* read_query_result */ @@ -1891,7 +1938,8 @@ static MYSQL_METHODS client_methods= cli_read_rows, /* read_rows */ cli_use_result, /* use_result */ cli_fetch_lengths, /* fetch_lengths */ - cli_flush_use_result /* flush_use_result */ + cli_flush_use_result, /* flush_use_result */ + cli_read_change_user_result /* read_change_user_result */ #ifndef MYSQL_SERVER ,cli_list_fields, /* list_fields */ cli_read_prepare_result, /* read_prepare_result */ @@ -1901,7 +1949,6 @@ static MYSQL_METHODS client_methods= NULL, /* free_embedded_thd */ cli_read_statistics, /* read_statistics */ cli_read_query_result, /* next_result */ - cli_read_change_user_result, /* read_change_user_result */ cli_read_binary_rows /* read_rows_from_cursor */ #endif }; @@ -2221,6 +2268,648 @@ int mysql_init_character_set(MYSQL *mysql) } C_MODE_END +/*********** client side authentication support **************************/ + +typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t; +static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, int); +static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); +static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); + +static auth_plugin_t native_password_client_plugin= +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, + native_password_plugin_name, + "R.J.Silk, Sergei Golubchik", + "Native MySQL authentication", + {1, 0, 0}, + NULL, + NULL, + native_password_auth_client +}; + +static auth_plugin_t old_password_client_plugin= +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, + old_password_plugin_name, + "R.J.Silk, Sergei Golubchik", + "Old MySQL-3.23 authentication", + {1, 0, 0}, + NULL, + NULL, + old_password_auth_client +}; + +struct st_mysql_client_plugin *mysql_client_builtins[]= +{ + (struct st_mysql_client_plugin *)&native_password_client_plugin, + (struct st_mysql_client_plugin *)&old_password_client_plugin, + 0 +}; + + + +/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ +typedef struct { + int (*read_packet)(struct st_plugin_vio *vio, uchar **buf); + int (*write_packet)(struct st_plugin_vio *vio, const uchar *pkt, int pkt_len); + void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); + /* -= end of MYSQL_PLUGIN_VIO =- */ + MYSQL *mysql; + auth_plugin_t *plugin; /**< what plugin we're under */ + const char *db; + struct { + uchar *pkt; /**< pointer into NET::buff */ + uint pkt_len; + } cached_server_reply; + int packets_read, packets_written; /**< counters for send/received packets */ + int mysql_change_user; /**< if it's mysql_change_user() */ + int last_read_packet_len; /**< the length of the last *read* packet */ +} MCPVIO_EXT; + +/** + sends a COM_CHANGE_USER command with a caller provided payload + + Packet format: + + Bytes Content + ----- ---- + n user name - \0-terminated string + n password + 3.23 scramble - \0-terminated string (9 bytes) + otherwise - length (1 byte) coded + n database name - \0-terminated string + 2 character set number (if the server >= 4.1.x) + n client auth plugin name - \0-terminated string, + (if the server supports plugin auth) + + @retval 0 ok + @retval 1 error +*/ +static int send_change_user_packet(MCPVIO_EXT *mpvio, + const uchar *data, int data_len) +{ + MYSQL *mysql= mpvio->mysql; + char *buff, *end; + int res= 1; + + buff= my_alloca(USERNAME_LENGTH + data_len + 1 + NAME_LEN + 2 + NAME_LEN); + + end= strmake(buff, mysql->user, USERNAME_LENGTH) + 1; + + if (!data_len) + *end++= 0; + else + { + if (mysql->client_flag & CLIENT_SECURE_CONNECTION) + { + DBUG_ASSERT(data_len <= 255); + if (data_len > 255) + { + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + goto error; + } + *end++= data_len; + } + else + { + DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); + DBUG_ASSERT(data[SCRAMBLE_LENGTH_323] == 0); + } + memcpy(end, data, data_len); + end+= data_len; + } + end= strmake(end, mpvio->db ? mpvio->db : "", NAME_LEN) + 1; + + if (mysql->server_capabilities & CLIENT_PROTOCOL_41) + { + int2store(end, (ushort) mysql->charset->number); + end+= 2; + } + + if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) + end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + + res= simple_command(mysql, COM_CHANGE_USER, + (uchar*)buff, (ulong)(end-buff), 1); + +error: + my_afree(buff); + return res; +} + +/** + sends a client authentication packet (second packet in the 3-way handshake) + + Packet format (when the server is 4.0 or earlier): + + Bytes Content + ----- ---- + 2 client capabilities + 3 max packet size + n user name, \0-terminated + 9 scramble_323, \0-terminated + + Packet format (when the server is 4.1 or newer): + + Bytes Content + ----- ---- + 4 client capabilities + 4 max packet size + 1 charset number + 23 reserved (always 0) + n user name, \0-terminated + n plugin auth data (e.g. scramble), length (1 byte) coded + n database name, \0-terminated + (if CLIENT_CONNECT_WITH_DB is set in the capabilities) + n client auth plugin name - \0-terminated string, + (if CLIENT_PLUGIN_AUTH is set in the capabilities) + + @retval 0 ok + @retval 1 error +*/ +static int send_client_reply_packet(MCPVIO_EXT *mpvio, + const uchar *data, int data_len) +{ + MYSQL *mysql= mpvio->mysql; + NET *net= &mysql->net; + char *buff, *end; + + /* see end= buff+32 below, fixed size of the packet is 32 bytes */ + buff= my_alloca(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN); + + mysql->client_flag|= mysql->options.client_flag; + mysql->client_flag|= CLIENT_CAPABILITIES; + + if (mysql->client_flag & CLIENT_MULTI_STATEMENTS) + mysql->client_flag|= CLIENT_MULTI_RESULTS; + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (mysql->options.ssl_key || mysql->options.ssl_cert || + mysql->options.ssl_ca || mysql->options.ssl_capath || + mysql->options.ssl_cipher) + mysql->options.use_ssl= 1; + if (mysql->options.use_ssl) + mysql->client_flag|= CLIENT_SSL; +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ + if (mpvio->db) + mysql->client_flag|= CLIENT_CONNECT_WITH_DB; + + /* Remove options that server doesn't support */ + mysql->client_flag= mysql->client_flag & + (~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41) + | mysql->server_capabilities); + +#ifndef HAVE_COMPRESS + mysql->client_flag&= ~CLIENT_COMPRESS; +#endif + + if (mysql->client_flag & CLIENT_PROTOCOL_41) + { + /* 4.1 server and 4.1 client has a 32 byte option flag */ + int4store(buff,mysql->client_flag); + int4store(buff+4, net->max_packet_size); + buff[8]= (char) mysql->charset->number; + bzero(buff+9, 32-9); + end= buff+32; + } + else + { + int2store(buff, mysql->client_flag); + int3store(buff+2, net->max_packet_size); + end= buff+5; + } +#ifdef HAVE_OPENSSL + if (mysql->client_flag & CLIENT_SSL) + { + /* Do the SSL layering. */ + struct st_mysql_options *options= &mysql->options; + struct st_VioSSLFd *ssl_fd; + + /* + Send mysql->client_flag, max_packet_size - unencrypted otherwise + the server does not know we want to do SSL + */ + if (my_net_write(net, (uchar*)buff, (size_t) (end-buff)) || net_flush(net)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending connection information to server", + errno); + goto error; + } + + /* Create the VioSSLConnectorFd - init SSL and load certs */ + if (!(ssl_fd= new_VioSSLConnectorFd(options->ssl_key, + options->ssl_cert, + options->ssl_ca, + options->ssl_capath, + options->ssl_cipher))) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + mysql->connector_fd= (unsigned char *) ssl_fd; + + /* Connect to the server */ + DBUG_PRINT("info", ("IO layer change in progress...")); + if (sslconnect(ssl_fd, net->vio, + (long) (mysql->options.connect_timeout))) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + DBUG_PRINT("info", ("IO layer change done!")); + + /* Verify server cert */ + if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + ssl_verify_server_cert(net->vio, mysql->host)) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + } +#endif /* HAVE_OPENSSL */ + + DBUG_PRINT("info",("Server version = '%s' capabilites: %lu status: %u client_flag: %lu", + mysql->server_version, mysql->server_capabilities, + mysql->server_status, mysql->client_flag)); + + compile_time_assert(MYSQL_USERNAME_LENGTH == USERNAME_LENGTH); + + /* This needs to be changed as it's not useful with big packets */ + if (mysql->user[0]) + strmake(end, mysql->user, USERNAME_LENGTH); + else + read_user_name(end); + + /* We have to handle different version of handshake here */ + DBUG_PRINT("info",("user: %s",end)); + end= strend(end) + 1; + if (data_len) + { + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + *end++= data_len; + memcpy(end, data, data_len); + end+= data_len; + } + else + { + DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); /* incl. \0 at the end */ + memcpy(end, data, data_len); + end+= data_len; + } + } + else + *end++= 0; + + /* Add database if needed */ + if (mpvio->db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) + { + end= strmake(end, mpvio->db, NAME_LEN) + 1; + mysql->db= my_strdup(mpvio->db, MYF(MY_WME)); + } + + if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) + end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + + /* Write authentication package */ + if (my_net_write(net, (uchar*) buff, (size_t) (end-buff)) || net_flush(net)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending authentication information", + errno); + goto error; + } + my_afree(buff); + return 0; + +error: + my_afree(buff); + return 1; +} + +/** + vio->read_packet() callback method for client authentication plugins + + This function is called by a client authentication plugin, when it wants + to read data from the server. +*/ +static int client_mpvio_read_packet(struct st_plugin_vio *mpv, uchar **buf) +{ + MCPVIO_EXT *mpvio= (MCPVIO_EXT*)mpv; + MYSQL *mysql= mpvio->mysql; + ulong pkt_len; + + /* there are cached data left, feed it to a plugin */ + if (mpvio->cached_server_reply.pkt) + { + *buf= mpvio->cached_server_reply.pkt; + mpvio->cached_server_reply.pkt= 0; + mpvio->packets_read++; + return mpvio->cached_server_reply.pkt_len; + } + + if (mpvio->packets_read == 0) + { + /* + the server handshake packet came from the wrong plugin, + or it's mysql_change_user(). Either way, there is no data + for a plugin to read. send a dummy packet to the server + to initiate a dialog. + */ + if (client_mpvio_write_packet(mpv, 0, 0)) + return (int)packet_error; + } + + /* otherwise read the data */ + pkt_len= (*mysql->methods->read_change_user_result)(mysql); + mpvio->last_read_packet_len= pkt_len; + *buf= mysql->net.read_pos; + + /* was it a request to change plugins ? */ + if (**buf == 254) + return (int)packet_error; /* if yes, this plugin shan't continue */ + + /* + the server sends \1\255 or \1\254 instead of just \255 or \254 - + for us to not confuse it with an error or "change plugin" packets. + We remove this escaping \1 here. + + See also server_mpvio_write_packet() where the escaping is done. + */ + if (pkt_len && **buf == 1) + { + (*buf)++; + pkt_len--; + } + mpvio->packets_read++; + return pkt_len; +} + +/** + vio->write_packet() callback method for client authentication plugins + + This function is called by a client authentication plugin, when it wants + to send data to the server. + + It transparently wraps the data into a change user or authentication + handshake packet, if neccessary. +*/ +static int client_mpvio_write_packet(struct st_plugin_vio *mpv, + const uchar *pkt, int pkt_len) +{ + int res; + MCPVIO_EXT *mpvio= (MCPVIO_EXT*)mpv; + + if (mpvio->packets_written == 0) + { + if (mpvio->mysql_change_user) + res= send_change_user_packet(mpvio, pkt, pkt_len); + else + res= send_client_reply_packet(mpvio, pkt, pkt_len); + } + else + { + NET *net= &mpvio->mysql->net; + if (mpvio->mysql->thd) + res= 1; /* no chit-chat in embedded */ + else + res= my_net_write(net, pkt, pkt_len) || net_flush(net); + if (res) + set_mysql_extended_error(mpvio->mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending authentication information", + errno); + } + mpvio->packets_written++; + return res; +} + +/** + fills MYSQL_PLUGIN_VIO_INFO structure with the information about the + connection +*/ +void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info) +{ + bzero(info, sizeof(*info)); + switch (vio->type) { + case VIO_TYPE_TCPIP: + info->protocol= MYSQL_VIO_TCP; + info->socket= vio->sd; + return; + case VIO_TYPE_SOCKET: + info->protocol= MYSQL_VIO_SOCKET; + info->socket= vio->sd; + return; + case VIO_TYPE_SSL: + { + struct sockaddr addr; + socklen_t addrlen= sizeof(addr); + if (getsockname(vio->sd, &addr, &addrlen)) + return; + info->protocol= addr.sa_family == AF_UNIX ? + MYSQL_VIO_SOCKET : MYSQL_VIO_TCP; + info->socket= vio->sd; + return; + } +#ifdef _WIN32 + case VIO_TYPE_NAMEDPIPE: + info->protocol= MYSQL_VIO_PIPE; + info->handle= vio->hPipe; + return; +#ifdef HAVE_SMEM + case VIO_TYPE_SHARED_MEMORY: + info->protocol= MYSQL_VIO_MEMORY; + info->handle= vio->handle_file_map; /* or what ? */ + return; +#endif +#endif + default: DBUG_ASSERT(0); + } +} + +static void client_mpvio_info(MYSQL_PLUGIN_VIO *vio, + MYSQL_PLUGIN_VIO_INFO *info) +{ + MCPVIO_EXT *mpvio= (MCPVIO_EXT*)vio; + mpvio_info(mpvio->mysql->net.vio, info); +} + +/** + Client side of the plugin driver authentication. + + @note this is used by both the mysql_real_connect and mysql_change_user + + @param mysql mysql + @param data pointer to the plugin auth data (scramble) in the + handshake packet + @param data_len the length of the data + @param data_plugin a plugin that data were prepared for + or 0 if it's mysql_change_user() + @param db initial db to use, can be 0 + + @retval 0 ok + @retval 1 error +*/ +int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, + const char *data_plugin, const char *db) +{ + const char *auth_plugin_name; + auth_plugin_t *auth_plugin; + MCPVIO_EXT mpvio; + ulong pkt_length; + int res; + + DBUG_ENTER ("run_plugin_auth"); + /* determine the default/initial plugin to use */ + if (mysql->options.extension && mysql->options.extension->default_auth && + mysql->server_capabilities & CLIENT_PLUGIN_AUTH) + { + auth_plugin_name= mysql->options.extension->default_auth; + if (!(auth_plugin= (auth_plugin_t*) mysql_client_find_plugin(mysql, + auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN (1); /* oops, not found */ + } + else + { + auth_plugin= mysql->server_capabilities & CLIENT_PROTOCOL_41 ? + &native_password_client_plugin : &old_password_client_plugin; + auth_plugin_name= auth_plugin->name; + } + + DBUG_PRINT ("info", ("using plugin %s", auth_plugin_name)); + + mysql->net.last_errno= 0; /* just in case */ + + if (data_plugin && strcmp(data_plugin, auth_plugin_name)) + { + /* data was prepared for a different plugin, don't show it to this one */ + data= 0; + data_len= 0; + } + + mpvio.mysql_change_user= data_plugin == 0; + mpvio.cached_server_reply.pkt= (uchar*)data; + mpvio.cached_server_reply.pkt_len= data_len; + mpvio.read_packet= client_mpvio_read_packet; + mpvio.write_packet= client_mpvio_write_packet; + mpvio.info= client_mpvio_info; + mpvio.mysql= mysql; + mpvio.packets_read= mpvio.packets_written= 0; + mpvio.db= db; + mpvio.plugin= auth_plugin; + + res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + DBUG_PRINT ("info", ("authenticate_user returned %s", + res == CR_OK ? "CR_OK" : + res == CR_ERROR ? "CR_ERROR" : + res == CR_OK_HANDSHAKE_COMPLETE ? + "CR_OK_HANDSHAKE_COMPLETE" : "error")); + + compile_time_assert(CR_OK == -1); + compile_time_assert(CR_ERROR == 0); + if (res > CR_OK && mysql->net.read_pos[0] != 254) + { + /* + the plugin returned an error. write it down in mysql, + unless the error code is CR_ERROR and mysql->net.last_errno + is already set (the plugin has done it) + */ + DBUG_PRINT ("info", ("res=%d", res)); + if (res > CR_ERROR) + set_mysql_error(mysql, res, unknown_sqlstate); + else + if (!mysql->net.last_errno) + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + DBUG_RETURN (1); + } + + /* read the OK packet (or use the cached value in mysql->net.read_pos */ + if (res == CR_OK) + pkt_length= (*mysql->methods->read_change_user_result)(mysql); + else /* res == CR_OK_HANDSHAKE_COMPLETE */ + pkt_length= mpvio.last_read_packet_len; + + DBUG_PRINT ("info", ("OK packet length=%lu", pkt_length)); + if (pkt_length == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "reading authorization packet", + errno); + DBUG_RETURN (1); + } + + if (mysql->net.read_pos[0] == 254) + { + /* The server asked to use a different authentication plugin */ + if (pkt_length == 1) + { + /* old "use short scramble" packet */ + DBUG_PRINT ("info", ("old use short scramble packet from server")); + auth_plugin_name= old_password_plugin_name; + mpvio.cached_server_reply.pkt= (uchar*)mysql->scramble; + mpvio.cached_server_reply.pkt_len= SCRAMBLE_LENGTH + 1; + } + else + { + /* new "use different plugin" packet */ + uint len; + auth_plugin_name= (char*)mysql->net.read_pos + 1; + len= strlen(auth_plugin_name); /* safe as my_net_read always appends \0 */ + mpvio.cached_server_reply.pkt_len= pkt_length - len - 2; + mpvio.cached_server_reply.pkt= mysql->net.read_pos + len + 2; + DBUG_PRINT ("info", ("change plugin packet from server for plugin %s", + auth_plugin_name)); + } + + if (!(auth_plugin= (auth_plugin_t *) mysql_client_find_plugin(mysql, + auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN (1); + + mpvio.plugin= auth_plugin; + res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); + + DBUG_PRINT ("info", ("second authenticate_user returned %s", + res == CR_OK ? "CR_OK" : + res == CR_ERROR ? "CR_ERROR" : + res == CR_OK_HANDSHAKE_COMPLETE ? + "CR_OK_HANDSHAKE_COMPLETE" : "error")); + if (res > CR_OK) + { + if (res > CR_ERROR) + set_mysql_error(mysql, res, unknown_sqlstate); + else + if (!mysql->net.last_errno) + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + DBUG_RETURN (1); + } + + if (res != CR_OK_HANDSHAKE_COMPLETE) + { + /* Read what server thinks about out new auth message report */ + if (cli_safe_read(mysql) == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "reading final connect information", + errno); + DBUG_RETURN (1); + } + } + } + /* + net->read_pos[0] should always be 0 here if the server implements + the protocol correctly + */ + DBUG_RETURN (mysql->net.read_pos[0] != 0); +} MYSQL * STDCALL CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, @@ -2228,7 +2917,10 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, uint port, const char *unix_socket,ulong client_flag) { char buff[NAME_LEN+USERNAME_LENGTH+100]; - char *end,*host_info= NULL; + int scramble_data_len, pkt_scramble_len; + char *end,*host_info= 0, *server_version_end, *pkt_end; + char *scramble_data; + const char *scramble_plugin; ulong pkt_length; NET *net= &mysql->net; #ifdef MYSQL_SERVER @@ -2600,8 +3292,8 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, errno); goto error; } + pkt_end= (char*)net->read_pos + pkt_length; /* Check if version of protocol matches current one */ - mysql->protocol_version= net->read_pos[0]; DBUG_DUMP("packet",(uchar*) net->read_pos,10); DBUG_PRINT("info",("mysql protocol version %d, server=%d", @@ -2613,31 +3305,29 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, PROTOCOL_VERSION); goto error; } - end=strend((char*) net->read_pos+1); + server_version_end= end= strend((char*) net->read_pos+1); mysql->thread_id=uint4korr(end+1); end+=5; /* - Scramble is split into two parts because old clients does not understand + Scramble is split into two parts because old clients do not understand long scrambles; here goes the first part. */ - strmake(mysql->scramble, end, SCRAMBLE_LENGTH_323); - end+= SCRAMBLE_LENGTH_323+1; + scramble_data= end; + scramble_data_len= SCRAMBLE_LENGTH_323 + 1; + scramble_plugin= old_password_plugin_name; + end+= scramble_data_len; - if (pkt_length >= (uint) (end+1 - (char*) net->read_pos)) + if (pkt_end >= end + 1) mysql->server_capabilities=uint2korr(end); - if (pkt_length >= (uint) (end+18 - (char*) net->read_pos)) + if (pkt_end >= end + 18) { /* New protocol with 16 bytes to describe server characteristics */ mysql->server_language=end[2]; mysql->server_status=uint2korr(end+3); + mysql->server_capabilities|= uint2korr(end+5) << 16; + pkt_scramble_len= end[7]; } end+= 18; - if (pkt_length >= (uint) (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 - - (char *) net->read_pos)) - strmake(mysql->scramble+SCRAMBLE_LENGTH_323, end, - SCRAMBLE_LENGTH-SCRAMBLE_LENGTH_323); - else - mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION; if (mysql->options.secure_auth && passwd[0] && !(mysql->server_capabilities & CLIENT_SECURE_CONNECTION)) @@ -2656,7 +3346,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, &mysql->unix_socket,unix_socket ? (uint) strlen(unix_socket)+1 : (uint) 1, &mysql->server_version, - (uint) (end - (char*) net->read_pos), + (uint) (server_version_end - (char*) net->read_pos + 1), NullS) || !(mysql->user=my_strdup(user,MYF(0))) || !(mysql->passwd=my_strdup(passwd,MYF(0)))) @@ -2673,198 +3363,47 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, strmov(mysql->server_version,(char*) net->read_pos+1); mysql->port=port; - /* - Part 2: format and send client info to the server for access check - */ - - client_flag|=mysql->options.client_flag; - client_flag|=CLIENT_CAPABILITIES; - if (client_flag & CLIENT_MULTI_STATEMENTS) - client_flag|= CLIENT_MULTI_RESULTS; - -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - if (mysql->options.ssl_key || mysql->options.ssl_cert || - mysql->options.ssl_ca || mysql->options.ssl_capath || - mysql->options.ssl_cipher) - mysql->options.use_ssl= 1; - if (mysql->options.use_ssl) - client_flag|=CLIENT_SSL; -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ - if (db) - client_flag|=CLIENT_CONNECT_WITH_DB; - - /* Remove options that server doesn't support */ - client_flag= ((client_flag & - ~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41)) | - (client_flag & mysql->server_capabilities)); -#ifndef HAVE_COMPRESS - client_flag&= ~CLIENT_COMPRESS; -#endif - - if (client_flag & CLIENT_PROTOCOL_41) - { - /* 4.1 server and 4.1 client has a 32 byte option flag */ - int4store(buff,client_flag); - int4store(buff+4, net->max_packet_size); - buff[8]= (char) mysql->charset->number; - bzero(buff+9, 32-9); - end= buff+32; - } - else - { - int2store(buff,client_flag); - int3store(buff+2,net->max_packet_size); - end= buff+5; - } - mysql->client_flag=client_flag; - -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - if (client_flag & CLIENT_SSL) + if (pkt_end >= end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1) { - /* Do the SSL layering. */ - struct st_mysql_options *options= &mysql->options; - struct st_VioSSLFd *ssl_fd; - /* - Send client_flag, max_packet_size - unencrypted otherwise - the server does not know we want to do SSL + move the first scramble part - directly in the NET buffer - + to get a full continuous scramble. We've read all the header, + and can overwrite it now. */ - if (my_net_write(net, (uchar*) buff, (uint) (end-buff)) || net_flush(net)) - { - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER(CR_SERVER_LOST_EXTENDED), - "sending connection information to server", - errno); - goto error; - } - - /* Create the VioSSLConnectorFd - init SSL and load certs */ - if (!(ssl_fd= new_VioSSLConnectorFd(options->ssl_key, - options->ssl_cert, - options->ssl_ca, - options->ssl_capath, - options->ssl_cipher))) - { - set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); - goto error; - } - mysql->connector_fd= (void*)ssl_fd; - - /* Connect to the server */ - DBUG_PRINT("info", ("IO layer change in progress...")); - if (sslconnect(ssl_fd, mysql->net.vio, - (long) (mysql->options.connect_timeout))) - { - set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); - goto error; - } - DBUG_PRINT("info", ("IO layer change done!")); - - /* Verify server cert */ - if ((client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && - ssl_verify_server_cert(mysql->net.vio, mysql->host)) - { - set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); - goto error; - } - - } -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ - - DBUG_PRINT("info",("Server version = '%s' capabilites: %lu status: %u client_flag: %lu", - mysql->server_version,mysql->server_capabilities, - mysql->server_status, client_flag)); - /* This needs to be changed as it's not useful with big packets */ - if (user && user[0]) - strmake(end,user,USERNAME_LENGTH); /* Max user name */ - else - read_user_name((char*) end); - - /* We have to handle different version of handshake here */ -#ifdef _CUSTOMCONFIG_ -#include "_cust_libmysql.h" -#endif - DBUG_PRINT("info",("user: %s",end)); - end= strend(end) + 1; - if (passwd[0]) - { - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + memmove(end - SCRAMBLE_LENGTH_323, scramble_data, + SCRAMBLE_LENGTH_323); + scramble_data= end - SCRAMBLE_LENGTH_323; + if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { - *end++= SCRAMBLE_LENGTH; - scramble(end, mysql->scramble, passwd); - end+= SCRAMBLE_LENGTH; + scramble_data_len= pkt_scramble_len; + scramble_plugin= scramble_data + scramble_data_len; + if (scramble_data + scramble_data_len > pkt_end) + scramble_data_len= pkt_end - scramble_data; } else { - scramble_323(end, mysql->scramble, passwd); - end+= SCRAMBLE_LENGTH_323 + 1; + scramble_data_len= pkt_end - scramble_data; + scramble_plugin= native_password_plugin_name; } } else - *end++= '\0'; /* empty password */ + mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION; + + mysql->client_flag= client_flag; - /* Add database if needed */ - if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) - { - end= strmake(end, db, NAME_LEN) + 1; - mysql->db= my_strdup(db,MYF(MY_WME)); - db= 0; - } - /* Write authentication package */ - if (my_net_write(net, (uchar*) buff, (size_t) (end-buff)) || net_flush(net)) - { - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER(CR_SERVER_LOST_EXTENDED), - "sending authentication information", - errno); - goto error; - } - /* - Part 3: Authorization data's been sent. Now server can reply with - OK-packet, or re-request scrambled password. + Part 2: invoke the plugin to send the authentication data to the server */ - if ((pkt_length=cli_safe_read(mysql)) == packet_error) - { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER(CR_SERVER_LOST_EXTENDED), - "reading authorization packet", - errno); + if (run_plugin_auth(mysql, scramble_data, scramble_data_len, + scramble_plugin, db)) goto error; - } - if (pkt_length == 1 && net->read_pos[0] == 254 && - mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - /* - By sending this very specific reply server asks us to send scrambled - password in old format. - */ - scramble_323(buff, mysql->scramble, passwd); - if (my_net_write(net, (uchar*) buff, SCRAMBLE_LENGTH_323 + 1) || - net_flush(net)) - { - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER(CR_SERVER_LOST_EXTENDED), - "sending password information", - errno); - goto error; - } - /* Read what server thinks about out new auth message report */ - if (cli_safe_read(mysql) == packet_error) - { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER(CR_SERVER_LOST_EXTENDED), - "reading final connect information", - errno); - goto error; - } - } + /* + Part 3: authenticated, finish the initialization of the connection + */ - if (client_flag & CLIENT_COMPRESS) /* We will use compression */ + if (mysql->client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress=1; #ifdef CHECK_LICENSE @@ -2872,7 +3411,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, goto error; #endif - if (db && mysql_select_db(mysql, db)) + if (db && !mysql->db && mysql_select_db(mysql, db)) { if (mysql->net.last_errno == CR_SERVER_LOST) set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, @@ -2933,7 +3472,7 @@ error: /* Free alloced memory */ end_server(mysql); mysql_close_free(mysql); - if (!(((ulong) client_flag) & CLIENT_REMEMBER_OPTIONS)) + if (!(client_flag & CLIENT_REMEMBER_OPTIONS)) mysql_close_free_options(mysql); } DBUG_RETURN(0); @@ -3054,6 +3593,12 @@ static void mysql_close_free_options(MYSQL *mysql) if (mysql->options.shared_memory_base_name != def_shared_memory_base_name) my_free(mysql->options.shared_memory_base_name); #endif /* HAVE_SMEM */ + if (mysql->options.extension) + { + my_free(mysql->options.extension->plugin_dir); + my_free(mysql->options.extension->default_auth); + my_free(mysql->options.extension); + } bzero((char*) &mysql->options,sizeof(mysql->options)); DBUG_VOID_RETURN; } @@ -3528,6 +4073,12 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) else mysql->options.client_flag&= ~CLIENT_SSL_VERIFY_SERVER_CERT; break; + case MYSQL_PLUGIN_DIR: + EXTENSION_SET_STRING(&mysql->options, plugin_dir, arg); + break; + case MYSQL_DEFAULT_AUTH: + EXTENSION_SET_STRING(&mysql->options, default_auth, arg); + break; default: DBUG_RETURN(1); } @@ -3631,4 +4182,104 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) return mysql->net.last_errno; } +/** + client authentication plugin that does native MySQL authentication + using a 20-byte (4.1+) scramble +*/ +static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + int pkt_len; + uchar *pkt; + + DBUG_ENTER ("native_password_auth_client"); + + + if (((MCPVIO_EXT *)vio)->mysql_change_user) + { + /* + in mysql_change_user() the client sends the first packet. + we use the old scramble. + */ + pkt= (uchar*)mysql->scramble; + pkt_len= SCRAMBLE_LENGTH + 1; + } + else + { + /* read the scramble */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + if (pkt_len != SCRAMBLE_LENGTH + 1) + DBUG_RETURN (CR_SERVER_HANDSHAKE_ERR); + + /* save it in MYSQL */ + memcpy(mysql->scramble, pkt, SCRAMBLE_LENGTH); + mysql->scramble[SCRAMBLE_LENGTH] = 0; + } + + if (mysql->passwd[0]) + { + char scrambled[SCRAMBLE_LENGTH + 1]; + DBUG_PRINT ("info", ("sending scramble")); + scramble(scrambled, (char*)pkt, mysql->passwd); + if (vio->write_packet(vio, (uchar*)scrambled, SCRAMBLE_LENGTH)) + DBUG_RETURN (CR_ERROR); + } + else + { + DBUG_PRINT ("info", ("no password")); + if (vio->write_packet(vio, 0, 0)) /* no password */ + DBUG_RETURN (CR_ERROR); + } + + DBUG_RETURN (CR_OK); +} + +/** + client authentication plugin that does old MySQL authentication + using an 8-byte (4.0-) scramble +*/ +static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + uchar *pkt; + int pkt_len; + + DBUG_ENTER ("old_password_auth_client"); + + if (((MCPVIO_EXT *)vio)->mysql_change_user) + { + /* + in mysql_change_user() the client sends the first packet. + we use the old scramble. + */ + pkt= (uchar*)mysql->scramble; + pkt_len= SCRAMBLE_LENGTH_323 + 1; + } + else + { + /* read the scramble */ + if ((pkt_len= vio->read_packet(vio, &pkt)) < 0) + return CR_ERROR; + + if (pkt_len != SCRAMBLE_LENGTH_323 + 1 && + pkt_len != SCRAMBLE_LENGTH + 1) + DBUG_RETURN (CR_SERVER_HANDSHAKE_ERR); + /* save it in MYSQL */ + memcpy(mysql->scramble, pkt, pkt_len); + mysql->scramble[pkt_len] = 0; + } + + if (mysql->passwd[0]) + { + char scrambled[SCRAMBLE_LENGTH_323 + 1]; + scramble_323(scrambled, (char*)pkt, mysql->passwd); + if (vio->write_packet(vio, (uchar*)scrambled, SCRAMBLE_LENGTH_323 + 1)) + DBUG_RETURN (CR_ERROR); + } + else + if (vio->write_packet(vio, 0, 0)) /* no password */ + DBUG_RETURN (CR_ERROR); + + DBUG_RETURN (CR_OK); +} diff --git a/sql-common/client_plugin.c b/sql-common/client_plugin.c new file mode 100644 index 00000000000..9526f5fdc4b --- /dev/null +++ b/sql-common/client_plugin.c @@ -0,0 +1,453 @@ +/* Copyright (C) 2010 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + Support code for the client side (libmysql) plugins + + Client plugins are somewhat different from server plugins, they are simpler. + + They do not need to be installed or in any way explicitly loaded on the + client, they are loaded automatically on demand. + One client plugin per shared object, soname *must* match the plugin name. + + There is no reference counting and no unloading either. +*/ + +#include <my_global.h> +#include "mysql.h" +#include <my_sys.h> +#include <m_string.h> +#ifdef THREAD +#include <my_pthread.h> +#else +#include <my_no_pthread.h> +#endif + +#include <sql_common.h> +#include "errmsg.h" +#include <mysql/client_plugin.h> + +struct st_client_plugin_int { + struct st_client_plugin_int *next; + void *dlhandle; + struct st_mysql_client_plugin *plugin; +}; + +static my_bool initialized= 0; +static MEM_ROOT mem_root; + +static const char *plugin_declarations_sym= "_mysql_client_plugin_declaration_"; +static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]= +{ + 0, /* these two are taken by Connector/C */ + 0, /* these two are taken by Connector/C */ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION +}; + +/* + Loaded plugins are stored in a linked list. + The list is append-only, the elements are added to the head (like in a stack). + The elements are added under a mutex, but the list can be read and traversed + without any mutex because once an element is added to the list, it stays + there. The main purpose of a mutex is to prevent two threads from + loading the same plugin twice in parallel. +*/ +struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS]; +#ifdef THREAD +static pthread_mutex_t LOCK_load_client_plugin; +#endif + +static int is_not_initialized(MYSQL *mysql, const char *name) +{ + if (initialized) + return 0; + + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, + unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), + name, "not initialized"); + return 1; +} + +/** + finds a plugin in the list + + @param name plugin name to search for + @param type plugin type + + @note this does NOT necessarily need a mutex, take care! + + @retval a pointer to a found plugin or 0 +*/ +static struct st_mysql_client_plugin * +find_plugin(const char *name, int type) +{ + struct st_client_plugin_int *p; + + DBUG_ASSERT(initialized); + DBUG_ASSERT(type >= 0 && type < MYSQL_CLIENT_MAX_PLUGINS); + if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS) + return 0; + + for (p= plugin_list[type]; p; p= p->next) + { + if (strcmp(p->plugin->name, name) == 0) + return p->plugin; + } + return NULL; +} + +/** + verifies the plugin and adds it to the list + + @param mysql MYSQL structure (for error reporting) + @param plugin plugin to install + @param dlhandle a handle to the shared object (returned by dlopen) + or 0 if the plugin was not dynamically loaded + @param argc number of arguments in the 'va_list args' + @param args arguments passed to the plugin initialization function + + @retval a pointer to an installed plugin or 0 +*/ +static struct st_mysql_client_plugin * +add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle, + int argc, va_list args) +{ + const char *errmsg; + struct st_client_plugin_int plugin_int, *p; + char errbuf[1024]; + + DBUG_ASSERT(initialized); + + plugin_int.plugin= plugin; + plugin_int.dlhandle= dlhandle; + + if (plugin->type >= MYSQL_CLIENT_MAX_PLUGINS) + { + errmsg= "Unknown client plugin type"; + goto err1; + } + + if (plugin->interface_version < plugin_version[plugin->type] || + (plugin->interface_version >> 8) > + (plugin_version[plugin->type] >> 8)) + { + errmsg= "Incompatible client plugin interface"; + goto err1; + } + + /* Call the plugin initialization function, if any */ + if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args)) + { + errmsg= errbuf; + goto err1; + } + + p= (struct st_client_plugin_int *) + memdup_root(&mem_root, &plugin_int, sizeof(plugin_int)); + + if (!p) + { + errmsg= "Out of memory"; + goto err2; + } + + safe_mutex_assert_owner(&LOCK_load_client_plugin); + + p->next= plugin_list[plugin->type]; + plugin_list[plugin->type]= p; + + return plugin; + +err2: + if (plugin->deinit) + plugin->deinit(); +err1: + if (dlhandle) + dlclose(dlhandle); + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name, + errmsg); + return NULL; +} + +/** + Loads plugins which are specified in the environment variable + LIBMYSQL_PLUGINS. + + Multiple plugins must be separated by semicolon. This function doesn't + return or log an error. + + The function is be called by mysql_client_plugin_init + + @todo + Support extended syntax, passing parameters to plugins, for example + LIBMYSQL_PLUGINS="plugin1(param1,param2);plugin2;..." + or + LIBMYSQL_PLUGINS="plugin1=int:param1,str:param2;plugin2;..." +*/ +static void load_env_plugins(MYSQL *mysql) +{ + char *plugs, *free_env, *s= getenv("LIBMYSQL_PLUGINS"); + + /* no plugins to load */ + if(!s) + return; + + free_env= plugs= my_strdup(s, MYF(MY_WME)); + + do { + if ((s= strchr(plugs, ';'))) + *s= '\0'; + mysql_load_plugin(mysql, plugs, -1, 0); + plugs= s + 1; + } while (s); + + my_free(free_env); +} + +/********** extern functions to be used by libmysql *********************/ + +/** + Initializes the client plugin layer. + + This function must be called before any other client plugin function. + + @retval 0 successful + @retval != 0 error occured +*/ +int mysql_client_plugin_init() +{ + MYSQL mysql; + struct st_mysql_client_plugin **builtin; + + if (initialized) + return 0; + + bzero(&mysql, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */ + + pthread_mutex_init(&LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW); + init_alloc_root(&mem_root, 128, 128); + + bzero(&plugin_list, sizeof(plugin_list)); + + initialized= 1; + + pthread_mutex_lock(&LOCK_load_client_plugin); + + for (builtin= mysql_client_builtins; *builtin; builtin++) + add_plugin(&mysql, *builtin, 0, 0, 0); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + + load_env_plugins(&mysql); + + return 0; +} + +/** + Deinitializes the client plugin layer. + + Unloades all client plugins and frees any associated resources. +*/ +void mysql_client_plugin_deinit() +{ + int i; + struct st_client_plugin_int *p; + + if (!initialized) + return; + + for (i=0; i < MYSQL_CLIENT_MAX_PLUGINS; i++) + for (p= plugin_list[i]; p; p= p->next) + { + if (p->plugin->deinit) + p->plugin->deinit(); + if (p->dlhandle) + dlclose(p->dlhandle); + } + + bzero(&plugin_list, sizeof(plugin_list)); + initialized= 0; + free_root(&mem_root, MYF(0)); + pthread_mutex_destroy(&LOCK_load_client_plugin); +} + +/************* public facing functions, for client consumption *********/ + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * +mysql_client_register_plugin(MYSQL *mysql, + struct st_mysql_client_plugin *plugin) +{ + if (is_not_initialized(mysql, plugin->name)) + return NULL; + + pthread_mutex_lock(&LOCK_load_client_plugin); + + /* make sure the plugin wasn't loaded meanwhile */ + if (find_plugin(plugin->name, plugin->type)) + { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, + unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), + plugin->name, "it is already loaded"); + plugin= NULL; + } + else + plugin= add_plugin(mysql, plugin, 0, 0, 0); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + return plugin; +} + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * +mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, + int argc, va_list args) +{ + const char *errmsg; + char dlpath[FN_REFLEN+1]; + void *sym, *dlhandle; + struct st_mysql_client_plugin *plugin; + + DBUG_ENTER ("mysql_load_plugin_v"); + DBUG_PRINT ("entry", ("name=%s type=%d int argc=%d", name, type, argc)); + if (is_not_initialized(mysql, name)) + { + DBUG_PRINT ("leave", ("mysql not initialized")); + DBUG_RETURN (NULL); + } + + pthread_mutex_lock(&LOCK_load_client_plugin); + + /* make sure the plugin wasn't loaded meanwhile */ + if (type >= 0 && find_plugin(name, type)) + { + errmsg= "it is already loaded"; + goto err; + } + + /* Compile dll path */ + strxnmov(dlpath, sizeof(dlpath) - 1, + mysql->options.extension && mysql->options.extension->plugin_dir ? + mysql->options.extension->plugin_dir : PLUGINDIR, "/", + name, SO_EXT, NullS); + + DBUG_PRINT ("info", ("dlopeninig %s", dlpath)); + /* Open new dll handle */ + if (!(dlhandle= dlopen(dlpath, RTLD_NOW))) + { +#if defined(__APPLE__) + /* Apple supports plugins with .so also, so try this as well */ + strxnmov(dlpath, sizeof(dlpath) - 1, + mysql->options.extension && mysql->options.extension->plugin_dir ? + mysql->options.extension->plugin_dir : PLUGINDIR, "/", + name, ".so", NullS); + if ((dlhandle= dlopen(dlpath, RTLD_NOW))) + goto have_plugin; +#endif + DBUG_PRINT ("info", ("failed to dlopen")); + errmsg= dlerror(); + goto err; + } + +#if defined(__APPLE__) +have_plugin: +#endif + if (!(sym= dlsym(dlhandle, plugin_declarations_sym))) + { + errmsg= "not a plugin"; + dlclose(dlhandle); + goto err; + } + + plugin= (struct st_mysql_client_plugin*)sym; + + if (type >=0 && type != plugin->type) + { + errmsg= "type mismatch"; + goto err; + } + + if (strcmp(name, plugin->name)) + { + errmsg= "name mismatch"; + goto err; + } + + if (type < 0 && find_plugin(name, plugin->type)) + { + errmsg= "it is already loaded"; + goto err; + } + + plugin= add_plugin(mysql, plugin, dlhandle, argc, args); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + + DBUG_PRINT ("leave", ("plugin loaded ok")); + DBUG_RETURN (plugin); + +err: + pthread_mutex_unlock(&LOCK_load_client_plugin); + DBUG_PRINT ("leave", ("plugin load error : %s", errmsg)); + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg); + DBUG_RETURN (NULL); +} + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * +mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...) +{ + struct st_mysql_client_plugin *p; + va_list args; + va_start(args, argc); + p= mysql_load_plugin_v(mysql, name, type, argc, args); + va_end(args); + return p; +} + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * +mysql_client_find_plugin(MYSQL *mysql, const char *name, int type) +{ + struct st_mysql_client_plugin *p; + + DBUG_ENTER ("mysql_client_find_plugin"); + DBUG_PRINT ("entry", ("name=%s, type=%d", name, type)); + if (is_not_initialized(mysql, name)) + DBUG_RETURN (NULL); + + if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS) + { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, + "invalid type"); + } + + if ((p= find_plugin(name, type))) + { + DBUG_PRINT ("leave", ("found %p", p)); + DBUG_RETURN (p); + } + + /* not found, load it */ + p= mysql_load_plugin(mysql, name, type, 0); + DBUG_PRINT ("leave", ("loaded %p", p)); + DBUG_RETURN (p); +} + diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index e8a594c4d8b..d290ad79e04 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -52,6 +52,7 @@ SET (SQL_SOURCE log_event_old.cc rpl_record_old.cc message.h mf_iocache.cc my_decimal.cc ../sql-common/my_time.c mysqld.cc net_serv.cc keycaches.cc + ../sql-common/client_plugin.c opt_range.cc opt_range.h opt_sum.cc ../sql-common/pack.c parse_file.cc password.c procedure.cc protocol.cc records.cc repl_failsafe.cc rpl_filter.cc set_var.cc diff --git a/sql/Makefile.am b/sql/Makefile.am index 7fed55f3cd6..b31a6e3c3e0 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -173,7 +173,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ rpl_handler.cc mdl.cc transaction.cc sql_audit.cc \ sha2.cc -nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c +nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c client_plugin.c libndb_la_CPPFLAGS= @ndbcluster_includes@ libndb_la_SOURCES= ha_ndbcluster.cc \ @@ -187,10 +187,10 @@ mysql_tzinfo_to_sql_SOURCES = tztime.cc mysql_tzinfo_to_sql_CXXFLAGS= -DTZINFO2SQL DEFS = -DMYSQL_SERVER \ - -DDEFAULT_MYSQL_HOME="\"$(MYSQLBASEdir)\"" \ - -DMYSQL_DATADIR="\"$(MYSQLDATAdir)\"" \ - -DSHAREDIR="\"$(MYSQLSHAREdir)\"" \ - -DPLUGINDIR="\"$(pkgplugindir)\"" \ + -DDEFAULT_MYSQL_HOME='"$(MYSQLBASEdir)"' \ + -DMYSQL_DATADIR='"$(MYSQLDATAdir)"' \ + -DSHAREDIR='"$(MYSQLSHAREdir)"' \ + -DPLUGINDIR='"$(pkgplugindir)"' \ -DHAVE_EVENT_SCHEDULER \ @DEFS@ @@ -214,6 +214,8 @@ link_sources: @LN_CP_F@ $(top_srcdir)/sql-common/pack.c pack.c rm -f client.c @LN_CP_F@ $(top_srcdir)/sql-common/client.c client.c + rm -f client_plugin.c + @LN_CP_F@ $(top_srcdir)/sql-common/client_plugin.c client_plugin.c rm -f my_time.c @LN_CP_F@ $(top_srcdir)/sql-common/my_time.c my_time.c rm -f my_user.c diff --git a/sql/client_settings.h b/sql/client_settings.h index 7d103d5904d..ff35cff2440 100644 --- a/sql/client_settings.h +++ b/sql/client_settings.h @@ -21,6 +21,7 @@ #endif /* CLIENT_SETTINGS_INCLUDED */ #include <thr_alarm.h> +#include <sql_common.h> #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | \ CLIENT_SECURE_CONNECTION | CLIENT_TRANSACTIONS | \ @@ -33,7 +34,8 @@ #undef HAVE_SMEM #undef _CUSTOMCONFIG_ -#define mysql_server_init(a,b,c) 0 +#define mysql_server_init(a,b,c) mysql_client_plugin_init() +#define mysql_server_end() mysql_client_plugin_deinit() #ifdef HAVE_REPLICATION C_MODE_START diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index d4a98265c49..9d394d740a8 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -9516,7 +9516,7 @@ pthread_handler_t ndb_util_thread_func(void *arg __attribute__((unused))) thd->client_capabilities = 0; my_net_init(&thd->net, 0); thd->main_security_ctx.master_access= ~0; - thd->main_security_ctx.priv_user = 0; + thd->main_security_ctx.priv_user[0] = 0; /* Do not use user-supplied timeout value for system threads. */ thd->variables.lock_wait_timeout= LONG_TIMEOUT; diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 26fdb8e1425..1f3b707be91 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -3681,7 +3681,7 @@ pthread_handler_t ndb_binlog_thread_func(void *arg) thd->client_capabilities= 0; my_net_init(&thd->net, 0); thd->main_security_ctx.master_access= ~0; - thd->main_security_ctx.priv_user= 0; + thd->main_security_ctx.priv_user[0]= 0; /* Do not use user-supplied timeout value for system threads. */ thd->variables.lock_wait_timeout= LONG_TIMEOUT; diff --git a/sql/lex.h b/sql/lex.h index fbedddc6941..6d5d711eb60 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -420,8 +420,9 @@ static SYMBOL symbols[] = { { "PROCEDURE", SYM(PROCEDURE_SYM)}, { "PROCESS" , SYM(PROCESS)}, { "PROCESSLIST", SYM(PROCESSLIST_SYM)}, - { "PROFILE", SYM(PROFILE_SYM)}, - { "PROFILES", SYM(PROFILES_SYM)}, + { "PROFILE", SYM(PROFILE_SYM)}, + { "PROFILES", SYM(PROFILES_SYM)}, + { "PROXY", SYM(PROXY_SYM)}, { "PURGE", SYM(PURGE)}, { "QUARTER", SYM(QUARTER_SYM)}, { "QUERY", SYM(QUERY_SYM)}, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index afc515a9d8c..5412a424c9c 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -58,6 +58,7 @@ #include "sql_repl.h" #include "rpl_filter.h" #include "repl_failsafe.h" +#include <sql_common.h> #include <my_stacktrace.h> #include "mysqld_suffix.h" #include "mysys_err.h" @@ -1479,6 +1480,7 @@ void clean_up(bool print_message) sql_print_information(ER_DEFAULT(ER_SHUTDOWN_COMPLETE),my_progname); cleanup_errmsgs(); thread_scheduler.end(); + mysql_client_plugin_deinit(); finish_client_errs(); DBUG_PRINT("quit", ("Error messages freed")); /* Tell main we are ready */ @@ -3335,6 +3337,7 @@ static int init_common_variables() if (init_errmessage()) /* Read error messages from file */ return 1; init_client_errs(); + mysql_client_plugin_init(); lex_init(); if (item_create_init()) return 1; diff --git a/sql/password.c b/sql/password.c index b77cb618a46..3b69705cc87 100644 --- a/sql/password.c +++ b/sql/password.c @@ -223,13 +223,13 @@ void scramble_323(char *to, const char *message, const char *password) */ my_bool -check_scramble_323(const char *scrambled, const char *message, +check_scramble_323(const unsigned char *scrambled, const char *message, ulong *hash_pass) { struct rand_struct rand_st; ulong hash_message[2]; - char buff[16],*to,extra; /* Big enough for check */ - const char *pos; + uchar buff[16],*to,extra; /* Big enough for check */ + const uchar *pos; hash_password(hash_message, message, SCRAMBLE_LENGTH_323); randominit(&rand_st,hash_pass[0] ^ hash_message[0], @@ -244,7 +244,7 @@ check_scramble_323(const char *scrambled, const char *message, to=buff; while (*scrambled) { - if (*scrambled++ != (char) (*to++ ^ extra)) + if (*scrambled++ != (uchar) (*to++ ^ extra)) return 1; /* Wrong password */ } return 0; @@ -510,7 +510,7 @@ scramble(char *to, const char *message, const char *password) */ my_bool -check_scramble(const char *scramble_arg, const char *message, +check_scramble(const uchar *scramble_arg, const char *message, const uint8 *hash_stage2) { SHA1_CONTEXT sha1_context; @@ -523,7 +523,7 @@ check_scramble(const char *scramble_arg, const char *message, mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); mysql_sha1_result(&sha1_context, buf); /* encrypt scramble */ - my_crypt((char *) buf, buf, (const uchar *) scramble_arg, SCRAMBLE_LENGTH); + my_crypt((char *) buf, buf, scramble_arg, SCRAMBLE_LENGTH); /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */ mysql_sha1_reset(&sha1_context); mysql_sha1_input(&sha1_context, buf, SHA1_HASH_SIZE); diff --git a/sql/protocol.cc b/sql/protocol.cc index 953656d3a4f..dd3a5d92a87 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -349,24 +349,6 @@ static bool write_eof_packet(THD *thd, NET *net, } /** - Please client to send scrambled_password in old format. - - @param thd thread handle - - @retval - 0 ok - @retval - !0 error -*/ - -bool send_old_password_request(THD *thd) -{ - NET *net= &thd->net; - return my_net_write(net, eof_buff, 1) || net_flush(net); -} - - -/** @param thd Thread handler @param sql_errno The error code to send @param err A pointer to the error message diff --git a/sql/protocol.h b/sql/protocol.h index f661c7663e5..1c86c6d6c49 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -204,7 +204,6 @@ public: void send_warning(THD *thd, uint sql_errno, const char *err=0); bool net_send_error(THD *thd, uint sql_errno, const char *err, const char* sqlstate); -bool send_old_password_request(THD *thd); uchar *net_store_data(uchar *to,const uchar *from, size_t length); uchar *net_store_data(uchar *to,int32 from); uchar *net_store_data(uchar *to,longlong from); diff --git a/sql/set_var.cc b/sql/set_var.cc index 9daaf883ea8..c35a2f33a32 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -735,9 +735,9 @@ int set_var_password::check(THD *thd) } if (!user->user.str) { - DBUG_ASSERT(thd->security_ctx->priv_user); - user->user.str= (char *) thd->security_ctx->priv_user; - user->user.length= strlen(thd->security_ctx->priv_user); + DBUG_ASSERT(thd->security_ctx->user); + user->user.str= (char *) thd->security_ctx->user; + user->user.length= strlen(thd->security_ctx->user); } /* Returns 1 as the function sends error to client */ return check_change_password(thd, user->host.str, user->user.str, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 9e7fdbfeae5..f3a4ffbece9 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6346,3 +6346,32 @@ ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN ER_FAILED_READ_FROM_PAR_FILE eng "Failed to read from the .par file" swe "Misslyckades läsa frÃ¥n .par filen" + +ER_ACCESS_DENIED_NO_PASSWORD_ERROR 28000 + cze "P-BÅ™Ãstup pro uživatele '%-.48s'@'%-.64s'" + dan "Adgang nægtet bruger: '%-.48s'@'%-.64s'" + nla "Toegang geweigerd voor gebruiker: '%-.48s'@'%-.64s'" + eng "Access denied for user '%-.48s'@'%-.64s'" + est "Ligipääs keelatud kasutajale '%-.48s'@'%-.64s'" + fre "Accès refusé pour l'utilisateur: '%-.48s'@'@%-.64s'" + ger "Benutzer '%-.48s'@'%-.64s' hat keine Zugriffsberechtigung" + greek "Δεν επιτÎÏεται η Ï€Ïόσβαση στο χÏήστη: '%-.48s'@'%-.64s'" + hun "A(z) '%-.48s'@'%-.64s' felhasznalo szamara tiltott eleres." + ita "Accesso non consentito per l'utente: '%-.48s'@'%-.64s'" + kor "'%-.48s'@'%-.64s' 사용ìžëŠ” ì ‘ê·¼ì´ ê±°ë¶€ ë˜ì—ˆìŠµë‹ˆë‹¤." + nor "Tilgang nektet for bruker: '%-.48s'@'%-.64s'" + norwegian-ny "Tilgang ikke tillate for brukar: '%-.48s'@'%-.64s'" + por "Acesso negado para o usuário '%-.48s'@'%-.64s'" + rum "Acces interzis pentru utilizatorul: '%-.48s'@'%-.64s'" + rus "ДоÑтуп закрыт Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s'@'%-.64s'" + serbian "Pristup je zabranjen korisniku '%-.48s'@'%-.64s'" + slo "Zakázaný prÃstup pre užÃvateľa: '%-.48s'@'%-.64s'" + spa "Acceso negado para usuario: '%-.48s'@'%-.64s'" + swe "Användare '%-.48s'@'%-.64s' är ej berättigad att logga in" + ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%-.48s'@'%-.64s'" + +ER_SET_PASSWORD_AUTH_PLUGIN + eng "SET PASSWORD has no significance for users authenticating via plugins" + +ER_GRANT_PLUGIN_USER_EXISTS + eng "GRANT with IDENTIFIED WITH is illegal because the user %-.*s already exists" diff --git a/sql/sp_head.h b/sql/sp_head.h index b2446c8f680..e72ce6455f6 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -42,6 +42,7 @@ #define TYPE_ENUM_FUNCTION 1 #define TYPE_ENUM_PROCEDURE 2 #define TYPE_ENUM_TRIGGER 3 +#define TYPE_ENUM_PROXY 4 Item_result sp_map_result_type(enum enum_field_types type); diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 19373507955..b5433db5659 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -44,6 +44,11 @@ #include "transaction.h" #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #include "records.h" // init_read_record, end_read_record +#include <sql_common.h> +#include <mysql/plugin_auth.h> +#include "sql_connect.h" +#include "hostname.h" +#include "sql_db.h" bool mysql_user_table_is_in_short_password_format= false; @@ -164,7 +169,317 @@ TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { const TABLE_FIELD_DEF mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields}; +static LEX_STRING native_password_plugin_name= { + C_STRING_WITH_LEN("mysql_native_password") +}; + +static LEX_STRING old_password_plugin_name= { + C_STRING_WITH_LEN("mysql_old_password") +}; + +/// @todo make it configurable +LEX_STRING *default_auth_plugin_name= &native_password_plugin_name; + #ifndef NO_EMBEDDED_ACCESS_CHECKS +static plugin_ref old_password_plugin; +#endif +static plugin_ref native_password_plugin; + +/* Classes */ + +struct acl_host_and_ip +{ + char *hostname; + long ip,ip_mask; // Used with masked ip:s +}; + +class ACL_ACCESS { +public: + ulong sort; + ulong access; +}; + +/* ACL_HOST is used if no host is specified */ + +class ACL_HOST :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + char *db; +}; + +class ACL_USER :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + uint hostname_length; + USER_RESOURCES user_resource; + char *user; + uint8 salt[SCRAMBLE_LENGTH+1]; // scrambled password in binary form + uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 + enum SSL_type ssl_type; + const char *ssl_cipher, *x509_issuer, *x509_subject; + LEX_STRING plugin; + LEX_STRING auth_string; + + ACL_USER *copy(MEM_ROOT *root) + { + ACL_USER *dst= (ACL_USER *)alloc_root(root, sizeof(ACL_USER)); + if (!dst) + return 0; + *dst= *this; + dst->user= safe_strdup_root(root, user); + dst->ssl_cipher= safe_strdup_root(root, ssl_cipher); + dst->x509_issuer= safe_strdup_root(root, x509_issuer); + dst->x509_subject= safe_strdup_root(root, x509_subject); + if (plugin.str == native_password_plugin_name.str || + plugin.str == old_password_plugin_name.str) + dst->plugin= plugin; + else + dst->plugin.str= strmake_root(root, plugin.str, plugin.length); + dst->auth_string.str = safe_strdup_root(root, auth_string.str); + dst->host.hostname= safe_strdup_root(root, host.hostname); + return dst; + } +}; + +class ACL_DB :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + char *user,*db; +}; + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static void update_hostname(acl_host_and_ip *host, const char *hostname); +static ulong get_sort(uint count,...); +static bool compare_hostname(const acl_host_and_ip *host,const char *hostname, + const char *ip); +static bool show_proxy_grants (THD *thd, LEX_USER *user, + char *buff, size_t buffsize); + +class ACL_PROXY_USER :public ACL_ACCESS +{ + acl_host_and_ip host; + const char *user; + acl_host_and_ip proxied_host; + const char *proxied_user; + bool with_grant; + + typedef enum { + MYSQL_PROXY_PRIV_HOST, + MYSQL_PROXY_PRIV_USER, + MYSQL_PROXY_PRIV_PROXIED_HOST, + MYSQL_PROXY_PRIV_PROXIED_USER, + MYSQL_PROXY_PRIV_WITH_GRANT } old_acl_proxy_users; +public: + ACL_PROXY_USER () {}; + + void init(const char *host_arg, const char *user_arg, + const char *proxied_host_arg, const char *proxied_user_arg, + bool with_grant_arg) + { + user= (user_arg && *user_arg) ? user_arg : NULL; + update_hostname (&host, + (host_arg && *host_arg) ? host_arg : NULL); + proxied_user= (proxied_user_arg && *proxied_user_arg) ? + proxied_user_arg : NULL; + update_hostname (&proxied_host, + (proxied_host_arg && *proxied_host_arg) ? + proxied_host_arg : NULL); + with_grant= with_grant_arg; + sort= get_sort (4, host.hostname, user, + proxied_host.hostname, proxied_user); + } + + void init(MEM_ROOT *mem, const char *host_arg, const char *user_arg, + const char *proxied_host_arg, const char *proxied_user_arg, + bool with_grant_arg) + { + init ((host_arg && *host_arg) ? strdup_root (mem, host_arg) : NULL, + (user_arg && *user_arg) ? strdup_root (mem, user_arg) : NULL, + (proxied_host_arg && *proxied_host_arg) ? + strdup_root (mem, proxied_host_arg) : NULL, + (proxied_user_arg && *proxied_user_arg) ? + strdup_root (mem, proxied_user_arg) : NULL, + with_grant_arg); + } + + void init(TABLE *table, MEM_ROOT *mem) + { + init (get_field(mem, table->field[MYSQL_PROXY_PRIV_HOST]), + get_field(mem, table->field[MYSQL_PROXY_PRIV_USER]), + get_field(mem, table->field[MYSQL_PROXY_PRIV_PROXIED_HOST]), + get_field(mem, table->field[MYSQL_PROXY_PRIV_PROXIED_USER]), + table->field[MYSQL_PROXY_PRIV_WITH_GRANT]->val_int() != 0); + } + + bool get_with_grant() { return with_grant; } + const char *get_user() { return user; } + const char *get_host() { return host.hostname; } + void set_user(MEM_ROOT *mem, const char *user_arg) + { + user= user_arg && *user_arg ? strdup_root (mem, user_arg) : NULL; + } + void set_host(MEM_ROOT *mem, const char *host_arg) + { + update_hostname (&host, + (host_arg && *host_arg) ? + strdup_root (mem, host_arg) : NULL); + } + + bool check_validity (bool check_no_resolve) + { + if (check_no_resolve && + (hostname_requires_resolving(host.hostname) || + hostname_requires_resolving(proxied_host.hostname))) + { + sql_print_warning ("'proxy_priv' entry '%s@%s %s@%s' " + "ignored in --skip-name-resolve mode.", + proxied_user ? proxied_user : "", + proxied_host.hostname ? proxied_host.hostname : "", + user ? user : "", + host.hostname ? host.hostname : ""); + return TRUE; + } + return FALSE; + } + + bool matches (const char *host_arg, const char *user_arg, const char *ip_arg, + const char *proxied_user_arg) + { + DBUG_ENTER ("ACL_PROXY_USER::matches"); + DBUG_PRINT ("info", ("compare_hostname(%s,%s,%s) &&" + "compare_hostname(%s,%s,%s) &&" + "wild_compare (%s,%s) &&" + "wild_compare (%s,%s)", + host.hostname ? host.hostname : "<NULL>", + host_arg ? host_arg : "<NULL>", + ip_arg ? ip_arg : "<NULL>", + proxied_host.hostname ? proxied_host.hostname : "<NULL>", + host_arg ? host_arg : "<NULL>", + ip_arg ? ip_arg : "<NULL>", + user_arg ? user_arg : "<NULL>", + user ? user : "<NULL>", + proxied_user_arg ? proxied_user_arg : "<NULL>", + proxied_user ? proxied_user : "<NULL>")); + DBUG_RETURN (compare_hostname (&host, host_arg, ip_arg) && + compare_hostname (&proxied_host, host_arg, ip_arg) && + (!user || + (user_arg && !wild_compare (user_arg, user, TRUE))) && + (!proxied_user || + (proxied_user && !wild_compare (proxied_user_arg, + proxied_user, TRUE)))); + } + + bool pk_equals (ACL_PROXY_USER *grant) + { + DBUG_ENTER ("pk_equals"); + DBUG_PRINT ("info", ("strcmp(%s,%s) &&" + "strcmp(%s,%s) &&" + "wild_compare (%s,%s) &&" + "wild_compare (%s,%s)", + user ? user : "<NULL>", + grant->user ? grant->user : "<NULL>", + proxied_user ? proxied_user : "<NULL>", + grant->proxied_user ? grant->proxied_user : "<NULL>", + host.hostname ? host.hostname : "<NULL>", + grant->host.hostname ? grant->host.hostname : "<NULL>", + proxied_host.hostname ? proxied_host.hostname : "<NULL>", + grant->proxied_host.hostname ? + grant->proxied_host.hostname : "<NULL>")); + DBUG_RETURN(((!user && !grant->user) || !strcmp (user, grant->user)) && + ((!proxied_user && !grant->proxied_user) || + !strcmp (proxied_user, grant->proxied_user)) && + ((!host.hostname && !grant->host.hostname) || + !strcmp (host.hostname, grant->host.hostname)) && + ((!proxied_host.hostname && !grant->proxied_host.hostname) || + !strcmp (proxied_host.hostname, grant->proxied_host.hostname))); + } + + bool granted_on (const char *host_arg, const char *user_arg) + { + return (((!user && (!user_arg || !user_arg[0])) || + (user && user_arg && !strcmp (user, user_arg))) && + ((!host.hostname && (!host_arg || !host_arg[0])) || + (host.hostname && host_arg && !strcmp (host.hostname, host_arg)))); + } + + void print_grant (String *str) + { + str->append(STRING_WITH_LEN("GRANT PROXY ON '")); + if (proxied_user) + str->append(proxied_user, strlen(proxied_user)); + str->append(STRING_WITH_LEN("'@'")); + if (proxied_host.hostname) + str->append(proxied_host.hostname, strlen(proxied_host.hostname)); + str->append(STRING_WITH_LEN("' TO '")); + if (user) + str->append(user, strlen(user)); + str->append(STRING_WITH_LEN("'@'")); + if (host.hostname) + str->append(host.hostname, strlen(host.hostname)); + str->append(STRING_WITH_LEN("'")); + if (with_grant) + str->append(STRING_WITH_LEN(" WITH GRANT OPTION")); + } + + void set_data (ACL_PROXY_USER *grant) + { + with_grant= grant->with_grant; + } + + static int store_pk (TABLE *table, + const LEX_STRING *host, + const LEX_STRING *user, + const LEX_STRING *proxied_host, + const LEX_STRING *proxied_user) + { + DBUG_ENTER ("ACL_PROXY_USER::store_pk"); + DBUG_PRINT ("info", ("host=%s, user=%s, proxied_host=%s, proxied_user=%s", + host->str ? host->str : "<NULL>", + user->str ? user->str : "<NULL>", + proxied_host->str ? proxied_host->str : "<NULL>", + proxied_user->str ? proxied_user->str : "<NULL>")); + if (table->field[MYSQL_PROXY_PRIV_HOST]->store(host->str, + host->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXY_PRIV_USER]->store(user->str, + user->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXY_PRIV_PROXIED_HOST]->store(proxied_host->str, + proxied_host->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXY_PRIV_PROXIED_USER]->store(proxied_user->str, + proxied_user->length, + system_charset_info)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(FALSE); + } + + static int store_data_record (TABLE *table, + const LEX_STRING *host, + const LEX_STRING *user, + const LEX_STRING *proxied_host, + const LEX_STRING *proxied_user, + bool with_grant) + { + DBUG_ENTER ("ACL_PROXY_USER::store_pk"); + if (store_pk (table, host, user, proxied_host, proxied_user)) + DBUG_RETURN(TRUE); + DBUG_PRINT ("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE")); + if (table->field[MYSQL_PROXY_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, + TRUE)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(FALSE); + } +}; #define FIRST_NON_YN_FIELD 26 @@ -187,7 +502,25 @@ static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, #define IP_ADDR_STRLEN (3+1+3+1+3+1+3) #define ACL_KEY_LENGTH (IP_ADDR_STRLEN+1+NAME_LEN+1+USERNAME_LENGTH+1) -static DYNAMIC_ARRAY acl_hosts,acl_users,acl_dbs; +#if defined(HAVE_OPENSSL) +/* + Without SSL the handshake consists of one packet. This packet + has both client capabilites and scrambled password. + With SSL the handshake might consist of two packets. If the first + packet (client capabilities) has CLIENT_SSL flag set, we have to + switch to SSL and read the second packet. The scrambled password + is in the second packet and client_capabilites field will be ignored. + Maybe it is better to accept flags other than CLIENT_SSL from the + second packet? +*/ +#define SSL_HANDSHAKE_SIZE 2 +#define NORMAL_HANDSHAKE_SIZE 6 +#define MIN_HANDSHAKE_SIZE 2 +#else +#define MIN_HANDSHAKE_SIZE 6 +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + +static DYNAMIC_ARRAY acl_hosts,acl_users,acl_dbs,acl_proxy_users; static MEM_ROOT mem, memex; static bool initialized=0; static bool allow_all_hosts=1; @@ -205,9 +538,6 @@ static ACL_USER *find_acl_user(const char *host, const char *user, static bool update_user_table(THD *thd, TABLE *table, const char *host, const char *user, const char *new_password, uint new_password_len); -static void update_hostname(acl_host_and_ip *host, const char *hostname); -static bool compare_hostname(const acl_host_and_ip *host,const char *hostname, - const char *ip); static my_bool acl_load(THD *thd, TABLE_LIST *tables); static my_bool grant_load(THD *thd, TABLE_LIST *tables); static inline void get_grantor(THD *thd, char* grantor); @@ -263,6 +593,19 @@ my_bool acl_init(bool dont_read_acl_tables) (my_hash_get_key) acl_entry_get_key, (my_hash_free_key) free, &my_charset_utf8_bin); + + /* + cache built-in native authentication plugins, + to avoid hash searches and a global mutex lock on every connect + */ + native_password_plugin= my_plugin_lock_by_name(0, + &native_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); + old_password_plugin= my_plugin_lock_by_name(0, + &old_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); + + if (!native_password_plugin || !old_password_plugin) + DBUG_RETURN(1); + if (dont_read_acl_tables) { DBUG_RETURN(0); /* purecov: tested */ @@ -287,6 +630,37 @@ my_bool acl_init(bool dont_read_acl_tables) DBUG_RETURN(return_val); } +/** + Choose from either native or old password plugins when assigning a password +*/ + +static bool +set_user_plugin (ACL_USER *user, int password_len) +{ + switch (password_len) + { + case 0: /* no password */ + case SCRAMBLED_PASSWORD_CHAR_LENGTH: + user->plugin= native_password_plugin_name; + return FALSE; + case SCRAMBLED_PASSWORD_CHAR_LENGTH_323: + user->plugin= old_password_plugin_name; + return FALSE; + case 45: /* 4.1: to be removed */ + sql_print_warning("Found 4.1.0 style password for user '%s@%s'. " + "Ignoring user. " + "You should change password for this user.", + user->user ? user->user : "", + user->host.hostname ? user->host.hostname : ""); + return TRUE; + default: + sql_print_warning("Found invalid password for user: '%s@%s'; " + "Ignoring user", user->user ? user->user : "", + user->host.hostname ? user->host.hostname : ""); + return TRUE; + } +} + /* Initialize structures responsible for user/db-level privilege checking @@ -419,6 +793,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) while (!(read_record_info.read_record(&read_record_info))) { ACL_USER user; + bzero(&user, sizeof(user)); update_hostname(&user.host, get_field(&mem, table->field[0])); user.user= get_field(&mem, table->field[1]); if (check_no_resolve && hostname_requires_resolving(user.host.hostname)) @@ -430,27 +805,15 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) continue; } - const char *password= get_field(thd->mem_root, table->field[2]); + char *password= get_field(&mem, table->field[2]); uint password_len= password ? strlen(password) : 0; + user.auth_string.str= password ? password : const_cast<char*>(""); + user.auth_string.length= password_len; set_user_salt(&user, password, password_len); - if (user.salt_len == 0 && password_len != 0) - { - switch (password_len) { - case 45: /* 4.1: to be removed */ - sql_print_warning("Found 4.1 style password for user '%s@%s'. " - "Ignoring user. " - "You should change password for this user.", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); - break; - default: - sql_print_warning("Found invalid password for user: '%s@%s'; " - "Ignoring user", user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); - break; - } - } - else // password is correct + + if (set_user_plugin(&user, password_len)) + continue; + { uint next_field; user.access= get_access(table,3,&next_field) & GLOBAL_ACLS; @@ -527,13 +890,43 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) ptr= get_field(thd->mem_root, table->field[next_field++]); user.user_resource.user_conn= ptr ? atoi(ptr) : 0; } - else - user.user_resource.user_conn= 0; + + if (table->s->fields >= 41) + { + /* We may have plugin & auth_String fields */ + char *tmpstr= get_field(&mem, table->field[next_field++]); + if (tmpstr) + { + if (user.auth_string.length) + { + sql_print_warning("'user' entry '%s@%s' has both a password " + "and an authentication plugin specified. The " + "password will be ignored.", + user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); + } + if (my_strcasecmp(system_charset_info, tmpstr, + native_password_plugin_name.str) == 0) + user.plugin= native_password_plugin_name; + else + if (my_strcasecmp(system_charset_info, tmpstr, + old_password_plugin_name.str) == 0) + user.plugin= old_password_plugin_name; + else + { + user.plugin.str= tmpstr; + user.plugin.length= strlen(tmpstr); + } + user.auth_string.str= get_field(&mem, table->field[next_field++]); + if (!user.auth_string.str) + user.auth_string.str= const_cast<char*>(""); + user.auth_string.length= strlen(user.auth_string.str); + } + } } else { user.ssl_type=SSL_TYPE_NONE; - bzero((char *)&(user.user_resource),sizeof(user.user_resource)); #ifndef TO_BE_REMOVED if (table->s->fields <= 13) { // Without grant @@ -617,6 +1010,25 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) sizeof(ACL_DB),(qsort_cmp) acl_compare); end_read_record(&read_record_info); freeze_size(&acl_dbs); + + init_read_record(&read_record_info,thd,table=tables[3].table,NULL,1,0,FALSE); + table->use_all_columns(); + (void) my_init_dynamic_array(&acl_proxy_users,sizeof(ACL_PROXY_USER),50,100); + while (!(read_record_info.read_record(&read_record_info))) + { + ACL_PROXY_USER proxy; + proxy.init(table, &mem); + if (proxy.check_validity(check_no_resolve)) + continue; + if (push_dynamic(&acl_proxy_users,(uchar*) &proxy)) + return TRUE; + } + my_qsort((uchar*) dynamic_element(&acl_proxy_users,0,ACL_PROXY_USER*), + acl_proxy_users.elements, + sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); + end_read_record(&read_record_info); + freeze_size(&acl_proxy_users); + init_check_host(); initialized=1; @@ -635,7 +1047,10 @@ void acl_free(bool end) delete_dynamic(&acl_users); delete_dynamic(&acl_dbs); delete_dynamic(&acl_wild_hosts); + delete_dynamic(&acl_proxy_users); my_hash_free(&acl_check_hosts); + plugin_unlock(0, native_password_plugin); + plugin_unlock(0, old_password_plugin); if (!end) acl_cache->clear(1); /* purecov: inspected */ else @@ -667,8 +1082,8 @@ void acl_free(bool end) my_bool acl_reload(THD *thd) { - TABLE_LIST tables[3]; - DYNAMIC_ARRAY old_acl_hosts,old_acl_users,old_acl_dbs; + TABLE_LIST tables[4]; + DYNAMIC_ARRAY old_acl_hosts,old_acl_users,old_acl_dbs,old_acl_proxy_users; MEM_ROOT old_mem; bool old_initialized; my_bool return_val= TRUE; @@ -684,9 +1099,14 @@ my_bool acl_reload(THD *thd) C_STRING_WITH_LEN("user"), "user", TL_READ); tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("db"), "db", TL_READ); + tables[3].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxy_priv"), + "proxy_priv", TL_READ); tables[0].next_local= tables[0].next_global= tables+1; tables[1].next_local= tables[1].next_global= tables+2; - tables[0].open_type= tables[1].open_type= tables[2].open_type= OT_BASE_ONLY; + tables[2].next_local= tables[2].next_global= tables+3; + tables[0].open_type= tables[1].open_type= tables[2].open_type= + tables[3].open_type= OT_BASE_ONLY; if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { @@ -705,6 +1125,7 @@ my_bool acl_reload(THD *thd) old_acl_hosts=acl_hosts; old_acl_users=acl_users; + old_acl_proxy_users=acl_proxy_users; old_acl_dbs=acl_dbs; old_mem=mem; delete_dynamic(&acl_wild_hosts); @@ -716,6 +1137,7 @@ my_bool acl_reload(THD *thd) acl_free(); /* purecov: inspected */ acl_hosts=old_acl_hosts; acl_users=old_acl_users; + acl_proxy_users=old_acl_proxy_users; acl_dbs=old_acl_dbs; mem=old_mem; init_check_host(); @@ -725,6 +1147,7 @@ my_bool acl_reload(THD *thd) free_root(&old_mem,MYF(0)); delete_dynamic(&old_acl_hosts); delete_dynamic(&old_acl_users); + delete_dynamic(&old_acl_proxy_users); delete_dynamic(&old_acl_dbs); } if (old_initialized) @@ -830,246 +1253,10 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) /* - Seek ACL entry for a user, check password, SSL cypher, and if - everything is OK, update THD user data and USER_RESOURCES struct. - - IMPLEMENTATION - This function does not check if the user has any sensible privileges: - only user's existence and validity is checked. - Note, that entire operation is protected by acl_cache_lock. + Gets user credentials without authentication and resource limit checks. SYNOPSIS acl_getroot() - thd thread handle. If all checks are OK, - thd->security_ctx->priv_user/master_access are updated. - thd->security_ctx->host/ip/user are used for checks. - mqh user resources; on success mqh is reset, else - unchanged - passwd scrambled & crypted password, received from client - (to check): thd->scramble or thd->scramble_323 is - used to decrypt passwd, so they must contain - original random string, - passwd_len length of passwd, must be one of 0, 8, - SCRAMBLE_LENGTH_323, SCRAMBLE_LENGTH - 'thd' and 'mqh' are updated on success; other params are IN. - - RETURN VALUE - 0 success: thd->priv_user, thd->priv_host, thd->master_access, mqh are - updated - 1 user not found or authentication failure - 2 user found, has long (4.1.1) salt, but passwd is in old (3.23) format. - -1 user found, has short (3.23) salt, but passwd is in new (4.1.1) format. -*/ - -int acl_getroot(THD *thd, USER_RESOURCES *mqh, - const char *passwd, uint passwd_len) -{ - ulong user_access= NO_ACCESS; - int res= 1; - ACL_USER *acl_user= 0; - Security_context *sctx= thd->security_ctx; - DBUG_ENTER("acl_getroot"); - - if (!initialized) - { - /* - here if mysqld's been started with --skip-grant-tables option. - */ - sctx->skip_grants(); - bzero((char*) mqh, sizeof(*mqh)); - DBUG_RETURN(0); - } - - mysql_mutex_lock(&acl_cache->lock); - - /* - Find acl entry in user database. Note, that find_acl_user is not the same, - because it doesn't take into account the case when user is not empty, - but acl_user->user is empty - */ - - for (uint i=0 ; i < acl_users.elements ; i++) - { - ACL_USER *acl_user_tmp= dynamic_element(&acl_users,i,ACL_USER*); - if (!acl_user_tmp->user || !strcmp(sctx->user, acl_user_tmp->user)) - { - if (compare_hostname(&acl_user_tmp->host, sctx->host, sctx->ip)) - { - /* check password: it should be empty or valid */ - if (passwd_len == acl_user_tmp->salt_len) - { - if (acl_user_tmp->salt_len == 0 || - (acl_user_tmp->salt_len == SCRAMBLE_LENGTH ? - check_scramble(passwd, thd->scramble, acl_user_tmp->salt) : - check_scramble_323(passwd, thd->scramble, - (ulong *) acl_user_tmp->salt)) == 0) - { - acl_user= acl_user_tmp; - res= 0; - } - } - else if (passwd_len == SCRAMBLE_LENGTH && - acl_user_tmp->salt_len == SCRAMBLE_LENGTH_323) - res= -1; - else if (passwd_len == SCRAMBLE_LENGTH_323 && - acl_user_tmp->salt_len == SCRAMBLE_LENGTH) - res= 2; - /* linear search complete: */ - break; - } - } - } - /* - This was moved to separate tree because of heavy HAVE_OPENSSL case. - If acl_user is not null, res is 0. - */ - - if (acl_user) - { - /* OK. User found and password checked continue validation */ -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - Vio *vio=thd->net.vio; - SSL *ssl= (SSL*) vio->ssl_arg; - X509 *cert; -#endif - - /* - At this point we know that user is allowed to connect - from given host by given username/password pair. Now - we check if SSL is required, if user is using SSL and - if X509 certificate attributes are OK - */ - switch (acl_user->ssl_type) { - case SSL_TYPE_NOT_SPECIFIED: // Impossible - case SSL_TYPE_NONE: // SSL is not required - user_access= acl_user->access; - break; -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - case SSL_TYPE_ANY: // Any kind of SSL is ok - if (vio_type(vio) == VIO_TYPE_SSL) - user_access= acl_user->access; - break; - case SSL_TYPE_X509: /* Client should have any valid certificate. */ - /* - Connections with non-valid certificates are dropped already - in sslaccept() anyway, so we do not check validity here. - - We need to check for absence of SSL because without SSL - we should reject connection. - */ - if (vio_type(vio) == VIO_TYPE_SSL && - SSL_get_verify_result(ssl) == X509_V_OK && - (cert= SSL_get_peer_certificate(ssl))) - { - user_access= acl_user->access; - X509_free(cert); - } - break; - case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ - /* - We do not check for absence of SSL because without SSL it does - not pass all checks here anyway. - If cipher name is specified, we compare it to actual cipher in - use. - */ - if (vio_type(vio) != VIO_TYPE_SSL || - SSL_get_verify_result(ssl) != X509_V_OK) - break; - if (acl_user->ssl_cipher) - { - DBUG_PRINT("info",("comparing ciphers: '%s' and '%s'", - acl_user->ssl_cipher,SSL_get_cipher(ssl))); - if (!strcmp(acl_user->ssl_cipher,SSL_get_cipher(ssl))) - user_access= acl_user->access; - else - { - if (global_system_variables.log_warnings) - sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'", - acl_user->ssl_cipher, - SSL_get_cipher(ssl)); - break; - } - } - /* Prepare certificate (if exists) */ - DBUG_PRINT("info",("checkpoint 1")); - if (!(cert= SSL_get_peer_certificate(ssl))) - { - user_access=NO_ACCESS; - break; - } - DBUG_PRINT("info",("checkpoint 2")); - /* If X509 issuer is specified, we check it... */ - if (acl_user->x509_issuer) - { - DBUG_PRINT("info",("checkpoint 3")); - char *ptr = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - DBUG_PRINT("info",("comparing issuers: '%s' and '%s'", - acl_user->x509_issuer, ptr)); - if (strcmp(acl_user->x509_issuer, ptr)) - { - if (global_system_variables.log_warnings) - sql_print_information("X509 issuer mismatch: should be '%s' " - "but is '%s'", acl_user->x509_issuer, ptr); - free(ptr); - X509_free(cert); - user_access=NO_ACCESS; - break; - } - user_access= acl_user->access; - free(ptr); - } - DBUG_PRINT("info",("checkpoint 4")); - /* X509 subject is specified, we check it .. */ - if (acl_user->x509_subject) - { - char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); - DBUG_PRINT("info",("comparing subjects: '%s' and '%s'", - acl_user->x509_subject, ptr)); - if (strcmp(acl_user->x509_subject,ptr)) - { - if (global_system_variables.log_warnings) - sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", - acl_user->x509_subject, ptr); - free(ptr); - X509_free(cert); - user_access=NO_ACCESS; - break; - } - user_access= acl_user->access; - free(ptr); - } - /* Deallocate the X509 certificate. */ - X509_free(cert); - break; -#else /* HAVE_OPENSSL */ - default: - /* - If we don't have SSL but SSL is required for this user the - authentication should fail. - */ - break; -#endif /* HAVE_OPENSSL */ - } - sctx->master_access= user_access; - sctx->priv_user= acl_user->user ? sctx->user : (char *) ""; - *mqh= acl_user->user_resource; - - if (acl_user->host.hostname) - strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); - else - *sctx->priv_host= 0; - } - mysql_mutex_unlock(&acl_cache->lock); - DBUG_RETURN(res); -} - - -/* - This is like acl_getroot() above, but it doesn't check password, - and we don't care about the user resources. - - SYNOPSIS - acl_getroot_no_password() sctx Context which should be initialized user user name host host name @@ -1081,13 +1268,13 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, TRUE Error */ -bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, - char *ip, char *db) +bool acl_getroot(Security_context *sctx, char *user, char *host, + char *ip, char *db) { int res= 1; uint i; ACL_USER *acl_user= 0; - DBUG_ENTER("acl_getroot_no_password"); + DBUG_ENTER("acl_getroot"); DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'", (host ? host : "(NULL)"), (ip ? ip : "(NULL)"), @@ -1110,8 +1297,7 @@ bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, sctx->master_access= 0; sctx->db_access= 0; - sctx->priv_user= (char *) ""; - *sctx->priv_host= 0; + *sctx->priv_user= *sctx->priv_host= 0; /* Find acl entry in user database. @@ -1153,7 +1339,11 @@ bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, } } sctx->master_access= acl_user->access; - sctx->priv_user= acl_user->user ? user : (char *) ""; + + if (acl_user->user) + strmake(sctx->priv_user, user, USERNAME_LENGTH); + else + *sctx->priv_user= 0; if (acl_user->host.hostname) strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); @@ -1179,7 +1369,9 @@ static void acl_update_user(const char *user, const char *host, const char *x509_issuer, const char *x509_subject, USER_RESOURCES *mqh, - ulong privileges) + ulong privileges, + const LEX_STRING *plugin, + const LEX_STRING *auth) { mysql_mutex_assert_owner(&acl_cache->lock); @@ -1193,6 +1385,14 @@ static void acl_update_user(const char *user, const char *host, (acl_user->host.hostname && !my_strcasecmp(system_charset_info, host, acl_user->host.hostname))) { + if (plugin->str[0]) + { + acl_user->plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user->plugin.length= plugin->length; + acl_user->auth_string.str= auth->str ? + strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); + acl_user->auth_string.length= auth->length; + } acl_user->access=privileges; if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) acl_user->user_resource.questions=mqh->questions; @@ -1229,7 +1429,9 @@ static void acl_insert_user(const char *user, const char *host, const char *x509_issuer, const char *x509_subject, USER_RESOURCES *mqh, - ulong privileges) + ulong privileges, + const LEX_STRING *plugin, + const LEX_STRING *auth) { ACL_USER acl_user; @@ -1237,6 +1439,22 @@ static void acl_insert_user(const char *user, const char *host, acl_user.user=*user ? strdup_root(&mem,user) : 0; update_hostname(&acl_user.host, *host ? strdup_root(&mem, host): 0); + if (plugin->str[0]) + { + acl_user.plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user.plugin.length= plugin->length; + acl_user.auth_string.str= auth->str ? + strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); + acl_user.auth_string.length= auth->length; + } + else + { + acl_user.plugin= password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323 ? + old_password_plugin_name : native_password_plugin_name; + acl_user.auth_string.str= strmake_root(&mem, password, password_len); + acl_user.auth_string.length= password_len; + } + acl_user.access=privileges; acl_user.user_resource = *mqh; acl_user.sort=get_sort(2,acl_user.host.hostname,acl_user.user); @@ -1632,8 +1850,18 @@ bool change_password(THD *thd, const char *host, const char *user, my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); goto end; } + + if (my_strcasecmp(system_charset_info, acl_user->plugin.str, + native_password_plugin_name.str) && + my_strcasecmp(system_charset_info, acl_user->plugin.str, + old_password_plugin_name.str)) + { + push_warning (thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SET_PASSWORD_AUTH_PLUGIN, ER(ER_SET_PASSWORD_AUTH_PLUGIN)); + } /* update loaded acl entry: */ set_user_salt(acl_user, new_password, new_password_len); + set_user_plugin (acl_user, new_password_len); if (update_user_table(thd, table, acl_user->host.hostname ? acl_user->host.hostname : "", @@ -2013,7 +2241,7 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, see also test_if_create_new_users() */ - else if (!password_len && no_auto_create) + else if (!password_len && !combo.plugin.length && no_auto_create) { my_error(ER_PASSWORD_NO_MATCH, MYF(0), combo.user.str, combo.host.str); goto end; @@ -2024,6 +2252,15 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, thd->security_ctx->user, thd->security_ctx->host_or_ip); goto end; } + else if (combo.plugin.str[0]) + { + if (!plugin_is_ready(&combo.plugin, MYSQL_AUTHENTICATION_PLUGIN)) + { + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), combo.plugin.str); + goto end; + } + } + old_row_exists = 0; restore_record(table,s->default_values); table->field[0]->store(combo.host.str,combo.host.length, @@ -2037,7 +2274,14 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, { old_row_exists = 1; store_record(table,record[1]); // Save copy for update - if (combo.password.str) // If password given + /* what == 'N' means revoke */ + if (combo.plugin.length && what != 'N') + { + my_error(ER_GRANT_PLUGIN_USER_EXISTS, MYF(0), combo.user.length, + combo.user.str); + goto end; + } + if (combo.password.str) // If password given table->field[2]->store(password, password_len, system_charset_info); else if (!rights && !revoke_grant && lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && @@ -2118,7 +2362,25 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) table->field[next_field+3]->store((longlong) mqh.user_conn, TRUE); mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour; + + next_field+=4; + if (combo.plugin.str[0]) + { + if (table->s->fields >= 41 && combo.plugin.str[0]) + { + table->field[next_field]->store(combo.plugin.str, combo.plugin.length, + system_charset_info); + table->field[next_field+1]->store(combo.auth.str, combo.auth.length, + system_charset_info); + } + else + { + my_error (ER_BAD_FIELD_ERROR, MYF(0), "plugin", "mysql.user"); + goto end; + } + } } + if (old_row_exists) { /* @@ -2162,7 +2424,9 @@ end: lex->x509_issuer, lex->x509_subject, &lex->mqh, - rights); + rights, + &combo.plugin, + &combo.auth); else acl_insert_user(combo.user.str, combo.host.str, password, password_len, lex->ssl_type, @@ -2170,7 +2434,9 @@ end: lex->x509_issuer, lex->x509_subject, &lex->mqh, - rights); + rights, + &combo.plugin, + &combo.auth); } DBUG_RETURN(error); } @@ -2285,6 +2551,160 @@ abort: } +static void +acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) +{ + mysql_mutex_assert_owner(&acl_cache->lock); + + DBUG_ENTER ("acl_update_proxy_user"); + for (uint i= 0 ; i < acl_proxy_users.elements ; i++) + { + ACL_PROXY_USER *acl_user= + dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); + + if (acl_user->pk_equals(new_value)) + { + if (is_revoke) + { + DBUG_PRINT ("info", ("delting ACL_PROXY_USER")); + delete_dynamic_element(&acl_proxy_users, i); + } + else + { + DBUG_PRINT ("info", ("updating ACL_PROXY_USER")); + acl_user->set_data(new_value); + } + break; + } + } + DBUG_VOID_RETURN; +} + + +static void +acl_insert_proxy_user(ACL_PROXY_USER *new_value) +{ + DBUG_ENTER ("acl_insert_proxy_user"); + mysql_mutex_assert_owner(&acl_cache->lock); + (void) push_dynamic(&acl_proxy_users, (uchar *) new_value); + my_qsort((uchar*) dynamic_element(&acl_proxy_users,0,ACL_PROXY_USER *), + acl_proxy_users.elements, + sizeof(ACL_PROXY_USER),(qsort_cmp) acl_compare); + DBUG_VOID_RETURN; +} + + +static int +replace_proxy_priv_table(THD *thd, TABLE *table, const LEX_USER *user, + const LEX_USER *proxied_user, bool with_grant_arg, + bool revoke_grant) +{ + bool old_row_exists=0; + int error; + uchar user_key[MAX_KEY_LENGTH]; + ACL_PROXY_USER new_grant; + + DBUG_ENTER("replace_proxy_priv_table"); + + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + DBUG_RETURN(-1); + } + + /* Check if there is such a user in user table in memory? */ + if (!find_acl_user(user->host.str,user->user.str, FALSE)) + { + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); + DBUG_RETURN(-1); + } + + table->use_all_columns(); + ACL_PROXY_USER::store_pk (table, &user->host, &user->user, + &proxied_user->host, &proxied_user->user); + + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); + + table->file->ha_index_init(0, 1); + if (table->file->index_read_map(table->record[0],user_key, + HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + { + DBUG_PRINT ("info", ("Row not found")); + if (revoke_grant) + { // no row, no revoke + my_error(ER_NONEXISTING_GRANT, MYF(0), user->user.str, user->host.str); + goto abort; + } + old_row_exists = 0; + restore_record(table,s->default_values); + ACL_PROXY_USER::store_data_record (table, &user->host, &user->user, + &proxied_user->host, + &proxied_user->user, + with_grant_arg); + } + else + { + DBUG_PRINT ("info", ("Row found")); + old_row_exists = 1; + store_record(table,record[1]); + } + + if (old_row_exists) + { + /* update old existing row */ + if (!revoke_grant) + { + if ((error= table->file->ha_update_row(table->record[1], + table->record[0])) && + error != HA_ERR_RECORD_IS_THE_SAME) + goto table_error; /* purecov: inspected */ + } + else + { + if ((error= table->file->ha_delete_row(table->record[1]))) + goto table_error; /* purecov: inspected */ + } + } + else if ((error= table->file->ha_write_row(table->record[0]))) + { + DBUG_PRINT ("info", ("error inserting the row")); + if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) + goto table_error; /* purecov: inspected */ + } + + acl_cache->clear(1); // Clear privilege cache + if (old_row_exists) + { + new_grant.init(user->host.str, user->user.str, + proxied_user->host.str, proxied_user->user.str, + with_grant_arg); + acl_update_proxy_user(&new_grant, revoke_grant); + } + else + { + new_grant.init (&mem, user->host.str, user->user.str, + proxied_user->host.str, proxied_user->user.str, + with_grant_arg); + acl_insert_proxy_user(&new_grant); + } + + table->file->ha_index_end(); + DBUG_RETURN(0); + + /* This could only happen if the grant tables got corrupted */ +table_error: + DBUG_PRINT ("info", ("table error")); + table->file->print_error(error,MYF(0)); /* purecov: inspected */ + +abort: + DBUG_PRINT ("info", ("aborting replace_proxy_priv_table")); + table->file->ha_index_end(); + DBUG_RETURN(-1); +} + + class GRANT_COLUMN :public Sql_alloc { public: @@ -3500,10 +3920,10 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, - ulong rights, bool revoke_grant) + ulong rights, bool revoke_grant, bool is_proxy) { List_iterator <LEX_USER> str_list (list); - LEX_USER *Str, *tmp_Str; + LEX_USER *Str, *tmp_Str, *proxied_user; char tmp_db[NAME_LEN+1]; bool create_new_users=0; TABLE_LIST tables[2]; @@ -3523,11 +3943,26 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, db=tmp_db; } - /* open the mysql.user and mysql.db tables */ + if (is_proxy) + { + DBUG_ASSERT (!db); + proxied_user= str_list++; + } + + /* open the mysql.user and mysql.db or mysql.proxy_priv tables */ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("user"), "user", TL_WRITE); - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("db"), "db", TL_WRITE); + if (is_proxy) + + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxy_priv"), + "proxy_priv", + TL_WRITE); + else + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("db"), + "db", + TL_WRITE); tables[0].next_local= tables[0].next_global= tables+1; /* @@ -3613,6 +4048,13 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, result= -1; } } + else if (is_proxy) + { + if (replace_proxy_priv_table (thd, tables[1].table, Str, proxied_user, + rights & GRANT_ACL ? TRUE : FALSE, + revoke_grant)) + result= -1; + } } mysql_mutex_unlock(&acl_cache->lock); @@ -5045,6 +5487,12 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) goto end; } + if (show_proxy_grants(thd, lex_user, buff, sizeof(buff))) + { + error= -1; + goto end; + } + end: mysql_mutex_unlock(&acl_cache->lock); mysql_rwlock_unlock(&LOCK_grant); @@ -5202,7 +5650,7 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) < 0 Error. */ -#define GRANT_TABLES 5 +#define GRANT_TABLES 6 int open_grant_tables(THD *thd, TABLE_LIST *tables) { DBUG_ENTER("open_grant_tables"); @@ -5226,10 +5674,14 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (tables+4)->init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE); + (tables+5)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxy_priv"), + "proxy_priv", TL_WRITE); tables->next_local= tables->next_global= tables+1; (tables+1)->next_local= (tables+1)->next_global= tables+2; (tables+2)->next_local= (tables+2)->next_global= tables+3; (tables+3)->next_local= (tables+3)->next_global= tables+4; + (tables+4)->next_local= (tables+4)->next_global= tables+5; #ifdef HAVE_REPLICATION /* @@ -5243,11 +5695,11 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) account in tests. */ tables[0].updating=tables[1].updating=tables[2].updating= - tables[3].updating=tables[4].updating=1; + tables[3].updating=tables[4].updating=tables[5].updating= 1; if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) DBUG_RETURN(1); tables[0].updating=tables[1].updating=tables[2].updating= - tables[3].updating=tables[4].updating=0;; + tables[3].updating=tables[4].updating=tables[5].updating=0; } #endif @@ -5376,7 +5828,7 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, int error; TABLE *table= tables[table_no].table; Field *host_field= table->field[0]; - Field *user_field= table->field[table_no ? 2 : 1]; + Field *user_field= table->field[table_no && table_no != 5 ? 2 : 1]; char *host_str= user_from->host.str; char *user_str= user_from->user.str; const char *host; @@ -5459,12 +5911,15 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, user= ""; #ifdef EXTRA_DEBUG - DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", - user, host, - get_field(thd->mem_root, table->field[1]) /*db*/, - get_field(thd->mem_root, table->field[3]) /*table*/, - get_field(thd->mem_root, - table->field[4]) /*column*/)); + if (table_no != 5) + { + DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", + user, host, + get_field(thd->mem_root, table->field[1]) /*db*/, + get_field(thd->mem_root, table->field[3]) /*table*/, + get_field(thd->mem_root, + table->field[4]) /*column*/)); + } #endif if (strcmp(user_str, user) || my_strcasecmp(system_charset_info, host_str, host)) @@ -5526,6 +5981,7 @@ static int handle_grant_struct(uint struct_no, bool drop, const char *host; ACL_USER *acl_user= NULL; ACL_DB *acl_db= NULL; + ACL_PROXY_USER *acl_proxy_user= NULL; GRANT_NAME *grant_name= NULL; DBUG_ENTER("handle_grant_struct"); DBUG_PRINT("info",("scan struct: %u search: '%s'@'%s'", @@ -5550,6 +6006,9 @@ static int handle_grant_struct(uint struct_no, bool drop, case 3: elements= proc_priv_hash.records; break; + case 5: + elements= acl_proxy_users.elements; + break; default: return -1; } @@ -5588,6 +6047,11 @@ static int handle_grant_struct(uint struct_no, bool drop, user= grant_name->user; host= grant_name->host.hostname; break; + case 5: + acl_proxy_user= dynamic_element(&acl_proxy_users, idx, ACL_PROXY_USER*);; + user= acl_proxy_user->get_user(); + host= acl_proxy_user->get_host(); + break; default: assert(0); } @@ -5623,6 +6087,11 @@ static int handle_grant_struct(uint struct_no, bool drop, case 3: my_hash_delete(&proc_priv_hash, (uchar*) grant_name); break; + + case 5: + delete_dynamic_element(&acl_proxy_users, idx); + break; + } elements--; idx--; @@ -5658,6 +6127,12 @@ static int handle_grant_struct(uint struct_no, bool drop, my_hash_update(&column_priv_hash, (uchar*) grant_name, (uchar*) grant_name->hash_key, grant_name->key_length); break; + + case 5: + acl_proxy_user->set_user (&mem, user_to->user.str); + acl_proxy_user->set_host (&mem, user_to->host.str); + break; + } } else @@ -5792,6 +6267,20 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, result= 1; /* At least one record/element found. */ } } + + /* Handle proxy_priv table. */ + if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle proxy_priv array. */ + if ((handle_grant_struct(5, drop, user_from, user_to) && ! result) || + found) + result= 1; /* At least one record/element found. */ + } end: DBUG_RETURN(result); } @@ -6478,38 +6967,44 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, tables->db= (char*)sp_db; tables->table_name= tables->alias= (char*)sp_name; - combo->host.length= strlen(combo->host.str); - combo->user.length= strlen(combo->user.str); - combo->host.str= thd->strmake(combo->host.str,combo->host.length); - combo->user.str= thd->strmake(combo->user.str,combo->user.length); + thd->make_lex_string(&combo->user, + combo->user.str, strlen(combo->user.str), 0); + thd->make_lex_string(&combo->host, + combo->host.str, strlen(combo->host.str), 0); + combo->password= empty_lex_str; + combo->plugin= empty_lex_str; + combo->auth= empty_lex_str; - if(au && au->salt_len) + if(au) { - if (au->salt_len == SCRAMBLE_LENGTH) - { - make_password_from_salt(passwd_buff, au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; - } - else if (au->salt_len == SCRAMBLE_LENGTH_323) + if (au->salt_len) { - make_password_from_salt_323(passwd_buff, (ulong *) au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + if (au->salt_len == SCRAMBLE_LENGTH) + { + make_password_from_salt(passwd_buff, au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + } + else if (au->salt_len == SCRAMBLE_LENGTH_323) + { + make_password_from_salt_323(passwd_buff, (ulong *) au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + } + else + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_PASSWD_LENGTH, + ER(ER_PASSWD_LENGTH), SCRAMBLED_PASSWORD_CHAR_LENGTH); + return TRUE; + } + combo->password.str= passwd_buff; } - else + + if (au->plugin.str != native_password_plugin_name.str && + au->plugin.str != old_password_plugin_name.str) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PASSWD_LENGTH, - ER(ER_PASSWD_LENGTH), - SCRAMBLED_PASSWORD_CHAR_LENGTH); - return TRUE; + combo->plugin= au->plugin; + combo->auth= au->auth_string; } - combo->password.str= passwd_buff; - } - else - { - combo->password.str= (char*)""; - combo->password.length= 0; } if (user_list.push_back(combo)) @@ -6542,6 +7037,127 @@ template class List<LEX_COLUMN>; template class List<LEX_USER>; #endif +/** + Validate if a user can proxy as another user + + @thd current thread + @param user the logged in user (proxy user) + @param authenticated_as the effective user a plugin is trying to + impersonate as (proxied user) + @return status + @retval FALSE OK + @retval TRUE user can't impersonate proxied user +*/ + +static bool +acl_find_proxy_user(const char *user, const char *host, const char *ip, + const char *authenticated_as, bool *proxy_used) +{ + uint i; + /* if the proxied and proxy user are the same return OK */ + DBUG_ENTER ("acl_find_proxy_user"); + DBUG_PRINT ("info", ("user=%s host=%s ip=%s authenticated_as=%s", user, host, ip, authenticated_as)); + + if (!strcmp (authenticated_as, user)) + { + DBUG_PRINT ("info", ("user is the same as authenticated_as")); + DBUG_RETURN (FALSE); + } + + *proxy_used= TRUE; + for (i=0 ; i < acl_proxy_users.elements ; i++) + { + ACL_PROXY_USER *proxy= dynamic_element (&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->matches (host, user, ip, authenticated_as)) + DBUG_RETURN(FALSE); + } + + DBUG_RETURN (TRUE); +} + + +bool +acl_check_proxy_grant_access (THD *thd, const char *host, const char *user, + bool with_grant) +{ + DBUG_ENTER ("acl_check_proxy_grant_access"); + DBUG_PRINT ("info", ("user=%s host=%s with_grant=%d", user, host, (int) with_grant)); + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + DBUG_RETURN(1); + } + + /* replication slave thread can do anything */ + if (thd->slave_thread) + { + DBUG_PRINT ("info", ("replication slave")); + DBUG_RETURN(FALSE); + } + + /* one can grant proxy to himself to others */ + if (!strcmp(thd->security_ctx->user, user) && + !my_strcasecmp(system_charset_info, host, + thd->security_ctx->host)) + { + DBUG_PRINT ("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal", + thd->security_ctx->user, user, + host, thd->security_ctx->host)); + DBUG_RETURN(FALSE); + } + + /* check for matching WITH PROXY rights */ + for (uint i=0 ; i < acl_proxy_users.elements ; i++) + { + ACL_PROXY_USER *proxy= dynamic_element (&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->matches (thd->security_ctx->host, + thd->security_ctx->user, + thd->security_ctx->ip, + user) && + proxy->get_with_grant()) + { + DBUG_PRINT ("info", ("found")); + DBUG_RETURN(FALSE); + } + } + + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + thd->security_ctx->user, + thd->security_ctx->host_or_ip); + DBUG_RETURN(TRUE); +} + + +static bool +show_proxy_grants (THD *thd, LEX_USER *user, char *buff, size_t buffsize) +{ + Protocol *protocol= thd->protocol; + int error= 0; + + for (uint i=0 ; i < acl_proxy_users.elements ; i++) + { + ACL_PROXY_USER *proxy= dynamic_element (&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->granted_on(user->host.str, user->user.str)) + { + String global(buff, buffsize, system_charset_info); + global.length(0); + proxy->print_grant(&global); + protocol->prepare_for_resend(); + protocol->store(global.ptr(),global.length(),global.charset()); + if (protocol->write()) + { + error= -1; + break; + } + } + } + return error; +} + + #endif /*NO_EMBEDDED_ACCESS_CHECKS */ @@ -7109,3 +7725,1599 @@ get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, } +/**************************************************************************** + AUTHENTICATION CODE + including initial connect handshake, invoking appropriate plugins, + client-server plugin negotiation, COM_CHANGE_USER, and native + MySQL authentication plugins. +****************************************************************************/ + +/* few defines to have less ifdef's in the code below */ +#ifdef EMBEDDED_LIBRARY +#undef HAVE_OPENSSL +#ifdef NO_EMBEDDED_ACCESS_CHECKS +#define initialized 0 +#define decrease_user_connections(X) /* nothing */ +#define check_for_max_user_connections(X,Y) 0 +#endif +#endif +#ifndef HAVE_OPENSSL +#define ssl_acceptor_fd 0 +#define sslaccept(A,B,C) 1 +#endif + + +class Thd_charset_adapter +{ + THD *thd; +public: + Thd_charset_adapter(THD *thd_arg) : thd (thd_arg) {} + bool init_client_charset(uint cs_number) + { + thd_init_client_charset(thd, cs_number); + thd->update_charset(); + return thd->is_error(); + } + + CHARSET_INFO *charset() { return thd->charset(); } +}; + + +/** + The internal version of what plugins know as MYSQL_PLUGIN_VIO, + basically the context of the authentication session +*/ +struct MPVIO_EXT : public MYSQL_PLUGIN_VIO +{ + MYSQL_SERVER_AUTH_INFO auth_info; + const ACL_USER *acl_user; + plugin_ref plugin; ///< what plugin we're under + LEX_STRING db; ///< db name from the handshake packet + /** when restarting a plugin this caches the last client reply */ + struct { + char *plugin, *pkt; ///< pointers into NET::buff + uint pkt_len; + } cached_client_reply; + /** this caches the first plugin packet for restart request on the client */ + struct { + char *pkt; + uint pkt_len; + } cached_server_packet; + int packets_read, packets_written; ///< counters for send/received packets + uint connect_errors; ///< if there were connect errors for this host + /** when plugin returns a failure this tells us what really happened */ + enum { SUCCESS, FAILURE, RESTART } status; + + /* encapsulation members */ + ulong client_capabilities; + char *scramble; + MEM_ROOT *mem_root; + struct rand_struct *rand; + my_thread_id thread_id; + uint *server_status; + NET *net; + ulong max_client_packet_length; + char *ip; + char *host; + Thd_charset_adapter *charset_adapter; + LEX_STRING acl_user_plugin; +}; + +/** + a helper function to report an access denied error in all the proper places +*/ +static void login_failed_error(MPVIO_EXT *mpvio, int passwd_used) +{ + THD *thd= current_thd; + if (passwd_used == 2) + { + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_NO_PASSWORD_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + /* + Log access denied messages to the error log when log-warnings = 2 + so that the overhead of the general query log is not required to track + failed connections. + */ + if (global_system_variables.log_warnings > 1) + { + sql_print_warning(ER(ER_ACCESS_DENIED_NO_PASSWORD_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + } + } + else + { + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + /* + Log access denied messages to the error log when log-warnings = 2 + so that the overhead of the general query log is not required to track + failed connections. + */ + if (global_system_variables.log_warnings > 1) + { + sql_print_warning(ER(ER_ACCESS_DENIED_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + } + } +} + +/** + sends a server handshake initialization packet, the very first packet + after the connection was established + + Packet format: + + Bytes Content + ----- ---- + 1 protocol version (always 10) + n server version string, \0-terminated + 4 thread id + 8 first 8 bytes of the plugin provided data (scramble) + 1 \0 byte, terminating the first part of a scramble + 2 server capabilities (two lower bytes) + 1 server character set + 2 server status + 2 server capabilities (two upper bytes) + 1 length of the scramble + 10 reserved, always 0 + n rest of the plugin provided data (at least 12 bytes) + 1 \0 byte, terminating the second part of a scramble + + @retval 0 ok + @retval 1 error +*/ +static bool send_server_handshake_packet(MPVIO_EXT *mpvio, + const char *data, uint data_len) +{ + DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); + DBUG_ASSERT(data_len <= 255); + + char *buff= (char *)my_alloca(1 + SERVER_VERSION_LENGTH + data_len + 64); + char scramble_buf[SCRAMBLE_LENGTH]; + char *end= buff; + + DBUG_ENTER ("send_server_handshake_packet"); + *end++= protocol_version; + + mpvio->client_capabilities= CLIENT_BASIC_FLAGS; + + if (opt_using_transactions) + mpvio->client_capabilities|= CLIENT_TRANSACTIONS; + + mpvio->client_capabilities|= CAN_CLIENT_COMPRESS; + + if (ssl_acceptor_fd) + { + mpvio->client_capabilities |= CLIENT_SSL; + mpvio->client_capabilities |= CLIENT_SSL_VERIFY_SERVER_CERT; + } + + if (data_len) + { + mpvio->cached_server_packet.pkt= (char*)memdup_root(mpvio->mem_root, + data, data_len); + mpvio->cached_server_packet.pkt_len= data_len; + } + + if (data_len < SCRAMBLE_LENGTH) + { + if (data_len) + { /* + the first packet *must* have at least 20 bytes of a scramble. + if a plugin provided less, we pad it to 20 with zeros + */ + memcpy(scramble_buf, data, data_len); + bzero(scramble_buf+data_len, SCRAMBLE_LENGTH-data_len); + data= scramble_buf; + } + else + { + /* + if the default plugin does not provide the data for the scramble at + all, we generate a scramble internally anyway, just in case the + user account (that will be known only later) uses a + native_password_plugin (which needs a scramble). If we don't send a + scramble now - wasting 20 bytes in the packet - + native_password_plugin will have to send it in a separate packet, + adding one more round trip. + */ + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + data= mpvio->scramble; + } + data_len= SCRAMBLE_LENGTH; + } + + end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1; + int4store((uchar*) end, mpvio->thread_id); + end+= 4; + + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of the scramble is placed here, and second + part at the end of packet. + */ + end= (char*)memcpy(end, data, SCRAMBLE_LENGTH_323); + end+= SCRAMBLE_LENGTH_323; + *end++= 0; + + int2store(end, mpvio->client_capabilities); + /* write server characteristics: up to 16 bytes allowed */ + end[2]=(char) default_charset_info->number; + int2store(end+3, mpvio->server_status[0]); + int2store(end+5, mpvio->client_capabilities >> 16); + end[7]= data_len; + bzero(end+8, 10); + end+= 18; + /* write scramble tail */ + end= (char*)memcpy(end, data + SCRAMBLE_LENGTH_323, + data_len - SCRAMBLE_LENGTH_323); + end+= data_len - SCRAMBLE_LENGTH_323; + end= strmake(end, plugin_name(mpvio->plugin)->str, + plugin_name(mpvio->plugin)->length); + + int res= my_net_write(mpvio->net, (uchar*) buff, (size_t) (end-buff)) || + net_flush(mpvio->net); + my_afree(buff); + DBUG_RETURN (res); +} + +static bool secure_auth(MPVIO_EXT *mpvio) +{ + THD *thd; + if (!opt_secure_auth) + return 0; + /* + If the server is running in secure auth mode, short scrambles are + forbidden. Extra juggling to report the same error as the old code. + */ + + thd= current_thd; + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + { + my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + } + else + { + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + } + return 1; +} + +/** + sends a "change plugin" packet, requesting a client to restart authentication + using a different authentication plugin + + Packet format: + + Bytes Content + ----- ---- + 1 byte with the value 254 + n client plugin to use, \0-terminated + n plugin provided data + + In a special case of switching from native_password_plugin to + old_password_plugin, the packet contains only one - the first - byte, + plugin name is omitted, plugin data aren't needed as the scramble was + already sent. This one-byte packet is identical to the "use the short + scramble" packet in the protocol before plugins were introduced. + + @retval 0 ok + @retval 1 error +*/ +static bool send_plugin_request_packet(MPVIO_EXT *mpvio, + const uchar *data, uint data_len) +{ + DBUG_ASSERT(mpvio->packets_written == 1); + DBUG_ASSERT(mpvio->packets_read == 1); + NET *net= mpvio->net; + static uchar switch_plugin_request_buf[]= { 254 }; + + DBUG_ENTER ("send_plugin_request_packet"); + mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART + + const char *client_auth_plugin= + ((st_mysql_auth *)(plugin_decl(mpvio->plugin)->info))->client_auth_plugin; + + DBUG_ASSERT(client_auth_plugin); + + /* + we send an old "short 4.0 scramble request", if we need to request a + client to use 4.0 auth plugin (short scramble) and the scramble was + already sent to the client + + below, cached_client_reply.plugin is the plugin name that client has used, + client_auth_plugin is derived from mysql.user table, for the given + user account, it's the plugin that the client need to use to login. + */ + bool switch_from_long_to_short_scramble= + native_password_plugin_name.str == mpvio->cached_client_reply.plugin && + client_auth_plugin == old_password_plugin_name.str; + + if (switch_from_long_to_short_scramble) + DBUG_RETURN (secure_auth(mpvio) || + my_net_write(net, switch_plugin_request_buf, 1) || + net_flush(net)); + + /* + We never request a client to switch from a short to long scramble. + Plugin-aware clients can do that, but traditionally it meant to + ask an old 4.0 client to use the new 4.1 authentication protocol. + */ + bool switch_from_short_to_long_scramble= + old_password_plugin_name.str == mpvio->cached_client_reply.plugin && + client_auth_plugin == native_password_plugin_name.str; + + if (switch_from_short_to_long_scramble) + { + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(current_thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN (1); + } + + DBUG_PRINT ("info", ("requesting client to use the %s plugin", + client_auth_plugin)); + DBUG_RETURN (net_write_command(net, switch_plugin_request_buf[0], + (uchar*)client_auth_plugin, + strlen(client_auth_plugin)+1, + (uchar*)data, data_len)); +} + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/** + Finds acl entry in user database for authentication purposes. + + Finds a user and copies it into mpvio. Reports an authentication + failure if a user is not found. + + @note find_acl_user is not the same, because it doesn't take into + account the case when user is not empty, but acl_user->user is empty + + @retval 0 found + @retval 1 not found +*/ +static bool find_mpvio_user(MPVIO_EXT *mpvio) +{ + DBUG_ENTER ("find_mpvio_user"); + DBUG_PRINT ("info", ("entry: %s", mpvio->auth_info.user_name)); + DBUG_ASSERT(mpvio->acl_user == 0); + mysql_mutex_lock(&acl_cache->lock); + for (uint i=0 ; i < acl_users.elements ; i++) + { + ACL_USER *acl_user_tmp= dynamic_element(&acl_users,i,ACL_USER*); + if ((!acl_user_tmp->user || + !strcmp(mpvio->auth_info.user_name, acl_user_tmp->user)) && + compare_hostname(&acl_user_tmp->host, mpvio->host, mpvio->ip)) + { + mpvio->acl_user= acl_user_tmp->copy(mpvio->mem_root); + if (acl_user_tmp->plugin.str == native_password_plugin_name.str || + acl_user_tmp->plugin.str == old_password_plugin_name.str) + mpvio->acl_user_plugin= acl_user_tmp->plugin; + else + make_lex_string_root(mpvio->mem_root, + &mpvio->acl_user_plugin, + acl_user_tmp->plugin.str, + acl_user_tmp->plugin.length, 0); + break; + } + } + mysql_mutex_unlock(&acl_cache->lock); + + if (!mpvio->acl_user) + { + login_failed_error(mpvio, 0); + DBUG_RETURN (1); + } + + /* user account requires non-default plugin and the client is too old */ + if (mpvio->acl_user->plugin.str != native_password_plugin_name.str && + mpvio->acl_user->plugin.str != old_password_plugin_name.str && + !(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, + native_password_plugin_name.str)); + DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, + old_password_plugin_name.str)); + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(current_thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN (1); + } + + mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; + mpvio->auth_info.auth_string_length= + (unsigned long) mpvio->acl_user->auth_string.length; + strmake(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? + mpvio->acl_user->user : "", USERNAME_LENGTH); + DBUG_PRINT ("info", ("exit: user=%s, auth_string=%s, authenticated as=%s" + "plugin=%s", + mpvio->auth_info.user_name, + mpvio->auth_info.auth_string, + mpvio->auth_info.authenticated_as, + mpvio->acl_user->plugin.str)); + DBUG_RETURN (0); +} +#endif + +/* the packet format is described in send_change_user_packet() */ +static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) +{ + NET *net= mpvio->net; + + char *user= (char*) net->read_pos; + char *end= user + packet_length; + /* Safe because there is always a trailing \0 at the end of the packet */ + char *passwd= strend(user)+1; + uint user_len= passwd - user - 1; + char *db= passwd; + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + + DBUG_ENTER ("parse_com_change_user_packet"); + if (passwd >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN (1); + } + + /* + Old clients send null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + + This strlen() can't be easily deleted without changing protocol. + + Cast *passwd to an unsigned char, so that it doesn't extend the sign for + *passwd > 127 and become 2**32-127+ after casting to uint. + */ + uint passwd_len= (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION ? + (uchar)(*passwd++) : strlen(passwd)); + + db+= passwd_len + 1; + /* + Database name is always NUL-terminated, so in case of empty database + the packet must contain at least the trailing '\0'. + */ + if (db >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN (1); + } + + uint db_len= strlen(db); + + char *ptr= db + db_len + 1; + + if (ptr+1 < end) + { + if (mpvio->charset_adapter->init_client_charset(uint2korr(ptr))) + DBUG_RETURN(1); + } + + + /* Convert database and user names to utf8 */ + db_len= copy_and_convert(db_buff, sizeof(db_buff)-1, system_charset_info, + db, db_len, mpvio->charset_adapter->charset(), + &dummy_errors); + db_buff[db_len]= 0; + + user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, user_len, + mpvio->charset_adapter->charset(), + &dummy_errors); + user_buff[user_len]= 0; + + /* we should not free mpvio->user here: it's saved by dispatch_command() */ + if (!(mpvio->auth_info.user_name= my_strndup(user_buff, user_len, MYF(MY_WME)))) + return 1; + mpvio->auth_info.user_name_length= user_len; + + if (make_lex_string_root(mpvio->mem_root, + &mpvio->db, db_buff, db_len, 0) == 0) + DBUG_RETURN (1); /* The error is set by make_lex_string(). */ + + if (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + strmake(mpvio->auth_info.authenticated_as, + mpvio->auth_info.user_name, USERNAME_LENGTH); + + mpvio->status= MPVIO_EXT::SUCCESS; + DBUG_RETURN (0); + } + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (find_mpvio_user(mpvio)) + DBUG_RETURN (1); + + char *client_plugin; + if (mpvio->client_capabilities & CLIENT_PLUGIN_AUTH) + { + client_plugin= ptr + 2; + if (client_plugin >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN (1); + } + } + else + { + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) + client_plugin= native_password_plugin_name.str; + else + { + client_plugin= old_password_plugin_name.str; + /* + For a passwordless accounts we use native_password_plugin. + But when an old 4.0 client connects to it, we change it to + old_password_plugin, otherwise MySQL will think that server + and client plugins don't match. + */ + if (mpvio->acl_user->auth_string.length == 0) + mpvio->acl_user_plugin= old_password_plugin_name; + } + } + + DBUG_PRINT ("info", ("client_plugin=%s, restart", client_plugin)); + /* + Remember the data part of the packet, to present it to plugin in + read_packet() + */ + mpvio->cached_client_reply.pkt= passwd; + mpvio->cached_client_reply.pkt_len= passwd_len; + mpvio->cached_client_reply.plugin= client_plugin; + mpvio->status= MPVIO_EXT::RESTART; +#endif + + DBUG_RETURN (0); +} + +/* the packet format is described in send_client_reply_packet() */ +static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, + uchar **buff, ulong pkt_len) +{ +#ifndef EMBEDDED_LIBRARY + NET *net= mpvio->net; + char *end; + + DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); + + if (pkt_len < MIN_HANDSHAKE_SIZE) + return packet_error; + + if (mpvio->connect_errors) + reset_host_errors(mpvio->ip); + + ulong client_capabilities= uint2korr(net->read_pos); + if (client_capabilities & CLIENT_PROTOCOL_41) + { + client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; + mpvio->max_client_packet_length= uint4korr(net->read_pos+4); + DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8])); + if (mpvio->charset_adapter->init_client_charset((uint) net->read_pos[8])) + return packet_error; + end= (char*) net->read_pos+32; + } + else + { + mpvio->max_client_packet_length= uint3korr(net->read_pos+2); + end= (char*) net->read_pos+5; + } + + /* Disable those bits which are not supported by the client. */ + mpvio->client_capabilities&= client_capabilities; + + +#if defined(HAVE_OPENSSL) + DBUG_PRINT("info", ("client capabilities: %lu", mpvio->client_capabilities)); + if (mpvio->client_capabilities & CLIENT_SSL) + { + char error_string[1024] __attribute__((unused)); + + /* Do the SSL layering. */ + if (!ssl_acceptor_fd) + return packet_error; + + DBUG_PRINT("info", ("IO layer change in progress...")); + if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout)) + { + DBUG_PRINT("error", ("Failed to accept new SSL connection")); + return packet_error; + } + + DBUG_PRINT("info", ("Reading user information over SSL layer")); + pkt_len= my_net_read(net); + if (pkt_len == packet_error || pkt_len < NORMAL_HANDSHAKE_SIZE) + { + DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", + pkt_len)); + return packet_error; + } + } +#endif + + if (end >= (char*) net->read_pos+ pkt_len +2) + return packet_error; + + if ((mpvio->client_capabilities & CLIENT_TRANSACTIONS) && + opt_using_transactions) + net->return_status= mpvio->server_status; + + char *user= end; + char *passwd= strend(user)+1; + uint user_len= passwd - user - 1, db_len; + char *db= passwd; + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + + /* + Old clients send null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + + This strlen() can't be easily deleted without changing protocol. + + Cast *passwd to an unsigned char, so that it doesn't extend the sign for + *passwd > 127 and become 2**32-127+ after casting to uint. + */ + uint passwd_len= mpvio->client_capabilities & CLIENT_SECURE_CONNECTION ? + (uchar)(*passwd++) : strlen(passwd); + + if (mpvio->client_capabilities & CLIENT_CONNECT_WITH_DB) + { + db= db + passwd_len + 1; + /* strlen() can't be easily deleted without changing protocol */ + db_len= strlen(db); + } + else + { + db= 0; + db_len= 0; + } + + if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len) + return packet_error; + + char *client_plugin= passwd + passwd_len + (db ? db_len + 1 : 0); + + /* Since 4.1 all database names are stored in utf8 */ + if (db) + { + db_len= copy_and_convert(db_buff, sizeof(db_buff)-1, system_charset_info, + db, db_len, mpvio->charset_adapter->charset(), + &dummy_errors); + db= db_buff; + db_buff[db_len]= 0; + } + + user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, user_len, + mpvio->charset_adapter->charset(), + &dummy_errors); + user= user_buff; + user_buff[user_len]= 0; + + /* If username starts and ends in "'", chop them off */ + if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'') + { + user[user_len-1]= 0; + user++; + user_len-= 2; + } + + if (make_lex_string_root(mpvio->mem_root, + &mpvio->db, db, db_len, 0) == 0) + return packet_error; /* The error is set by make_lex_string(). */ + if (mpvio->auth_info.user_name) + my_free(mpvio->auth_info.user_name); + if (!(mpvio->auth_info.user_name= my_strndup(user, user_len, MYF(MY_WME)))) + return packet_error; /* The error is set by my_strdup(). */ + mpvio->auth_info.user_name_length= user_len; + + if (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + mpvio->status= MPVIO_EXT::SUCCESS; + return packet_error; + } + + if (find_mpvio_user(mpvio)) + return packet_error; + + if (mpvio->client_capabilities & CLIENT_PLUGIN_AUTH) + { + if ((client_plugin + strlen(client_plugin)) > + (char *)net->read_pos + pkt_len) + return packet_error; + } + else + { + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) + client_plugin= native_password_plugin_name.str; + else + { + client_plugin= old_password_plugin_name.str; + /* + For a passwordless accounts we use native_password_plugin. + But when an old 4.0 client connects to it, we change it to + old_password_plugin, otherwise MySQL will think that server + and client plugins don't match. + */ + if (mpvio->acl_user->auth_string.length == 0) + mpvio->acl_user_plugin= old_password_plugin_name; + } + } + + /* + if the acl_user needs a different plugin to authenticate + (specified in GRANT ... AUTHENTICATED VIA plugin_name ..) + we need to restart the authentication in the server. + But perhaps the client has already used the correct plugin - + in that case the authentication on the client may not need to be + restarted and a server auth plugin will read the data that the client + has just send. Cache them to return in the next server_mpvio_read_packet(). + */ + if (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str, + plugin_name(mpvio->plugin)->str) != 0) + { + mpvio->cached_client_reply.pkt= passwd; + mpvio->cached_client_reply.pkt_len= passwd_len; + mpvio->cached_client_reply.plugin= client_plugin; + mpvio->status= MPVIO_EXT::RESTART; + return packet_error; + } + + /* + ok, we don't need to restart the authentication on the server. + but if the client used the wrong plugin, we need to restart + the authentication on the client. Do it here, the server plugin + doesn't need to know. + */ + const char *client_auth_plugin= + ((st_mysql_auth *)(plugin_decl(mpvio->plugin)->info))->client_auth_plugin; + + if (client_auth_plugin && + my_strcasecmp(system_charset_info, client_plugin, client_auth_plugin)) + { + mpvio->cached_client_reply.plugin= client_plugin; + if (send_plugin_request_packet(mpvio, + (uchar*)mpvio->cached_server_packet.pkt, + mpvio->cached_server_packet.pkt_len)) + return packet_error; + + passwd_len= my_net_read(mpvio->net); + passwd = (char*)mpvio->net->read_pos; + } + + *buff= (uchar*)passwd; + return passwd_len; +#else + return 0; +#endif +} + + +/** + Make sure that when sending plugin supplued data to the client they + are not considered a special out-of-band command, like e.g. + \255 (error) or \254 (change user request packet). + To avoid this we send plugin data packets starting with one of these + 2 bytes "wrapped" in a command \1. + For the above reason we have to wrap plugin data packets starting with + \1 as well. +*/ + +#define IS_OUT_OF_BAND_PACKET(packet,packet_len) \ + ((packet_len) > 0 && \ + (*(packet) == 1 || *(packet) == 255 || *(packet) == 254)) + +static inline int +wrap_plguin_data_into_proper_command(NET *net, + const uchar *packet, int packet_len) +{ + DBUG_ASSERT(IS_OUT_OF_BAND_PACKET(packet, packet_len)); + return net_write_command(net, 1, (uchar*)"", 0, packet, packet_len); +} + + +/** + vio->write_packet() callback method for server authentication plugins + + This function is called by a server authentication plugin, when it wants + to send data to the client. + + It transparently wraps the data into a handshake packet, + and handles plugin negotiation with the client. If necessary, + it escapes the plugin data, if it starts with a mysql protocol packet byte. +*/ +static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, + const uchar *packet, int packet_len) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT*)param; + int res; + + DBUG_ENTER ("server_mpvio_write_packet"); + /* reset cached_client_reply */ + mpvio->cached_client_reply.pkt= 0; + /* for the 1st packet we wrap plugin data into the handshake packet */ + if (mpvio->packets_written == 0) + res= send_server_handshake_packet(mpvio, (char*)packet, packet_len); + else if (mpvio->status == MPVIO_EXT::RESTART) + res= send_plugin_request_packet(mpvio, packet, packet_len); + else if (IS_OUT_OF_BAND_PACKET(packet, packet_len)) + res= wrap_plguin_data_into_proper_command(mpvio->net, packet, packet_len); + else + { + res= my_net_write(mpvio->net, packet, packet_len) || + net_flush(mpvio->net); + } + mpvio->packets_written++; + DBUG_RETURN (res); +} + +/** + vio->read_packet() callback method for server authentication plugins + + This function is called by a server authentication plugin, when it wants + to read data from the client. + + It transparently extracts the client plugin data, if embedded into + a client authentication handshake packet, and handles plugin negotiation + with the client, if necessary. +*/ +static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT*)param; + ulong pkt_len; + + DBUG_ENTER ("server_mpvio_read_packet"); + if (mpvio->packets_written == 0) + { + /* + plugin wants to read the data without sending anything first. + send an empty packet to force a server handshake packet to be sent + */ + if (mpvio->write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(mpvio->net); + } + else if (mpvio->cached_client_reply.pkt) + { + DBUG_ASSERT(mpvio->status == MPVIO_EXT::RESTART); + DBUG_ASSERT(mpvio->packets_read > 0); + /* + if the have the data cached from the last server_mpvio_read_packet + (which can be the case if it's a restarted authentication) + and a client has used the correct plugin, then we can return the + cached data straight away and avoid one round trip. + */ + const char *client_auth_plugin= + ((st_mysql_auth *)(plugin_decl(mpvio->plugin)->info))->client_auth_plugin; + if (client_auth_plugin == 0 || + my_strcasecmp(system_charset_info, mpvio->cached_client_reply.plugin, + client_auth_plugin) == 0) + { + mpvio->status= MPVIO_EXT::FAILURE; + *buf= (uchar*)mpvio->cached_client_reply.pkt; + mpvio->cached_client_reply.pkt= 0; + mpvio->packets_read++; + DBUG_RETURN ((int)mpvio->cached_client_reply.pkt_len); + } + /* + But if the client has used the wrong plugin, the cached data are + useless. Furthermore, we have to send a "change plugin" request + to the client. + */ + if (mpvio->write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(mpvio->net); + } + else + pkt_len= my_net_read(mpvio->net); + + if (pkt_len == packet_error) + goto err; + + mpvio->packets_read++; + + /* + the 1st packet has the plugin data wrapped into the client authentication + handshake packet + */ + if (mpvio->packets_read == 1) + { + pkt_len= parse_client_handshake_packet(mpvio, buf, pkt_len); + if (pkt_len == packet_error) + goto err; + } + else + *buf = mpvio->net->read_pos; + + DBUG_RETURN ((int)pkt_len); + +err: + if (mpvio->status == MPVIO_EXT::FAILURE) + { + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), mpvio->auth_info.host_or_ip); + } + DBUG_RETURN (-1); +} + +/** + fills MYSQL_PLUGIN_VIO_INFO structure with the information about the + connection +*/ +static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio, + MYSQL_PLUGIN_VIO_INFO *info) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT*)vio; + mpvio_info(mpvio->net->vio, info); +} + +static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) +{ +#if defined(HAVE_OPENSSL) + Vio *vio=thd->net.vio; + SSL *ssl= (SSL*) vio->ssl_arg; + X509 *cert; +#endif + + /* + At this point we know that user is allowed to connect + from given host by given username/password pair. Now + we check if SSL is required, if user is using SSL and + if X509 certificate attributes are OK + */ + switch (acl_user->ssl_type) { + case SSL_TYPE_NOT_SPECIFIED: // Impossible + case SSL_TYPE_NONE: // SSL is not required + return 0; +#if defined(HAVE_OPENSSL) + case SSL_TYPE_ANY: // Any kind of SSL is ok + return vio_type(vio) != VIO_TYPE_SSL; + case SSL_TYPE_X509: /* Client should have any valid certificate. */ + /* + Connections with non-valid certificates are dropped already + in sslaccept() anyway, so we do not check validity here. + + We need to check for absence of SSL because without SSL + we should reject connection. + */ + if (vio_type(vio) == VIO_TYPE_SSL && + SSL_get_verify_result(ssl) == X509_V_OK && + (cert= SSL_get_peer_certificate(ssl))) + { + X509_free(cert); + return 0; + } + return 1; + case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ + /* If a cipher name is specified, we compare it to actual cipher in use. */ + if (vio_type(vio) != VIO_TYPE_SSL || + SSL_get_verify_result(ssl) != X509_V_OK) + return 1; + if (acl_user->ssl_cipher) + { + DBUG_PRINT("info",("comparing ciphers: '%s' and '%s'", + acl_user->ssl_cipher,SSL_get_cipher(ssl))); + if (strcmp(acl_user->ssl_cipher,SSL_get_cipher(ssl))) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'", + acl_user->ssl_cipher, SSL_get_cipher(ssl)); + return 1; + } + } + /* Prepare certificate (if exists) */ + if (!(cert= SSL_get_peer_certificate(ssl))) + return 1; + /* If X509 issuer is specified, we check it... */ + if (acl_user->x509_issuer) + { + char *ptr = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + DBUG_PRINT("info",("comparing issuers: '%s' and '%s'", + acl_user->x509_issuer, ptr)); + if (strcmp(acl_user->x509_issuer, ptr)) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 issuer mismatch: should be '%s' " + "but is '%s'", acl_user->x509_issuer, ptr); + free(ptr); + X509_free(cert); + return 1; + } + free(ptr); + } + /* X509 subject is specified, we check it .. */ + if (acl_user->x509_subject) + { + char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + DBUG_PRINT("info",("comparing subjects: '%s' and '%s'", + acl_user->x509_subject, ptr)); + if (strcmp(acl_user->x509_subject,ptr)) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", + acl_user->x509_subject, ptr); + free(ptr); + X509_free(cert); + return 1; + } + free(ptr); + } + X509_free(cert); + return 0; +#else /* HAVE_OPENSSL */ + default: + /* + If we don't have SSL but SSL is required for this user the + authentication should fail. + */ + return 1; +#endif /* HAVE_OPENSSL */ + } + return 1; +} + + +static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, + MPVIO_EXT *mpvio) +{ + int res= CR_OK, old_status= MPVIO_EXT::FAILURE; + bool unlock_plugin= false; + plugin_ref plugin; + + if (auth_plugin_name->str == native_password_plugin_name.str) + plugin= native_password_plugin; + else +#ifndef EMBEDDED_LIBRARY + if (auth_plugin_name->str == old_password_plugin_name.str) + plugin= old_password_plugin; + else if ((plugin= my_plugin_lock_by_name(thd, auth_plugin_name, + MYSQL_AUTHENTICATION_PLUGIN))) + unlock_plugin= true; + else +#endif + plugin= NULL; + + mpvio->plugin= plugin; + old_status= mpvio->status; + + if (plugin) + { + st_mysql_auth *auth= (st_mysql_auth*)plugin_decl(plugin)->info; + res= auth->authenticate_user(mpvio, &mpvio->auth_info); + + if (unlock_plugin) + plugin_unlock(thd, plugin); + } + else + { + /* Server cannot load the required plugin. */ + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str); + res= CR_ERROR; + } + + /* + If the status was MPVIO_EXT::RESTART before the authenticate_user() call + it can never be MPVIO_EXT::RESTART after the call, because any call + to write_packet() or read_packet() will reset the status. + + But (!) if a plugin never called a read_packet() or write_packet(), the + status will stay unchanged. We'll fix it, by resetting the status here. + */ + if (old_status == MPVIO_EXT::RESTART && mpvio->status == MPVIO_EXT::RESTART) + mpvio->status= MPVIO_EXT::FAILURE; // reset to the default + + return res; +} + + +static void +server_mpvio_initialize(THD *thd, MPVIO_EXT *mpvio, uint connect_errors, + Thd_charset_adapter *charset_adapter) +{ + memset(mpvio, 0, sizeof(MPVIO_EXT)); + mpvio->read_packet= server_mpvio_read_packet; + mpvio->write_packet= server_mpvio_write_packet; + mpvio->info= server_mpvio_info; + mpvio->auth_info.host_or_ip= thd->security_ctx->host_or_ip; + mpvio->auth_info.host_or_ip_length= + (unsigned int) strlen (thd->security_ctx->host_or_ip); + mpvio->auth_info.user_name= thd->security_ctx->user; + mpvio->auth_info.user_name_length= thd->security_ctx->user ? + (unsigned int) strlen(thd->security_ctx->user) : 0; + mpvio->connect_errors= connect_errors; + mpvio->status= MPVIO_EXT::FAILURE; + + mpvio->client_capabilities= thd->client_capabilities; + mpvio->mem_root= thd->mem_root; + mpvio->scramble= thd->scramble; + mpvio->rand= &thd->rand; + mpvio->thread_id= thd->thread_id; + mpvio->server_status= &thd->server_status; + mpvio->net= &thd->net; + mpvio->ip= thd->security_ctx->ip; + mpvio->host= thd->security_ctx->host; + mpvio->charset_adapter= charset_adapter; +} + + +static void +server_mpvio_update_thd(THD *thd, MPVIO_EXT *mpvio) +{ + thd->client_capabilities= mpvio->client_capabilities; + thd->max_client_packet_length= mpvio->max_client_packet_length; + if (mpvio->client_capabilities & CLIENT_INTERACTIVE) + thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; + thd->security_ctx->user= mpvio->auth_info.user_name; + if (thd->client_capabilities & CLIENT_IGNORE_SPACE) + thd->variables.sql_mode|= MODE_IGNORE_SPACE; +} + +/** + Perform the handshake, authorize the client and update thd sctx variables. + + @param thd thread handle + @param connect_errors number of previous failed connect attemps + from this host + @param com_change_user_pkt_len size of the COM_CHANGE_USER packet + (without the first, command, byte) or 0 + if it's not a COM_CHANGE_USER (that is, if + it's a new connection) + + @retval 0 success, thd is updated. + @retval 1 error +*/ +bool +acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len) +{ + int res= CR_OK; + MPVIO_EXT mpvio; + Thd_charset_adapter charset_adapter(thd); + + const LEX_STRING *auth_plugin_name= default_auth_plugin_name; + enum enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER + : COM_CONNECT; + + DBUG_ENTER ("acl_authenticate"); + compile_time_assert(MYSQL_USERNAME_LENGTH == USERNAME_LENGTH); + + server_mpvio_initialize(thd, &mpvio, connect_errors, &charset_adapter); + + DBUG_PRINT ("info", ("com_change_user_pkt_len=%u", com_change_user_pkt_len)); + + /* + Clear thd->db as it points to something, that will be freed when + connection is closed. We don't want to accidentally free a wrong + pointer if connect failed. + */ + thd->reset_db(NULL, 0); + + if (command == COM_CHANGE_USER) + { + mpvio.packets_written++; // pretend that a server handshake packet was sent + mpvio.packets_read++; // take COM_CHANGE_USER packet into account + + /* Clear variables that are allocated */ + thd->user_connect= 0; + + if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) + { + server_mpvio_update_thd(thd, &mpvio); + DBUG_RETURN(1); + } + + DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART || + mpvio.status == MPVIO_EXT::SUCCESS); + } + else + { + /* mark the thd as having no scramble yet */ + mpvio.scramble[SCRAMBLE_LENGTH]= 1; + + /* + perform the first authentication attempt, with the default plugin. + This sends the server handshake packet, reads the client reply + with a user name, and performs the authentication if everyone has used + the correct plugin. + */ + + res= do_auth_once(thd, auth_plugin_name, &mpvio); + } + + /* + retry the authentication, if - after receiving the user name - + we found that we need to switch to a non-default plugin + */ + if (mpvio.status == MPVIO_EXT::RESTART) + { + DBUG_ASSERT (mpvio.acl_user); + DBUG_ASSERT(command == COM_CHANGE_USER || + my_strcasecmp(system_charset_info, auth_plugin_name->str, + mpvio.acl_user->plugin.str)); + auth_plugin_name= &mpvio.acl_user->plugin; + res= do_auth_once (thd, auth_plugin_name, &mpvio); + } + + server_mpvio_update_thd(thd, &mpvio); + + Security_context *sctx= thd->security_ctx; + const ACL_USER *acl_user= mpvio.acl_user; + + thd->password= mpvio.auth_info.password_used; // remember for error messages + + /* + Log the command here so that the user can check the log + for the tried logins and also to detect break-in attempts. + + if sctx->user is unset it's protocol failure, bad packet. + */ + if (mpvio.auth_info.user_name) + { + if (strcmp(mpvio.auth_info.authenticated_as, mpvio.auth_info.user_name)) + { + general_log_print(thd, command, "%s@%s as %s on %s", + mpvio.auth_info.user_name, mpvio.auth_info.host_or_ip, + mpvio.auth_info.authenticated_as ? + mpvio.auth_info.authenticated_as : "anonymous", + mpvio.db.str ? mpvio.db.str : (char*) ""); + } + else + general_log_print(thd, command, (char*) "%s@%s on %s", + mpvio.auth_info.user_name, mpvio.auth_info.host_or_ip, + mpvio.db.str ? mpvio.db.str : (char*) ""); + } + + if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) + { + DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE); + + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); + DBUG_RETURN (1); + } + + sctx->proxy_user[0]= 0; + + if (initialized) // if not --skip-grant-tables + { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + bool is_proxy_user= FALSE; + const char *auth_user = mpvio.acl_user->user ? mpvio.acl_user->user : ""; + /* check if the user is allowed to proxy as another user */ + if (acl_find_proxy_user(auth_user, sctx->host, sctx->ip, + mpvio.auth_info.authenticated_as, + &is_proxy_user)) + { + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); + DBUG_RETURN(1); + } + + if (is_proxy_user) + my_snprintf(sctx->proxy_user, sizeof (sctx->proxy_user) - 1, + "'%s'@'%s'", auth_user, + acl_user->host.hostname ? acl_user->host.hostname : ""); +#endif + + sctx->master_access= acl_user->access; + strmake(sctx->priv_user, mpvio.auth_info.authenticated_as, USERNAME_LENGTH - 1); + if (acl_user->host.hostname) + strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); + else + *sctx->priv_host= 0; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + OK. Let's check the SSL. Historically it was checked after the password, + as an additional layer, not instead of the password + (in which case it would've been a plugin too). + */ + if (acl_check_ssl(thd, acl_user)) + { + if (!thd->is_error()) + login_failed_error(&mpvio, thd->password); + DBUG_RETURN (1); + } + + /* Don't allow the user to connect if he has done too many queries */ + if ((acl_user->user_resource.questions || acl_user->user_resource.updates || + acl_user->user_resource.conn_per_hour || + acl_user->user_resource.user_conn || + global_system_variables.max_user_connections) && + get_or_create_user_conn(thd, + (opt_old_style_user_limits ? sctx->user : sctx->priv_user), + (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host), + &acl_user->user_resource)) + DBUG_RETURN (1); // The error is set by get_or_create_user_conn() + +#endif + } + else + sctx->skip_grants(); + + if (thd->user_connect && + (thd->user_connect->user_resources.conn_per_hour || + thd->user_connect->user_resources.user_conn || + global_system_variables.max_user_connections) && + check_for_max_user_connections(thd, thd->user_connect)) + { + DBUG_RETURN (1); // The error is set in check_for_max_user_connections() + } + + DBUG_PRINT("info", + ("Capabilities: %lu packet_length: %ld Host: '%s' " + "Login user: '%s' Priv_user: '%s' Using password: %s " + "Access: %lu db: '%s'", + thd->client_capabilities, thd->max_client_packet_length, + sctx->host_or_ip, sctx->user, sctx->priv_user, + thd->password ? "yes": "no", + sctx->master_access, mpvio.db.str)); + + if (command == COM_CONNECT && + !(thd->main_security_ctx.master_access & SUPER_ACL)) + { + mysql_mutex_lock(&LOCK_connection_count); + bool count_ok= (connection_count <= max_connections); + mysql_mutex_unlock(&LOCK_connection_count); + if (!count_ok) + { // too many connections + my_error(ER_CON_COUNT_ERROR, MYF(0)); + DBUG_RETURN (1); + } + } + + /* + This is the default access rights for the current database. It's + set to 0 here because we don't have an active database yet (and we + may not have an active database to set. + */ + sctx->db_access=0; + + /* Change a database if necessary */ + if (mpvio.db.length) + { + if (mysql_change_db(thd, &mpvio.db, FALSE)) + { + /* mysql_change_db() has pushed the error message. */ + if (thd->user_connect) + { + decrease_user_connections(thd->user_connect); + thd->user_connect= 0; + } + DBUG_RETURN (1); + } + } + + if (mpvio.auth_info.external_user[0]) + sctx->external_user= my_strdup(mpvio.auth_info.external_user, MYF(0)); + + if (res == CR_OK_HANDSHAKE_COMPLETE) + thd->stmt_da->disable_status(); + else + my_ok(thd); + +#if defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) + /* + Allow the network layer to skip big packets. Although a malicious + authenticated session might use this to trick the server to read + big packets indefinitely, this is a previously established behavior + that needs to be preserved as to not break backwards compatibility. + */ + thd->net.skip_big_packet= TRUE; +#endif + + /* Ready to handle queries */ + DBUG_RETURN (0); +} + +/** + MySQL Server Password Authentication Plugin + + In the MySQL authentication protocol: + 1. the server sends the random scramble to the client + 2. client sends the encrypted password back to the server + 3. the server checks the password. +*/ +static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) +{ + uchar *pkt; + int pkt_len; + MPVIO_EXT *mpvio=(MPVIO_EXT*)vio; + + DBUG_ENTER ("native_password_authenticate"); + + /* generate the scramble, or reuse the old one */ + if (mpvio->scramble[SCRAMBLE_LENGTH]) + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)mpvio->scramble, SCRAMBLE_LENGTH + 1)) + return CR_ERROR; + + /* reply and authenticate */ + + /* + <digression> + This is more complex than it looks. + + The plugin (we) may be called right after the client was connected - + and will need to send a scramble, read reply, authenticate. + + Or the plugin may be called after another plugin has sent a scramble, + and read the reply. If the client has used the correct client-plugin, + we won't need to read anything here from the client, the client + has already sent a reply with everything we need for authentication. + + Or the plugin may be called after another plugin has sent a scramble, + and read the reply, but the client has used the wrong client-plugin. + We'll need to sent a "switch to another plugin" packet to the + client and read the reply. "Use the short scramble" packet is a special + case of "switch to another plugin" packet. + + Or, perhaps, the plugin may be called after another plugin has + done the handshake but did not send a useful scramble. We'll need + to send a scramble (and perhaps a "switch to another plugin" packet) + and read the reply. + + Besides, a client may be an old one, that doesn't understand plugins. + Or doesn't even understand 4.0 scramble. + + And we want to keep the same protocol on the wire unless non-native + plugins are involved. + + Anyway, it still looks simple from a plugin point of view: + "send the scramble, read the reply and authenticate" + All the magic is transparently handled by the server. + </digression> + */ + + /* read the reply with the encrypted password */ + if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) + DBUG_RETURN (CR_ERROR); + DBUG_PRINT ("info", ("reply read : pkt_len=%d", pkt_len)); + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + DBUG_RETURN (CR_OK); +#endif + + if (pkt_len == 0) /* no password */ + DBUG_RETURN (info->auth_string[0] ? CR_ERROR : CR_OK); + + info->password_used = 1; + if (pkt_len == SCRAMBLE_LENGTH) + { + if (!mpvio->acl_user->salt_len) + DBUG_RETURN(CR_ERROR); + + DBUG_RETURN (check_scramble(pkt, mpvio->scramble, mpvio->acl_user->salt) ? + CR_ERROR : CR_OK); + } + + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), mpvio->auth_info.host_or_ip); + DBUG_RETURN (CR_ERROR); +} + +static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) +{ + uchar *pkt; + int pkt_len; + MPVIO_EXT *mpvio=(MPVIO_EXT*)vio; + + /* generate the scramble, or reuse the old one */ + if (mpvio->scramble[SCRAMBLE_LENGTH]) + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)mpvio->scramble, SCRAMBLE_LENGTH + 1)) + return CR_ERROR; + + /* read the reply and authenticate */ + if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) + return CR_ERROR; + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + return CR_OK; +#endif + + /* + legacy: if switch_from_long_to_short_scramble, + the password is sent \0-terminated, the pkt_len is always 9 bytes. + We need to figure out the correct scramble length here. + */ + if (pkt_len == SCRAMBLE_LENGTH_323+1) + pkt_len= strnlen((char*)pkt, pkt_len); + + if (pkt_len == 0) /* no password */ + return info->auth_string[0] ? CR_ERROR : CR_OK; + + if (secure_auth(mpvio)) + return CR_ERROR; + + info->password_used = 1; + + if (pkt_len == SCRAMBLE_LENGTH_323) + { + if (!mpvio->acl_user->salt_len) + return CR_ERROR; + + return check_scramble_323(pkt, mpvio->scramble, + (ulong *)mpvio->acl_user->salt) ? CR_ERROR : CR_OK; + } + + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), mpvio->auth_info.host_or_ip); + return CR_ERROR; +} + +static struct st_mysql_auth native_password_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + native_password_plugin_name.str, + native_password_authenticate +}; + +static struct st_mysql_auth old_password_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + old_password_plugin_name.str, + old_password_authenticate +}; + +mysql_declare_plugin(mysql_password) +{ + MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ + &native_password_handler, /* type descriptor */ + native_password_plugin_name.str, /* Name */ + "R.J.Silk, Sergei Golubchik", /* Author */ + "Native MySQL authentication", /* Description */ + PLUGIN_LICENSE_GPL, /* License */ + NULL, /* Init function */ + NULL, /* Deinit function */ + 0x0100, /* Version (1.0) */ + NULL, /* status variables */ + NULL, /* system variables */ + NULL /* config options */ +}, +{ + MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ + &old_password_handler, /* type descriptor */ + old_password_plugin_name.str, /* Name */ + "R.J.Silk, Sergei Golubchik", /* Author */ + "Old MySQL-4.0 authentication", /* Description */ + PLUGIN_LICENSE_GPL, /* License */ + NULL, /* Init function */ + NULL, /* Deinit function */ + 0x0100, /* Version (1.0) */ + NULL, /* status variables */ + NULL, /* system variables */ + NULL /* config options */ +} +mysql_declare_plugin_end; + diff --git a/sql/sql_acl.h b/sql/sql_acl.h index c0b536b7740..3a7eefa058c 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -173,53 +173,6 @@ enum mysql_db_table_field extern const TABLE_FIELD_DEF mysql_db_table_def; extern bool mysql_user_table_is_in_short_password_format; -/* Classes */ - -struct acl_host_and_ip -{ - char *hostname; - long ip,ip_mask; // Used with masked ip:s -}; - - -class ACL_ACCESS { -public: - ulong sort; - ulong access; -}; - - -/* ACL_HOST is used if no host is specified */ - -class ACL_HOST :public ACL_ACCESS -{ -public: - acl_host_and_ip host; - char *db; -}; - - -class ACL_USER :public ACL_ACCESS -{ -public: - acl_host_and_ip host; - uint hostname_length; - USER_RESOURCES user_resource; - char *user; - uint8 salt[SCRAMBLE_LENGTH+1]; // scrambled password in binary form - uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 3.23, 20 - 4.1.1 - enum SSL_type ssl_type; - const char *ssl_cipher, *x509_issuer, *x509_subject; -}; - - -class ACL_DB :public ACL_ACCESS -{ -public: - acl_host_and_ip host; - char *user,*db; -}; - /* prototypes */ bool hostname_requires_resolving(const char *hostname); @@ -228,17 +181,16 @@ my_bool acl_reload(THD *thd); void acl_free(bool end=0); ulong acl_get(const char *host, const char *ip, const char *user, const char *db, my_bool db_is_pattern); -int acl_getroot(THD *thd, USER_RESOURCES *mqh, const char *passwd, - uint passwd_len); -bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, - char *ip, char *db); +bool acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len); +bool acl_getroot(Security_context *sctx, char *user, char *host, + char *ip, char *db); bool acl_check_host(const char *host, const char *ip); int check_change_password(THD *thd, const char *host, const char *user, char *password, uint password_len); bool change_password(THD *thd, const char *host, const char *user, char *password); bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &user_list, - ulong rights, bool revoke); + ulong rights, bool revoke, bool is_proxy); int mysql_table_grant(THD *thd, TABLE_LIST *table, List <LEX_USER> &user_list, List <LEX_COLUMN> &column_list, ulong rights, bool revoke); @@ -420,4 +372,6 @@ get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, const char *schema_name, const char *table_name); +bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user, + bool with_grant); #endif /* SQL_ACL_INCLUDED */ diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 5b6962b9ecb..aad868dc1ab 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -40,7 +40,7 @@ static inline uint make_user_name(THD *thd, char *buf) { Security_context *sctx= thd->security_ctx; return strxnmov(buf, MAX_USER_HOST_SIZE, - sctx->priv_user ? sctx->priv_user : "", "[", + sctx->priv_user[0] ? sctx->priv_user : "", "[", sctx->user ? sctx->user : "", "] @ ", sctx->host ? sctx->host : "", " [", sctx->ip ? sctx->ip : "", "]", NullS) - buf; diff --git a/sql/sql_builtin.cc.in b/sql/sql_builtin.cc.in index d9d9b610baf..8265c781e79 100644 --- a/sql/sql_builtin.cc.in +++ b/sql/sql_builtin.cc.in @@ -23,7 +23,7 @@ extern "C" extern #endif builtin_plugin - @mysql_mandatory_plugins@ @mysql_optional_plugins@ builtin_binlog_plugin; + @mysql_mandatory_plugins@ @mysql_optional_plugins@ builtin_binlog_plugin, builtin_mysql_password_plugin; struct st_mysql_plugin *mysql_optional_plugins[]= { @@ -32,5 +32,5 @@ struct st_mysql_plugin *mysql_optional_plugins[]= struct st_mysql_plugin *mysql_mandatory_plugins[]= { - builtin_binlog_plugin, @mysql_mandatory_plugins@ 0 + builtin_binlog_plugin, builtin_mysql_password_plugin, @mysql_mandatory_plugins@ 0 }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 60a871e9e88..153258c9272 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -532,7 +532,8 @@ THD::THD() catalog= (char*)"std"; // the only catalog we have for now main_security_ctx.init(); security_ctx= &main_security_ctx; - no_errors=password= 0; + no_errors= 0; + password= 0; query_start_used= 0; count_cuted_fields= CHECK_FIELD_IGNORE; killed= NOT_KILLED; @@ -1325,6 +1326,20 @@ void THD::cleanup_after_query() } +LEX_STRING * +make_lex_string_root(MEM_ROOT *mem_root, + LEX_STRING *lex_str, const char* str, uint length, + bool allocate_lex_string) +{ + if (allocate_lex_string) + if (!(lex_str= (LEX_STRING *)alloc_root(mem_root, sizeof(LEX_STRING)))) + return 0; + if (!(lex_str->str= strmake_root(mem_root, str, length))) + return 0; + lex_str->length= length; + return lex_str; +} + /** Create a LEX_STRING in this connection. @@ -1339,13 +1354,8 @@ LEX_STRING *THD::make_lex_string(LEX_STRING *lex_str, const char* str, uint length, bool allocate_lex_string) { - if (allocate_lex_string) - if (!(lex_str= (LEX_STRING *)alloc(sizeof(LEX_STRING)))) - return 0; - if (!(lex_str->str= strmake_root(mem_root, str, length))) - return 0; - lex_str->length= length; - return lex_str; + return make_lex_string_root (mem_root, lex_str, str, + length, allocate_lex_string); } @@ -2911,9 +2921,9 @@ void THD::set_status_var_init() void Security_context::init() { - host= user= priv_user= ip= 0; + host= user= ip= external_user= 0; host_or_ip= "connecting host"; - priv_host[0]= '\0'; + priv_user[0]= priv_host[0]= '\0'; master_access= 0; #ifndef NO_EMBEDDED_ACCESS_CHECKS db_access= NO_ACCESS; @@ -2935,6 +2945,12 @@ void Security_context::destroy() user= NULL; } + if (external_user) + { + my_free(external_user); + user= NULL; + } + my_free(ip); ip= NULL; } @@ -2945,8 +2961,7 @@ void Security_context::skip_grants() /* privileges for the user are unknown everything is allowed */ host_or_ip= (char *)""; master_access= ~NO_ACCESS; - priv_user= (char *)""; - *priv_host= '\0'; + *priv_user= *priv_host= '\0'; } @@ -2988,7 +3003,7 @@ bool Security_context::set_user(char *user_arg) of a statement under credentials of a different user, e.g. definer of a procedure, we authenticate this user in a local instance of Security_context by means of this method (and - ultimately by means of acl_getroot_no_password), and make the + ultimately by means of acl_getroot), and make the local instance active in the thread by re-setting thd->security_ctx pointer. @@ -3022,19 +3037,12 @@ change_security_context(THD *thd, DBUG_ASSERT(definer_user->str && definer_host->str); *backup= NULL; - /* - The current security context may have NULL members - if we have just started the thread and not authenticated - any user. This use case is currently in events worker thread. - */ - needs_change= (thd->security_ctx->priv_user == NULL || - strcmp(definer_user->str, thd->security_ctx->priv_user) || - thd->security_ctx->priv_host == NULL || + needs_change= (strcmp(definer_user->str, thd->security_ctx->priv_user) || my_strcasecmp(system_charset_info, definer_host->str, thd->security_ctx->priv_host)); if (needs_change) { - if (acl_getroot_no_password(this, definer_user->str, definer_host->str, + if (acl_getroot(this, definer_user->str, definer_host->str, definer_host->str, db->str)) { my_error(ER_NO_SUCH_USER, MYF(0), definer_user->str, @@ -3392,6 +3400,10 @@ void THD::get_definer(LEX_USER *definer) definer->host= invoker_host; definer->password.str= NULL; definer->password.length= 0; + definer->plugin.str= (char *) ""; + definer->plugin.length= 0; + definer->auth.str= (char *) ""; + definer->auth.length= 0; } else #endif diff --git a/sql/sql_class.h b/sql/sql_class.h index b23b65dae2f..53cc3d4251b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -862,9 +862,13 @@ public: priv_user - The user privilege we are using. May be "" for anonymous user. ip - client IP */ - char *host, *user, *priv_user, *ip; + char *host, *user, *ip; + char priv_user[USERNAME_LENGTH]; + char proxy_user[USERNAME_LENGTH + MAX_HOSTNAME + 5]; /* The host privilege we are using */ char priv_host[MAX_HOSTNAME]; + /* The external user (if available) */ + char *external_user; /* points to host if host is available, otherwise points to ip */ const char *host_or_ip; ulong master_access; /* Global privileges from mysql.user */ @@ -2100,7 +2104,8 @@ public: char scramble[SCRAMBLE_LENGTH+1]; bool slave_thread, one_shot_set; - bool no_errors, password; + bool no_errors; + uchar password; /** Set to TRUE if execution of the current compound statement can not continue. In particular, disables activation of @@ -2910,6 +2915,11 @@ my_eof(THD *thd) #define reenable_binlog(A) (A)->variables.option_bits= tmp_disable_binlog__save_options;} +LEX_STRING * +make_lex_string_root(MEM_ROOT *mem_root, + LEX_STRING *lex_str, const char* str, uint length, + bool allocate_lex_string); + /* Used to hold information about file and file structure in exchange via non-DB file (...INTO OUTFILE..., ...LOAD DATA...) diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 003203b5466..4822e2d66c3 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -61,9 +61,9 @@ #ifndef NO_EMBEDDED_ACCESS_CHECKS static HASH hash_user_connections; -static int get_or_create_user_conn(THD *thd, const char *user, - const char *host, - USER_RESOURCES *mqh) +int get_or_create_user_conn(THD *thd, const char *user, + const char *host, + const USER_RESOURCES *mqh) { int return_val= 0; size_t temp_len, user_len; @@ -129,7 +129,6 @@ end: 1 error */ -static int check_for_max_user_connections(THD *thd, USER_CONN *uc) { int error=0; @@ -290,256 +289,6 @@ end: #endif /* NO_EMBEDDED_ACCESS_CHECKS */ - -/** - Check if user exist and password supplied is correct. - - @param thd thread handle, thd->security_ctx->{host,user,ip} are used - @param command originator of the check: now check_user is called - during connect and change user procedures; used for - logging. - @param passwd scrambled password received from client - @param passwd_len length of scrambled password - @param db database name to connect to, may be NULL - @param check_count TRUE if establishing a new connection. In this case - check that we have not exceeded the global - max_connections limist - - @note Host, user and passwd may point to communication buffer. - Current implementation does not depend on that, but future changes - should be done with this in mind; 'thd' is INOUT, all other params - are 'IN'. - - @retval 0 OK; thd->security_ctx->user/master_access/priv_user/db_access and - thd->db are updated; OK is sent to the client. - @retval 1 error, e.g. access denied or handshake error, not sent to - the client. A message is pushed into the error stack. -*/ - -int -check_user(THD *thd, enum enum_server_command command, - const char *passwd, uint passwd_len, const char *db, - bool check_count) -{ - DBUG_ENTER("check_user"); - LEX_STRING db_str= { (char *) db, db ? strlen(db) : 0 }; - - /* - Clear thd->db as it points to something, that will be freed when - connection is closed. We don't want to accidentally free a wrong - pointer if connect failed. Also in case of 'CHANGE USER' failure, - current database will be switched to 'no database selected'. - */ - thd->reset_db(NULL, 0); - -#ifdef NO_EMBEDDED_ACCESS_CHECKS - thd->main_security_ctx.master_access= GLOBAL_ACLS; // Full rights - /* Change database if necessary */ - if (db && db[0]) - { - if (mysql_change_db(thd, &db_str, FALSE)) - DBUG_RETURN(1); - } - my_ok(thd); - DBUG_RETURN(0); -#else - - my_bool opt_secure_auth_local; - mysql_mutex_lock(&LOCK_global_system_variables); - opt_secure_auth_local= opt_secure_auth; - mysql_mutex_unlock(&LOCK_global_system_variables); - - /* - If the server is running in secure auth mode, short scrambles are - forbidden. - */ - if (opt_secure_auth_local && passwd_len == SCRAMBLE_LENGTH_323) - { - my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); - general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); - DBUG_RETURN(1); - } - if (passwd_len != 0 && - passwd_len != SCRAMBLE_LENGTH && - passwd_len != SCRAMBLE_LENGTH_323) - { - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - DBUG_RETURN(1); - } - - USER_RESOURCES ur; - int res= acl_getroot(thd, &ur, passwd, passwd_len); -#ifndef EMBEDDED_LIBRARY - if (res == -1) - { - /* - This happens when client (new) sends password scrambled with - scramble(), but database holds old value (scrambled with - scramble_323()). Here we please client to send scrambled_password - in old format. - */ - NET *net= &thd->net; - if (opt_secure_auth_local) - { - my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip); - general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip); - DBUG_RETURN(1); - } - /* We have to read very specific packet size */ - if (send_old_password_request(thd) || - my_net_read(net) != SCRAMBLE_LENGTH_323 + 1) - { - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - DBUG_RETURN(1); - } - /* Final attempt to check the user based on reply */ - /* So as passwd is short, errcode is always >= 0 */ - res= acl_getroot(thd, &ur, (char *) net->read_pos, SCRAMBLE_LENGTH_323); - } -#endif /*EMBEDDED_LIBRARY*/ - /* here res is always >= 0 */ - if (res == 0) - { - if (!(thd->main_security_ctx.master_access & - NO_ACCESS)) // authentication is OK - { - DBUG_PRINT("info", - ("Capabilities: %lu packet_length: %ld Host: '%s' " - "Login user: '%s' Priv_user: '%s' Using password: %s " - "Access: %lu db: '%s'", - thd->client_capabilities, - thd->max_client_packet_length, - thd->main_security_ctx.host_or_ip, - thd->main_security_ctx.user, - thd->main_security_ctx.priv_user, - passwd_len ? "yes": "no", - thd->main_security_ctx.master_access, - (thd->db ? thd->db : "*none*"))); - - if (check_count) - { - mysql_mutex_lock(&LOCK_connection_count); - bool count_ok= connection_count <= max_connections || - (thd->main_security_ctx.master_access & SUPER_ACL); - mysql_mutex_unlock(&LOCK_connection_count); - - if (!count_ok) - { // too many connections - my_error(ER_CON_COUNT_ERROR, MYF(0)); - DBUG_RETURN(1); - } - } - - /* - Log the command before authentication checks, so that the user can - check the log for the tried login tried and also to detect - break-in attempts. - */ - general_log_print(thd, command, - (thd->main_security_ctx.priv_user == - thd->main_security_ctx.user ? - (char*) "%s@%s on %s" : - (char*) "%s@%s as anonymous on %s"), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip, - db ? db : (char*) ""); - - /* - This is the default access rights for the current database. It's - set to 0 here because we don't have an active database yet (and we - may not have an active database to set. - */ - thd->main_security_ctx.db_access=0; - - /* Don't allow user to connect if he has done too many queries */ - if ((ur.questions || ur.updates || ur.conn_per_hour || ur.user_conn || - global_system_variables.max_user_connections) && - get_or_create_user_conn(thd, - (opt_old_style_user_limits ? thd->main_security_ctx.user : - thd->main_security_ctx.priv_user), - (opt_old_style_user_limits ? thd->main_security_ctx.host_or_ip : - thd->main_security_ctx.priv_host), - &ur)) - { - /* The error is set by get_or_create_user_conn(). */ - DBUG_RETURN(1); - } - if (thd->user_connect && - (thd->user_connect->user_resources.conn_per_hour || - thd->user_connect->user_resources.user_conn || - global_system_variables.max_user_connections) && - check_for_max_user_connections(thd, thd->user_connect)) - { - /* The error is set in check_for_max_user_connections(). */ - DBUG_RETURN(1); - } - - /* Change database if necessary */ - if (db && db[0]) - { - if (mysql_change_db(thd, &db_str, FALSE)) - { - /* mysql_change_db() has pushed the error message. */ - if (thd->user_connect) - { - decrease_user_connections(thd->user_connect); - thd->user_connect= 0; - } - DBUG_RETURN(1); - } - } - my_ok(thd); - thd->password= test(passwd_len); // remember for error messages -#ifndef EMBEDDED_LIBRARY - /* - Allow the network layer to skip big packets. Although a malicious - authenticated session might use this to trick the server to read - big packets indefinitely, this is a previously established behavior - that needs to be preserved as to not break backwards compatibility. - */ - thd->net.skip_big_packet= TRUE; -#endif - /* Ready to handle queries */ - DBUG_RETURN(0); - } - } - else if (res == 2) // client gave short hash, server has long hash - { - my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); - general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); - DBUG_RETURN(1); - } - my_error(ER_ACCESS_DENIED_ERROR, MYF(0), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip, - passwd_len ? ER(ER_YES) : ER(ER_NO)); - general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip, - passwd_len ? ER(ER_YES) : ER(ER_NO)); - /* - Log access denied messages to the error log when log-warnings = 2 - so that the overhead of the general query log is not required to track - failed connections. - */ - if (global_system_variables.log_warnings > 1) - { - sql_print_warning(ER(ER_ACCESS_DENIED_ERROR), - thd->main_security_ctx.user, - thd->main_security_ctx.host_or_ip, - passwd_len ? ER(ER_YES) : ER(ER_NO)); - } - DBUG_RETURN(1); -#endif /* NO_EMBEDDED_ACCESS_CHECKS */ -} - - /* Check for maximum allowable user connections, if the mysqld server is started with corresponding variable that is greater then 0. @@ -672,17 +421,14 @@ bool init_new_connection_handler_thread() thd thread handle RETURN - 0 success, OK is sent to user, thd is updated. - -1 error, which is sent to user - > 0 error code (not sent to user) + 0 success, thd is updated. + 1 error */ static int check_connection(THD *thd) { uint connect_errors= 0; NET *net= &thd->net; - ulong pkt_len= 0; - char *end; DBUG_PRINT("info", ("New connection received on %s", vio_description(net->vio))); @@ -747,203 +493,10 @@ static int check_connection(THD *thd) } vio_keepalive(net->vio, TRUE); - ulong server_capabilites; - { - /* buff[] needs to big enough to hold the server_version variable */ - char buff[SERVER_VERSION_LENGTH + 1 + SCRAMBLE_LENGTH + 1 + 64]; - server_capabilites= CLIENT_BASIC_FLAGS; - - if (opt_using_transactions) - server_capabilites|= CLIENT_TRANSACTIONS; -#ifdef HAVE_COMPRESS - server_capabilites|= CLIENT_COMPRESS; -#endif /* HAVE_COMPRESS */ -#if defined(HAVE_OPENSSL) - if (ssl_acceptor_fd) - { - server_capabilites |= CLIENT_SSL; /* Wow, SSL is available! */ - server_capabilites |= CLIENT_SSL_VERIFY_SERVER_CERT; - } -#endif /* HAVE_OPENSSL */ - - end= strnmov(buff, server_version, SERVER_VERSION_LENGTH) + 1; - int4store((uchar*) end, thd->thread_id); - end+= 4; - /* - So as check_connection is the only entry point to authorization - procedure, scramble is set here. This gives us new scramble for - each handshake. - */ - create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); - /* - Old clients does not understand long scrambles, but can ignore packet - tail: that's why first part of the scramble is placed here, and second - part at the end of packet. - */ - end= strmake(end, thd->scramble, SCRAMBLE_LENGTH_323) + 1; - - int2store(end, server_capabilites); - /* write server characteristics: up to 16 bytes allowed */ - end[2]=(char) default_charset_info->number; - int2store(end+3, thd->server_status); - bzero(end+5, 13); - end+= 18; - /* write scramble tail */ - end= strmake(end, thd->scramble + SCRAMBLE_LENGTH_323, - SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323) + 1; - - /* At this point we write connection message and read reply */ - if (net_write_command(net, (uchar) protocol_version, (uchar*) "", 0, - (uchar*) buff, (size_t) (end-buff)) || - (pkt_len= my_net_read(net)) == packet_error || - pkt_len < MIN_HANDSHAKE_SIZE) - { - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), - thd->main_security_ctx.host_or_ip); - return 1; - } - } -#ifdef _CUSTOMCONFIG_ -#include "_cust_sql_parse.h" -#endif - if (connect_errors) - reset_host_errors(thd->main_security_ctx.ip); if (thd->packet.alloc(thd->variables.net_buffer_length)) return 1; /* The error is set by alloc(). */ - thd->client_capabilities= uint2korr(net->read_pos); - if (thd->client_capabilities & CLIENT_PROTOCOL_41) - { - thd->client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; - thd->max_client_packet_length= uint4korr(net->read_pos+4); - DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8])); - thd_init_client_charset(thd, (uint) net->read_pos[8]); - thd->update_charset(); - end= (char*) net->read_pos+32; - } - else - { - thd->max_client_packet_length= uint3korr(net->read_pos+2); - end= (char*) net->read_pos+5; - } - /* - Disable those bits which are not supported by the server. - This is a precautionary measure, if the client lies. See Bug#27944. - */ - thd->client_capabilities&= server_capabilites; - - if (thd->client_capabilities & CLIENT_IGNORE_SPACE) - thd->variables.sql_mode|= MODE_IGNORE_SPACE; -#if defined(HAVE_OPENSSL) - DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities)); - if (thd->client_capabilities & CLIENT_SSL) - { - /* Do the SSL layering. */ - if (!ssl_acceptor_fd) - { - inc_host_errors(thd->main_security_ctx.ip); - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - return 1; - } - DBUG_PRINT("info", ("IO layer change in progress...")); - if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout)) - { - DBUG_PRINT("error", ("Failed to accept new SSL connection")); - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - return 1; - } - DBUG_PRINT("info", ("Reading user information over SSL layer")); - if ((pkt_len= my_net_read(net)) == packet_error || - pkt_len < NORMAL_HANDSHAKE_SIZE) - { - DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", - pkt_len)); - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - return 1; - } - } -#endif /* HAVE_OPENSSL */ - - if (end >= (char*) net->read_pos+ pkt_len +2) - { - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - return 1; - } - - if (thd->client_capabilities & CLIENT_INTERACTIVE) - thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; - if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && - opt_using_transactions) - net->return_status= &thd->server_status; - - char *user= end; - char *passwd= strend(user)+1; - uint user_len= passwd - user - 1; - char *db= passwd; - char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 - char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 - uint dummy_errors; - - /* - Old clients send null-terminated string as password; new clients send - the size (1 byte) + string (not null-terminated). Hence in case of empty - password both send '\0'. - - This strlen() can't be easily deleted without changing protocol. - - Cast *passwd to an unsigned char, so that it doesn't extend the sign for - *passwd > 127 and become 2**32-127+ after casting to uint. - */ - uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar)(*passwd++) : strlen(passwd); - db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? - db + passwd_len + 1 : 0; - /* strlen() can't be easily deleted without changing protocol */ - uint db_len= db ? strlen(db) : 0; - - if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len) - { - inc_host_errors(thd->main_security_ctx.ip); - - my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); - return 1; - } - - /* Since 4.1 all database names are stored in utf8 */ - if (db) - { - db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, - system_charset_info, - db, db_len, - thd->charset(), &dummy_errors)]= 0; - db= db_buff; - } - - user_buff[user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, - system_charset_info, user, user_len, - thd->charset(), &dummy_errors)]= '\0'; - user= user_buff; - - /* If username starts and ends in "'", chop them off */ - if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'') - { - user[user_len-1]= 0; - user++; - user_len-= 2; - } - - my_free(thd->main_security_ctx.user); - if (!(thd->main_security_ctx.user= my_strdup(user, MYF(MY_WME)))) - return 1; /* The error is set by my_strdup(). */ - return check_user(thd, COM_CONNECT, passwd, passwd_len, db, TRUE); + return acl_authenticate(thd, connect_errors, 0); } diff --git a/sql/sql_connect.h b/sql/sql_connect.h index 2334b7303be..06cb4287f3a 100644 --- a/sql/sql_connect.h +++ b/sql/sql_connect.h @@ -40,4 +40,8 @@ int check_user(THD *thd, enum enum_server_command command, const char *passwd, uint passwd_len, const char *db, bool check_count); +int get_or_create_user_conn(THD *thd, const char *user, + const char *host, const USER_RESOURCES *mqh); +int check_for_max_user_connections(THD *thd, USER_CONN *uc); + #endif /* SQL_CONNECT_INCLUDED */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index a0d347f48de..85c7b807ea6 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1811,8 +1811,10 @@ public: table(0),tables_in_use(0),stacked_inserts(0), status(0), group_count(0) { DBUG_ENTER("Delayed_insert constructor"); - thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; + thd.security_ctx->user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; + strmake(thd.security_ctx->priv_user, thd.security_ctx->user, + USERNAME_LENGTH); thd.current_tablenr=0; thd.command=COM_DELAYED_INSERT; thd.lex->current_select= 0; // for my_message_sql diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 6411b194a8a..9d9348acc02 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -41,6 +41,7 @@ sys_var *trg_new_row_fake_var= (sys_var*) 0x01; LEX_STRING constant for null-string to be used in parser and other places. */ const LEX_STRING null_lex_str= {NULL, 0}; +const LEX_STRING empty_lex_str= {"", 0}; /** @note The order of the elements of this array must correspond to the order of elements in enum_binlog_stmt_unsafe. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index f1b558b8be4..a5a6c34f30a 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -982,6 +982,8 @@ struct st_sp_chistics enum enum_sp_data_access daccess; }; +extern const LEX_STRING null_lex_str; +extern const LEX_STRING empty_lex_str; struct st_trg_chistics { @@ -2037,6 +2039,7 @@ struct LEX: public Query_tables_list sp_name *spname; bool sp_lex_in_use; /* Keep track on lex usage in SPs for error handling */ bool all_privileges; + bool proxy_priv; sp_pcontext *spcont; st_sp_chistics sp_chistics; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2ef8e9761b1..8a59189e3eb 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -501,9 +501,8 @@ static void handle_bootstrap_impl(THD *thd) #endif /* EMBEDDED_LIBRARY */ thd_proc_info(thd, 0); - thd->security_ctx->priv_user= - thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); - thd->security_ctx->priv_host[0]=0; + thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); + thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]=0; /* Make the "client" handle multiple results. This is necessary to enable stored procedures with SELECTs and Dynamic SQL @@ -976,96 +975,34 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_CHANGE_USER: { status_var_increment(thd->status_var.com_other); - char *user= (char*) packet, *packet_end= packet + packet_length; - /* Safe because there is always a trailing \0 at the end of the packet */ - char *passwd= strend(user)+1; thd->change_user(); thd->clear_error(); // if errors from rollback - /* - Old clients send null-terminated string ('\0' for empty string) for - password. New clients send the size (1 byte) + string (not null - terminated, so also '\0' for empty string). + /* acl_authenticate() takes the data from net->read_pos */ + net->read_pos= (uchar*)packet; - Cast *passwd to an unsigned char, so that it doesn't extend the sign - for *passwd > 127 and become 2**32-127 after casting to uint. - */ - char db_buff[NAME_LEN+1]; // buffer to store db in utf8 - char *db= passwd; - char *save_db; - /* - If there is no password supplied, the packet must contain '\0', - in any type of handshake (4.1 or pre-4.1). - */ - if (passwd >= packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar)(*passwd++) : strlen(passwd)); - uint dummy_errors, save_db_length, db_length; - int res; + uint save_db_length= thd->db_length; + char *save_db= thd->db; + USER_CONN *save_user_connect= thd->user_connect; Security_context save_security_ctx= *thd->security_ctx; - USER_CONN *save_user_connect; - - db+= passwd_len + 1; - /* - Database name is always NUL-terminated, so in case of empty database - the packet must contain at least the trailing '\0'. - */ - if (db >= packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - db_length= strlen(db); - - char *ptr= db + db_length + 1; - uint cs_number= 0; - - if (ptr < packet_end) - { - if (ptr + 2 > packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - - cs_number= uint2korr(ptr); - } - - /* Convert database name to utf8 */ - db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, - system_charset_info, db, db_length, - thd->charset(), &dummy_errors)]= 0; - db= db_buff; + CHARSET_INFO *save_character_set_client= + thd->variables.character_set_client; + CHARSET_INFO *save_collation_connection= + thd->variables.collation_connection; + CHARSET_INFO *save_character_set_results= + thd->variables.character_set_results; - /* Save user and privileges */ - save_db_length= thd->db_length; - save_db= thd->db; - save_user_connect= thd->user_connect; - - if (!(thd->security_ctx->user= my_strdup(user, MYF(0)))) - { - thd->security_ctx->user= save_security_ctx.user; - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - break; - } - - /* Clear variables that are allocated */ - thd->user_connect= 0; - thd->security_ctx->priv_user= thd->security_ctx->user; - res= check_user(thd, COM_CHANGE_USER, passwd, passwd_len, db, FALSE); - - if (res) + if (acl_authenticate(thd, 0, packet_length)) { my_free(thd->security_ctx->user); *thd->security_ctx= save_security_ctx; thd->user_connect= save_user_connect; - thd->db= save_db; - thd->db_length= save_db_length; + thd->reset_db (save_db, save_db_length); + thd->variables.character_set_client= save_character_set_client; + thd->variables.collation_connection= save_collation_connection; + thd->variables.character_set_results= save_character_set_results; + thd->update_charset(); } else { @@ -1076,12 +1013,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif /* NO_EMBEDDED_ACCESS_CHECKS */ my_free(save_db); my_free(save_security_ctx.user); - - if (cs_number) - { - thd_init_client_charset(thd, cs_number); - thd->update_charset(); - } } break; } @@ -3844,7 +3775,8 @@ end_with_restore_list: case SQLCOM_REVOKE: case SQLCOM_GRANT: { - if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, + if (lex->type != TYPE_ENUM_PROXY && + check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, first_table ? first_table->db : select_lex->db, first_table ? &first_table->grant.privilege : NULL, first_table ? &first_table->grant.m_internal : NULL, @@ -3854,6 +3786,7 @@ end_with_restore_list: if (thd->security_ctx->user) // If not replication { LEX_USER *user, *tmp_user; + bool first_user= TRUE; List_iterator <LEX_USER> user_list(lex->users_list); while ((tmp_user= user_list++)) @@ -3868,20 +3801,23 @@ end_with_restore_list: user->host.str); // Are we trying to change a password of another user DBUG_ASSERT(user->host.str != 0); - if (strcmp(thd->security_ctx->user, user->user.str) || - my_strcasecmp(system_charset_info, - user->host.str, thd->security_ctx->host_or_ip)) + + /* + GRANT/REVOKE PROXY has the target user as a first entry in the list. + */ + if (lex->type == TYPE_ENUM_PROXY && first_user) { - // TODO: use check_change_password() - if (is_acl_user(user->host.str, user->user.str) && - user->password.str && - check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1)) - { - my_message(ER_PASSWORD_NOT_ALLOWED, - ER(ER_PASSWORD_NOT_ALLOWED), MYF(0)); + first_user= FALSE; + if (acl_check_proxy_grant_access (thd, user->host.str, user->user.str, + lex->grant & GRANT_ACL)) goto error; - } - } + } + else if (is_acl_user(user->host.str, user->user.str) && + user->password.str && + check_change_password (thd, user->host.str, user->user.str, + user->password.str, + user->password.length)) + goto error; } } if (first_table) @@ -3916,16 +3852,19 @@ end_with_restore_list: } else { - if (lex->columns.elements || lex->type) + if (lex->columns.elements || (lex->type && lex->type != TYPE_ENUM_PROXY)) { my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); goto error; } else - /* Conditionally writes to binlog */ - res = mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, - lex->sql_command == SQLCOM_REVOKE); + { + /* Conditionally writes to binlog */ + res = mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, + lex->sql_command == SQLCOM_REVOKE, + lex->type == TYPE_ENUM_PROXY); + } if (!res) { if (lex->sql_command == SQLCOM_GRANT) @@ -4221,8 +4160,8 @@ end_with_restore_list: if (sp_grant_privileges(thd, lex->sphead->m_db.str, name, lex->sql_command == SQLCOM_CREATE_PROCEDURE)) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PROC_AUTO_GRANT_FAIL, - ER(ER_PROC_AUTO_GRANT_FAIL)); + ER_PROC_AUTO_GRANT_FAIL, ER(ER_PROC_AUTO_GRANT_FAIL)); + thd->clear_error(); } /* @@ -5108,12 +5047,19 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, { // We can never grant this DBUG_PRINT("error",("No possible access")); if (!no_errors) - my_error(ER_ACCESS_DENIED_ERROR, MYF(0), - sctx->priv_user, - sctx->priv_host, - (thd->password ? - ER(ER_YES) : - ER(ER_NO))); /* purecov: tested */ + { + if (thd->password == 2) + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + sctx->priv_user, + sctx->priv_host); + else + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + sctx->priv_user, + sctx->priv_host, + (thd->password ? + ER(ER_YES) : + ER(ER_NO))); /* purecov: tested */ + } DBUG_RETURN(TRUE); /* purecov: tested */ } @@ -7604,8 +7550,9 @@ void get_default_definer(THD *thd, LEX_USER *definer) definer->host.str= (char *) sctx->priv_host; definer->host.length= strlen(definer->host.str); - definer->password.str= NULL; - definer->password.length= 0; + definer->password= null_lex_str; + definer->plugin= empty_lex_str; + definer->auth= empty_lex_str; } diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 7e8c1fed999..f749fbec4fb 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -30,6 +30,7 @@ #include <my_pthread.h> #include <my_getopt.h> #include <mysql/plugin_audit.h> +#include <mysql/plugin_auth.h> #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #define REPORT_TO_LOG 1 #define REPORT_TO_USER 2 @@ -65,6 +66,7 @@ const LEX_STRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM]= { C_STRING_WITH_LEN("INFORMATION SCHEMA") }, { C_STRING_WITH_LEN("AUDIT") }, { C_STRING_WITH_LEN("REPLICATION") }, + { C_STRING_WITH_LEN("AUTHENTICATION") } }; extern int initialize_schema_table(st_plugin_int *plugin); @@ -81,13 +83,13 @@ extern int finalize_audit_plugin(st_plugin_int *plugin); plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { 0,ha_initialize_handlerton,0,0,initialize_schema_table, - initialize_audit_plugin + initialize_audit_plugin,0,0 }; plugin_type_init plugin_type_deinitialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { 0,ha_finalize_handlerton,0,0,finalize_schema_table, - finalize_audit_plugin + finalize_audit_plugin,0,0 }; #ifdef HAVE_DLOPEN @@ -110,7 +112,8 @@ static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_DAEMON_INTERFACE_VERSION, MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, MYSQL_AUDIT_INTERFACE_VERSION, - MYSQL_REPLICATION_INTERFACE_VERSION + MYSQL_REPLICATION_INTERFACE_VERSION, + MYSQL_AUTHENTICATION_INTERFACE_VERSION }; static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= { @@ -120,7 +123,8 @@ static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_DAEMON_INTERFACE_VERSION, MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, MYSQL_AUDIT_INTERFACE_VERSION, - MYSQL_REPLICATION_INTERFACE_VERSION + MYSQL_REPLICATION_INTERFACE_VERSION, + MYSQL_AUTHENTICATION_INTERFACE_VERSION }; /* support for Services */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ca951897055..9c712484faa 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1185,6 +1185,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token PROCESSLIST_SYM %token PROFILE_SYM %token PROFILES_SYM +%token PROXY_SYM %token PURGE %token QUARTER_SYM %token QUERY_SYM @@ -12217,6 +12218,9 @@ user: $$->user = $1; $$->host.str= (char *) "%"; $$->host.length= 1; + $$->password= null_lex_str; + $$->plugin= empty_lex_str; + $$->auth= empty_lex_str; if (check_string_char_length(&$$->user, ER(ER_USERNAME), USERNAME_CHAR_LENGTH, @@ -12229,6 +12233,9 @@ user: if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) MYSQL_YYABORT; $$->user = $1; $$->host=$3; + $$->password= null_lex_str; + $$->plugin= empty_lex_str; + $$->auth= empty_lex_str; if (check_string_char_length(&$$->user, ER(ER_USERNAME), USERNAME_CHAR_LENGTH, @@ -12491,6 +12498,7 @@ keyword_sp: | PROCESSLIST_SYM {} | PROFILE_SYM {} | PROFILES_SYM {} + | PROXY_SYM {} | QUARTER_SYM {} | QUERY_SYM {} | QUICK {} @@ -12877,7 +12885,7 @@ option_value: if (!(user=(LEX_USER*) thd->alloc(sizeof(LEX_USER)))) MYSQL_YYABORT; user->host=null_lex_str; - user->user.str=thd->security_ctx->priv_user; + user->user.str=thd->security_ctx->user; set_var_password *var= new set_var_password(user, $3); if (var == NULL) MYSQL_YYABORT; @@ -13230,6 +13238,13 @@ revoke_command: { Lex->sql_command = SQLCOM_REVOKE_ALL; } + | PROXY_SYM ON user FROM grant_list + { + LEX *lex= Lex; + lex->users_list.push_front ($3); + lex->sql_command= SQLCOM_REVOKE; + lex->type= TYPE_ENUM_PROXY; + } ; grant: @@ -13269,6 +13284,13 @@ grant_command: lex->sql_command= SQLCOM_GRANT; lex->type= TYPE_ENUM_PROCEDURE; } + | PROXY_SYM ON user TO_SYM grant_list opt_grant_option + { + LEX *lex= Lex; + lex->users_list.push_front ($3); + lex->sql_command= SQLCOM_GRANT; + lex->type= TYPE_ENUM_PROXY; + } ; opt_table: @@ -13462,6 +13484,8 @@ grant_user: user IDENTIFIED_SYM BY TEXT_STRING { $$=$1; $1->password=$4; + if (Lex->sql_command == SQLCOM_REVOKE) + MYSQL_YYABORT; if ($4.length) { if (YYTHD->variables.old_passwords) @@ -13487,7 +13511,28 @@ grant_user: } } | user IDENTIFIED_SYM BY PASSWORD TEXT_STRING - { $$= $1; $1->password= $5; } + { + if (Lex->sql_command == SQLCOM_REVOKE) + MYSQL_YYABORT; + $$= $1; + $1->password= $5; + } + | user IDENTIFIED_SYM WITH ident_or_text + { + if (Lex->sql_command == SQLCOM_REVOKE) + MYSQL_YYABORT; + $$= $1; + $1->plugin= $4; + $1->auth= empty_lex_str; + } + | user IDENTIFIED_SYM WITH ident_or_text AS TEXT_STRING_sys + { + if (Lex->sql_command == SQLCOM_REVOKE) + MYSQL_YYABORT; + $$= $1; + $1->plugin= $4; + $1->auth= $6; + } | user { $$= $1; $1->password= null_lex_str; } ; @@ -13559,6 +13604,11 @@ grant_options: | WITH grant_option_list ; +opt_grant_option: + /* empty */ {} + | WITH GRANT OPTION { Lex->grant |= GRANT_ACL;} + ; + grant_option_list: grant_option_list grant_option {} | grant_option {} diff --git a/sql/structs.h b/sql/structs.h index 5ffcc4dc62e..8290227830c 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -156,7 +156,7 @@ extern const char *show_comp_option_name[]; typedef int *(*update_var)(THD *, struct st_mysql_show_var *); typedef struct st_lex_user { - LEX_STRING user, host, password; + LEX_STRING user, host, password, plugin, auth; } LEX_USER; /* diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 320e6d9253e..6c456825aa1 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1419,6 +1419,14 @@ static Sys_var_uint Sys_protocol_version( READ_ONLY GLOBAL_VAR(protocol_version), NO_CMD_LINE, VALID_RANGE(0, ~0), DEFAULT(PROTOCOL_VERSION), BLOCK_SIZE(1)); +static Sys_var_proxy_user Sys_proxy_user( + "proxy_user", "The proxy user account name used when logging in", + IN_SYSTEM_CHARSET); + +static Sys_var_external_user Sys_exterenal_user( + "external_user", "The external user account used when logging in", + IN_SYSTEM_CHARSET); + static Sys_var_ulong Sys_read_buff_size( "read_buffer_size", "Each thread that does a sequential scan allocates a buffer of " diff --git a/sql/sys_vars.h b/sql/sys_vars.h index ca3fc99deba..9dc6890a84e 100644 --- a/sql/sys_vars.h +++ b/sql/sys_vars.h @@ -452,6 +452,67 @@ public: { return type != STRING_RESULT; } }; + +class Sys_var_proxy_user: public sys_var +{ +public: + Sys_var_proxy_user(const char *name_arg, + const char *comment, enum charset_enum is_os_charset_arg) + : sys_var(&all_sys_vars, name_arg, comment, + sys_var::READONLY+sys_var::ONLY_SESSION, 0, -1, + NO_ARG, SHOW_CHAR, (intptr)NULL, + 0, VARIABLE_NOT_IN_BINLOG, + 0, 0, + 0, 0, PARSE_NORMAL) + { + is_os_charset= is_os_charset_arg == IN_FS_CHARSET; + option.var_type= GET_STR; + } + bool do_check(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return true; + } + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return true; + } + bool global_update(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return false; + } + void session_save_default(THD *thd, set_var *var) + { DBUG_ASSERT(FALSE); } + void global_save_default(THD *thd, set_var *var) + { DBUG_ASSERT(FALSE); } + bool check_update_type(Item_result type) + { return true; } +protected: + virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base) + { + return thd->security_ctx->proxy_user[0] ? + (uchar *) &(thd->security_ctx->proxy_user[0]) : NULL; + } +}; + +class Sys_var_external_user : public Sys_var_proxy_user +{ +public: + Sys_var_external_user(const char *name_arg, const char *comment_arg, + enum charset_enum is_os_charset_arg) + : Sys_var_proxy_user (name_arg, comment_arg, is_os_charset_arg) + {} + +protected: + virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base) + { + return thd->security_ctx->proxy_user[0] ? + (uchar *) &(thd->security_ctx->proxy_user[0]) : NULL; + } +}; + /** The class for string variables. Useful for strings that aren't necessarily \0-terminated. Otherwise the same as Sys_var_charptr. diff --git a/sql/table.cc b/sql/table.cc index bcdfd23b4c1..67f49195b73 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3786,11 +3786,8 @@ bool TABLE_LIST::prepare_view_securety_context(THD *thd) { DBUG_PRINT("info", ("This table is suid view => load contest")); DBUG_ASSERT(view && view_sctx); - if (acl_getroot_no_password(view_sctx, - definer.user.str, - definer.host.str, - definer.host.str, - thd->db)) + if (acl_getroot(view_sctx, definer.user.str, definer.host.str, + definer.host.str, thd->db)) { if ((thd->lex->sql_command == SQLCOM_SHOW_CREATE) || (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) @@ -3809,10 +3806,15 @@ bool TABLE_LIST::prepare_view_securety_context(THD *thd) } else { - my_error(ER_ACCESS_DENIED_ERROR, MYF(0), - thd->security_ctx->priv_user, - thd->security_ctx->priv_host, - (thd->password ? ER(ER_YES) : ER(ER_NO))); + if (thd->password == 2) + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + thd->security_ctx->priv_user, + thd->security_ctx->priv_host); + else + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + thd->security_ctx->priv_user, + thd->security_ctx->priv_host, + (thd->password ? ER(ER_YES) : ER(ER_NO))); } DBUG_RETURN(TRUE); } diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 61f9d4881cb..edb98f8c1a7 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -33,6 +33,7 @@ #include <my_getopt.h> #include <m_string.h> #include <mysqld_error.h> +#include <sql_common.h> #define VER "2.1" #define MAX_TEST_QUERY_LENGTH 300 /* MAX QUERY BUFFER LENGTH */ |