diff options
Diffstat (limited to 'sql')
-rwxr-xr-x | sql/CMakeLists.txt | 2 | ||||
-rw-r--r-- | sql/Makefile.am | 12 | ||||
-rw-r--r-- | sql/client_settings.h | 4 | ||||
-rw-r--r-- | sql/ha_ndbcluster.cc | 2 | ||||
-rw-r--r-- | sql/ha_ndbcluster_binlog.cc | 2 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/mysql_priv.h | 6 | ||||
-rw-r--r-- | sql/mysqld.cc | 3 | ||||
-rw-r--r-- | sql/password.c | 12 | ||||
-rw-r--r-- | sql/protocol.cc | 18 | ||||
-rw-r--r-- | sql/protocol.h | 1 | ||||
-rw-r--r-- | sql/sql_acl.cc | 1993 | ||||
-rw-r--r-- | sql/sql_acl.h | 54 | ||||
-rw-r--r-- | sql/sql_builtin.cc.in | 4 | ||||
-rw-r--r-- | sql/sql_class.cc | 20 | ||||
-rw-r--r-- | sql/sql_class.h | 3 | ||||
-rw-r--r-- | sql/sql_connect.cc | 458 | ||||
-rw-r--r-- | sql/sql_insert.cc | 4 | ||||
-rw-r--r-- | sql/sql_lex.h | 2 | ||||
-rw-r--r-- | sql/sql_parse.cc | 116 | ||||
-rw-r--r-- | sql/sql_plugin.cc | 23 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 22 | ||||
-rw-r--r-- | sql/structs.h | 2 | ||||
-rw-r--r-- | sql/table.cc | 7 |
24 files changed, 1813 insertions, 958 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 531dfacce7a..69fbaa0d587 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -54,7 +54,7 @@ SET (SQL_SOURCE log_event.cc rpl_record.cc rpl_reporting.cc 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 + mysqld.cc net_serv.cc ../sql-common/client_plugin.c nt_servc.cc nt_servc.h 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 0242c11a22b..a3393c6bfc2 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -126,7 +126,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_servers.cc event_parse_data.cc \ opt_table_elimination.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 \ @@ -140,10 +140,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@ @@ -167,6 +167,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 4f06c15a29e..ed1e8d67cfe 100644 --- a/sql/client_settings.h +++ b/sql/client_settings.h @@ -15,6 +15,7 @@ #include <thr_alarm.h> +#include <sql_common.h> #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | \ CLIENT_SECURE_CONNECTION | CLIENT_TRANSACTIONS | \ @@ -31,7 +32,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 35ca65683d6..5732f2fff34 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -9185,7 +9185,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; CHARSET_INFO *charset_connection; charset_connection= get_charset_by_csname("utf8", diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index b24b17106c6..77851119e34 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; /* Set up ndb binlog diff --git a/sql/lex.h b/sql/lex.h index 47b563c2c85..7671f23682d 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -590,6 +590,7 @@ static SYMBOL symbols[] = { { "VARCHARACTER", SYM(VARCHAR)}, { "VARIABLES", SYM(VARIABLES)}, { "VARYING", SYM(VARYING)}, + { "VIA", SYM(VIA_SYM)}, { "VIEW", SYM(VIEW_SYM)}, { "VIRTUAL", SYM(VIRTUAL_SYM)}, { "WAIT", SYM(WAIT_SYM)}, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 2fc12ea10c0..c9afe10a97d 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1067,9 +1067,6 @@ int write_bin_log(THD *thd, bool clear_error, char const *query, ulong query_length); /* sql_connect.cc */ -int check_user(THD *thd, enum enum_server_command command, - const char *passwd, uint passwd_len, const char *db, - bool check_count); pthread_handler_t handle_one_connection(void *arg); bool init_new_connection_handler_thread(); void reset_mqh(LEX_USER *lu, bool get_them); @@ -1082,6 +1079,9 @@ bool login_connection(THD *thd); void end_connection(THD *thd); void prepare_new_connection_state(THD* thd); void update_global_user_stats(THD* thd, bool create_user, time_t now); +int get_or_create_user_conn(THD *thd, const char *user, + const char *host, USER_RESOURCES *mqh); +int check_for_max_user_connections(THD *thd, USER_CONN *uc); int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create, bool silent); bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 90fbafae22f..8890a354338 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -22,6 +22,7 @@ #include "rpl_mi.h" #include "sql_repl.h" #include "rpl_filter.h" +#include "client_settings.h" #include "repl_failsafe.h" #include <my_stacktrace.h> #include "mysqld_suffix.h" @@ -1394,6 +1395,7 @@ void clean_up(bool print_message) if (print_message && errmesg && server_start_time) sql_print_information(ER(ER_SHUTDOWN_COMPLETE),my_progname); thread_scheduler.end(); + mysql_library_end(); finish_client_errs(); my_free((uchar*) my_error_unregister(ER_ERROR_FIRST, ER_ERROR_LAST), MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR)); @@ -3489,6 +3491,7 @@ static int init_common_variables(const char *conf_file_name, int argc, if (init_errmessage()) /* Read error messages from file */ return 1; init_client_errs(); + mysql_library_init(never,never,never); /* for replication */ lex_init(); if (item_create_init()) return 1; diff --git a/sql/password.c b/sql/password.c index 3c662e0c8f3..0cfa15b15d6 100644 --- a/sql/password.c +++ b/sql/password.c @@ -193,13 +193,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 my_rnd_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); my_rnd_init(&rand_st,hash_pass[0] ^ hash_message[0], @@ -214,7 +214,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; @@ -481,7 +481,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; @@ -494,7 +494,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 11b4c085505..f9bdec1b2ac 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -341,24 +341,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 5a043d9c482..3eb5091a7c7 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -177,7 +177,6 @@ public: void send_warning(THD *thd, uint sql_errno, const char *err=0); bool net_send_error(THD *thd, uint sql_errno=0, const char *err=0); void net_end_statement(THD *thd); -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/sql_acl.cc b/sql/sql_acl.cc index 2ae6831429e..453af413cb0 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -30,6 +30,8 @@ #include <stdarg.h> #include "sp_head.h" #include "sp.h" +#include <sql_common.h> +#include <mysql/plugin_auth.h> static const TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { @@ -148,6 +150,84 @@ 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; + +static plugin_ref native_password_plugin, old_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 #define FIRST_NON_YN_FIELD 26 @@ -171,6 +251,24 @@ 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) +#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; static MEM_ROOT mem, memex; static bool initialized=0; @@ -265,6 +363,19 @@ my_bool acl_init(bool dont_read_acl_tables) (hash_get_key) acl_entry_get_key, (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 */ @@ -422,6 +533,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)) @@ -433,27 +545,34 @@ 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(thd->mem_root, 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; - } + + switch (password_len) { + case 0: /* no password */ + case SCRAMBLED_PASSWORD_CHAR_LENGTH: + user.plugin= native_password_plugin_name; + break; + case SCRAMBLED_PASSWORD_CHAR_LENGTH_323: + user.plugin= old_password_plugin_name; + break; + 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 : ""); + continue; + default: + sql_print_warning("Found invalid password for user: '%s@%s'; " + "Ignoring user", user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); + continue; } - else // password is correct + { uint next_field; user.access= get_access(table,3,&next_field) & GLOBAL_ACLS; @@ -530,13 +649,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 @@ -639,6 +788,8 @@ void acl_free(bool end) delete_dynamic(&acl_dbs); delete_dynamic(&acl_wild_hosts); 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 @@ -841,246 +992,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); - } - - VOID(pthread_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 */ -#ifdef 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 - user_access= acl_user->access; - break; -#ifdef HAVE_OPENSSL - 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; - } - VOID(pthread_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 @@ -1092,13 +1007,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)"), @@ -1121,8 +1036,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. @@ -1164,7 +1078,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); @@ -1190,7 +1108,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) { safe_mutex_assert_owner(&acl_cache->lock); @@ -1204,6 +1124,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; @@ -1240,7 +1168,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; @@ -1248,6 +1178,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); @@ -1966,6 +1912,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, @@ -1979,7 +1934,7 @@ 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 + 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 && @@ -2060,7 +2015,17 @@ 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 (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); + } } + if (old_row_exists) { /* @@ -2104,7 +2069,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, @@ -2112,7 +2079,9 @@ end: lex->x509_issuer, lex->x509_subject, &lex->mqh, - rights); + rights, + &combo.plugin, + &combo.auth); } DBUG_RETURN(error); } @@ -4445,14 +4414,14 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, ulong get_table_grant(THD *thd, TABLE_LIST *table) { ulong privilege; - Security_context *sctx= thd->security_ctx; - const char *db = table->db ? table->db : thd->db; GRANT_TABLE *grant_table; rw_rdlock(&LOCK_grant); #ifdef EMBEDDED_LIBRARY grant_table= NULL; #else + Security_context *sctx= thd->security_ctx; + const char *db = table->db ? table->db : thd->db; grant_table= table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user, table->table_name, 0); #endif @@ -6328,38 +6297,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)) @@ -6853,3 +6828,1429 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, } #endif + +/**************************************************************************** + 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,D) 1 +#define NORMAL_HANDSHAKE_SIZE 6 +#endif + +/** + 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; + THD *thd; + ACL_USER *acl_user; ///< a copy, independent from acl_users array + 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; +}; + + +/** + a helper function to report an access denied error in all the proper places +*/ + +static void login_failed_error(THD *thd, bool passwd_used) +{ + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip, + passwd_used ? 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_used ? ER(ER_YES) : ER(ER_NO)); + status_var_increment(thd->status_var.access_denied_errors); + /* + 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_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); + + THD *thd= mpvio->thd; + char *buff= (char *)my_alloca(1 + SERVER_VERSION_LENGTH + 1 + data_len + 64); + char scramble_buf[SCRAMBLE_LENGTH]; + char *end= buff; + + *end++= protocol_version; + + thd->client_capabilities= CLIENT_BASIC_FLAGS; + + if (data_len) + { + mpvio->cached_server_packet.pkt= (char*)thd->memdup(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(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + data= thd->scramble; + } + data_len= SCRAMBLE_LENGTH; + } + + if (opt_using_transactions) + thd->client_capabilities|= CLIENT_TRANSACTIONS; + + thd->client_capabilities|= CAN_CLIENT_COMPRESS; + + if (ssl_acceptor_fd) + { + thd->client_capabilities |= CLIENT_SSL; + thd->client_capabilities |= CLIENT_SSL_VERIFY_SERVER_CERT; + } + + end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1; + int4store((uchar*) end, mpvio->thd->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, thd->client_capabilities); + /* write server characteristics: up to 16 bytes allowed */ + end[2]=(char) default_charset_info->number; + int2store(end+3, mpvio->thd->server_status); + int2store(end+5, thd->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->thd->net, (uchar*) buff, (size_t) (end-buff)) || + net_flush(&mpvio->thd->net); + my_afree(buff); + return res; +} + +static bool secure_auth(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. + */ + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), + thd->security_ctx->user, + thd->security_ctx->host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + thd->security_ctx->user, + thd->security_ctx->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->thd->net; + static uchar switch_plugin_request_buf[]= { 254 }; + + + 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) + return secure_auth(mpvio->thd) || + 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(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + return 1; + } + + 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, Security_context *sctx) +{ + DBUG_ASSERT(mpvio->acl_user == 0); + + pthread_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(sctx->user, acl_user_tmp->user)) && + compare_hostname(&acl_user_tmp->host, sctx->host, sctx->ip)) + { + mpvio->acl_user= acl_user_tmp->copy(mpvio->thd->mem_root); + break; + } + } + pthread_mutex_unlock(&acl_cache->lock); + + if (!mpvio->acl_user) + { + login_failed_error(mpvio->thd, 0); + 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->thd->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(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + return 1; + } + + mpvio->auth_info.user_name= sctx->user; + mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; + strmake(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? + mpvio->acl_user->user : "", USERNAME_LENGTH); + + 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) +{ + THD *thd= mpvio->thd; + NET *net= &thd->net; + Security_context *sctx= thd->security_ctx; + + 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; + + if (passwd >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + 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= (thd->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)); + return 1; + } + + uint db_len= strlen(db); + + char *ptr= db + db_len + 1; + + if (ptr+1 < end) + { + uint cs_number= uint2korr(ptr); + thd_init_client_charset(thd, cs_number); + thd->update_charset(); + } + + /* Convert database and user names to utf8 */ + db_len= copy_and_convert(db_buff, sizeof(db_buff)-1, system_charset_info, + db, db_len, thd->charset(), &dummy_errors); + + user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, user_len, + thd->charset(), &dummy_errors); + + if (!(sctx->user= my_strndup(user_buff, user_len, MYF(MY_WME)))) + return 1; + + /* Clear variables that are allocated */ + thd->user_connect= 0; + strmake(sctx->priv_user, sctx->user, USERNAME_LENGTH); + + if (thd->make_lex_string(&mpvio->db, db_buff, db_len, 0) == 0) + return 1; /* The error is set by make_lex_string(). */ + + /* + 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 (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + mpvio->status= MPVIO_EXT::SUCCESS; + return 0; + } + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (find_mpvio_user(mpvio, sctx)) + return 1; + + char *client_plugin; + if (thd->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)); + return 1; + } + } + else + { + if (thd->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; + } + } + + /* 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 + + 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 + THD *thd= mpvio->thd; + NET *net= &thd->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(&net->vio->remote.sin_addr); + + ulong client_capabilities= uint2korr(net->read_pos); + if (client_capabilities & CLIENT_PROTOCOL_41) + { + 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 client. */ + thd->client_capabilities&= client_capabilities; + + if (thd->client_capabilities & CLIENT_IGNORE_SPACE) + thd->variables.sql_mode|= MODE_IGNORE_SPACE; + + DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities)); + if (thd->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, error_string)) + { + 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; + } + } + + if (end >= (char*) net->read_pos+ pkt_len +2) + return packet_error; + + 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, 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= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? + (uchar)(*passwd++) : strlen(passwd); + + if (thd->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, thd->charset(), &dummy_errors); + db= db_buff; + } + + user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, user_len, + thd->charset(), &dummy_errors); + 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-= 2; + } + + Security_context *sctx= thd->security_ctx; + + if (thd->make_lex_string(&mpvio->db, db, db_len, 0) == 0) + return packet_error; /* The error is set by make_lex_string(). */ + if (sctx->user) + x_free(sctx->user); + if (!(sctx->user= my_strndup(user, user_len, MYF(MY_WME)))) + return packet_error; /* The error is set by my_strdup(). */ + + /* + 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 (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + mpvio->status= MPVIO_EXT::SUCCESS; + return packet_error; + } + + if (find_mpvio_user(mpvio, sctx)) + return packet_error; + + if (thd->client_capabilities & CLIENT_PLUGIN_AUTH) + { + if ((client_plugin + strlen(client_plugin)) > + (char *)net->read_pos + pkt_len) + return packet_error; + } + else + { + if (thd->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->thd->net); + passwd = (char*)mpvio->thd->net.read_pos; + } + + *buff= (uchar*)passwd; + return passwd_len; +#else + return 0; +#endif +} + + +/** + 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; + + /* 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 (packet_len > 0 && (*packet == 1 || *packet == 255 || *packet == 254)) + { + /* + we cannot allow plugin data packet to start from 255 or 254 - + as the client will treat it as an error or "change plugin" packet. + We'll escape these bytes with \1. Consequently, we + have to escape \1 byte too. + */ + res= net_write_command(&mpvio->thd->net, 1, (uchar*)"", 0, + packet, packet_len); + } + else + { + res= my_net_write(&mpvio->thd->net, packet, packet_len) || + net_flush(&mpvio->thd->net); + } + mpvio->packets_written++; + 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; + + 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 (server_mpvio_write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(&mpvio->thd->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++; + 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 (server_mpvio_write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(&mpvio->thd->net); + } + else + pkt_len= my_net_read(&mpvio->thd->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->thd->net.read_pos; + + return (int)pkt_len; + +err: + if (mpvio->status == MPVIO_EXT::FAILURE && !mpvio->thd->is_error()) + { + inc_host_errors(&mpvio->thd->net.vio->remote.sin_addr); + my_error(ER_HANDSHAKE_ERROR, MYF(0), + mpvio->thd->security_ctx->host_or_ip); + } + 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->thd->net.vio, info); +} + + +static bool acl_check_ssl(THD *thd, 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, 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; +#endif + + 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; +} + +/** + 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; + 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); + + bzero(&mpvio, sizeof(mpvio)); + mpvio.read_packet= server_mpvio_read_packet; + mpvio.write_packet= server_mpvio_write_packet; + mpvio.info= server_mpvio_info; + mpvio.thd= thd; + mpvio.connect_errors= connect_errors; + mpvio.status= MPVIO_EXT::FAILURE; + + 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 + + if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) + DBUG_RETURN(1); + + DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART || + mpvio.status == MPVIO_EXT::SUCCESS); + } + else + { + /* mark the thd as having no scramble yet */ + thd->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); + } + + Security_context *sctx= thd->security_ctx; + 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 (sctx->user) + { + if (strcmp(sctx->priv_user, sctx->user)) + { + general_log_print(thd, command, "%s@%s as %s on %s", + sctx->user, sctx->host_or_ip, + sctx->priv_user[0] ? sctx->priv_user : "anonymous", + mpvio.db.str ? mpvio.db.str : (char*) ""); + } + else + general_log_print(thd, command, (char*) "%s@%s on %s", + sctx->user, sctx->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(thd, thd->password); + DBUG_RETURN(1); + } + + if (initialized) // if not --skip-grant-tables + { + sctx->master_access= acl_user->access; + strmake(sctx->priv_user, mpvio.auth_info.authenticated_as, USERNAME_LENGTH); + if (acl_user->host.hostname) + strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME); + else + *sctx->priv_host= 0; + + /* + 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)) + { + login_failed_error(thd, 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 || 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() + } + else + sctx->skip_grants(); + + if (thd->user_connect && + (thd->user_connect->user_resources.conn_per_hour || + thd->user_connect->user_resources.user_conn || + max_user_connections) && + check_for_max_user_connections(thd, thd->user_connect)) + { + status_var_increment(denied_connections); + 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)) + { + pthread_mutex_lock(&LOCK_connection_count); + bool count_ok= (*thd->scheduler->connection_count <= + *thd->scheduler->max_connections); + VOID(pthread_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) + { + status_var_increment(thd->status_var.access_denied_errors); + decrease_user_connections(thd->user_connect); + thd->user_connect= 0; + } + DBUG_RETURN(1); + } + } + + thd->net.net_skip_rest_factor= 2; // skip at most 2*max_packet_size + + if (res == CR_OK_HANDSHAKE_COMPLETE) + thd->main_da.disable_status(); + else + my_ok(thd); + + /* 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; + THD *thd=mpvio->thd; + + /* generate the scramble, or reuse the old one */ + if (thd->scramble[SCRAMBLE_LENGTH]) + create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)thd->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) + return CR_ERROR; + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + return CR_OK; +#endif + + if (pkt_len == 0) /* no password */ + return info->auth_string[0] ? CR_ERROR : CR_OK; + + info->password_used = 1; + if (pkt_len == SCRAMBLE_LENGTH) + return check_scramble(pkt, thd->scramble, mpvio->acl_user->salt) ? + CR_ERROR : CR_OK; + + inc_host_errors(&mpvio->thd->net.vio->remote.sin_addr); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); + 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; + THD *thd=mpvio->thd; + + /* generate the scramble, or reuse the old one */ + if (thd->scramble[SCRAMBLE_LENGTH]) + create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)thd->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(thd)) + return CR_ERROR; + + info->password_used = 1; + + if (pkt_len == SCRAMBLE_LENGTH_323) + return check_scramble_323(pkt, thd->scramble, + (ulong *)mpvio->acl_user->salt) ? CR_ERROR : CR_OK; + + inc_host_errors(&mpvio->thd->net.vio->remote.sin_addr); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.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 4c835e2718c..5078c80cedf 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -161,53 +161,6 @@ enum mysql_db_table_field extern const TABLE_FIELD_DEF mysql_db_table_def; -/* 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); @@ -216,10 +169,9 @@ 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); diff --git a/sql/sql_builtin.cc.in b/sql/sql_builtin.cc.in index 7ecd4918d7b..06f23cf97af 100644 --- a/sql/sql_builtin.cc.in +++ b/sql/sql_builtin.cc.in @@ -19,10 +19,10 @@ typedef struct st_mysql_plugin builtin_plugin[]; extern builtin_plugin - builtin_binlog_plugin@mysql_plugin_defs@; + builtin_binlog_plugin, builtin_mysql_password_plugin@mysql_plugin_defs@; struct st_mysql_plugin *mysqld_builtins[]= { - builtin_binlog_plugin@mysql_plugin_defs@,(struct st_mysql_plugin *)0 + builtin_binlog_plugin, builtin_mysql_password_plugin@mysql_plugin_defs@,(struct st_mysql_plugin *)0 }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index cd5840ac32e..593d1e68af0 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2984,9 +2984,9 @@ void THD::set_status_var_init() void Security_context::init() { - host= user= priv_user= ip= 0; + host= user= ip= 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; @@ -3010,8 +3010,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'; } @@ -3053,7 +3052,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. @@ -3087,19 +3086,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, diff --git a/sql/sql_class.h b/sql/sql_class.h index 0db3de6c929..a5aa24bff0a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -805,7 +805,8 @@ 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]; /* The host privilege we are using */ char priv_host[MAX_HOSTNAME]; /* points to host if host is available, otherwise points to ip */ diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 36fd45c1b7b..30c6c4fc653 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -27,24 +27,6 @@ extern pthread_mutex_t LOCK_global_user_client_stats; extern pthread_mutex_t LOCK_global_table_stats; extern pthread_mutex_t LOCK_global_index_stats; -#ifdef 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 */ - #ifdef __WIN__ extern void win_install_sigabrt_handler(); #endif @@ -56,9 +38,9 @@ extern void win_install_sigabrt_handler(); #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, + USER_RESOURCES *mqh) { int return_val= 0; size_t temp_len, user_len; @@ -124,7 +106,6 @@ end: 1 error */ -static int check_for_max_user_connections(THD *thd, USER_CONN *uc) { int error=0; @@ -276,240 +257,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; - pthread_mutex_lock(&LOCK_global_system_variables); - opt_secure_auth_local= opt_secure_auth; - pthread_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->remote.sin_addr); - 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) - { - bool count_ok= 1; - - if (!(thd->main_security_ctx.master_access & SUPER_ACL)) - { - pthread_mutex_lock(&LOCK_connection_count); - count_ok= (*thd->scheduler->connection_count <= - *thd->scheduler->max_connections); - VOID(pthread_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 || - 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 || - max_user_connections) && - check_for_max_user_connections(thd, thd->user_connect)) - { - /* The error is set in check_for_max_user_connections(). */ - status_var_increment(denied_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); - status_var_increment(thd->status_var.access_denied_errors); - DBUG_RETURN(1); - } - } - my_ok(thd); - thd->net.net_skip_rest_factor= 2; // skip at most 2*max_packet_size - thd->password= test(passwd_len); // remember for error messages - /* 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)); - status_var_increment(thd->status_var.access_denied_errors); - - 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. @@ -1088,9 +835,8 @@ 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 */ #ifndef EMBEDDED_LIBRARY @@ -1098,8 +844,6 @@ 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))); @@ -1161,200 +905,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 */ -#ifdef 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->remote.sin_addr); - 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->remote.sin_addr); 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; -#ifdef HAVE_OPENSSL - DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities)); - if (thd->client_capabilities & CLIENT_SSL) - { - char error_string[1024]; - /* Do the SSL layering. */ - if (!ssl_acceptor_fd) - { - inc_host_errors(&thd->remote.sin_addr); - 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, error_string)) - { - DBUG_PRINT("error", ("Failed to accept new SSL connection")); - inc_host_errors(&thd->remote.sin_addr); - 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->remote.sin_addr); - 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->remote.sin_addr); - 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->remote.sin_addr); - 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; - } - - if (thd->main_security_ctx.user) - x_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_insert.cc b/sql/sql_insert.cc index afc86ef6d4f..78cc103793d 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1778,8 +1778,10 @@ public: table(0),tables_in_use(0),stacked_inserts(0), status(0), dead(0), group_count(0) { - 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.version=refresh_version; thd.command=COM_DELAYED_INSERT; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e67be64c847..f555784ce4b 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -952,6 +952,8 @@ extern sys_var *trg_new_row_fake_var; enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE, XA_SUSPEND, XA_FOR_MIGRATE}; +extern const LEX_STRING null_lex_str; +extern const LEX_STRING empty_lex_str; /* Class representing list of all tables used by statement. diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3890e74304f..d520542700e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -444,9 +444,8 @@ static void handle_bootstrap_impl(THD *thd) thd_proc_info(thd, 0); thd->version=refresh_version; - 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 @@ -1093,96 +1092,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); - } + 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; - /* 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; - - /* 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)) { x_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 { @@ -1193,12 +1130,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif /* NO_EMBEDDED_ACCESS_CHECKS */ x_free(save_db); x_free(save_security_ctx.user); - - if (cs_number) - { - thd_init_client_charset(thd, cs_number); - thd->update_charset(); - } } break; } @@ -4348,8 +4279,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(); } /* @@ -7727,8 +7658,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 4d9490b4b9f..4d20d4a9ab1 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -16,6 +16,7 @@ #include "mysql_priv.h" #include <my_pthread.h> #include <my_getopt.h> +#include <mysql/plugin_auth.h> #define REPORT_TO_LOG 1 #define REPORT_TO_USER 2 @@ -46,7 +47,10 @@ const LEX_STRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM]= { C_STRING_WITH_LEN("STORAGE ENGINE") }, { C_STRING_WITH_LEN("FTPARSER") }, { C_STRING_WITH_LEN("DAEMON") }, - { C_STRING_WITH_LEN("INFORMATION SCHEMA") } + { 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); @@ -59,12 +63,12 @@ extern int finalize_schema_table(st_plugin_int *plugin); */ plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { - 0,ha_initialize_handlerton,0,0,initialize_schema_table + 0,ha_initialize_handlerton,0,0,initialize_schema_table, 0, 0, 0 }; plugin_type_init plugin_type_deinitialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { - 0,ha_finalize_handlerton,0,0,finalize_schema_table + 0,ha_finalize_handlerton,0,0,finalize_schema_table, 0, 0, 0 }; #ifdef HAVE_DLOPEN @@ -85,7 +89,10 @@ static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_HANDLERTON_INTERFACE_VERSION, MYSQL_FTPARSER_INTERFACE_VERSION, MYSQL_DAEMON_INTERFACE_VERSION, - MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION + MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, + 0xff00, /* audit plugins are supported in a later versions */ + 0xff00, /* replication plugins are supported in a later versions */ + MYSQL_AUTHENTICATION_INTERFACE_VERSION }; static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= { @@ -93,7 +100,10 @@ static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_HANDLERTON_INTERFACE_VERSION, MYSQL_FTPARSER_INTERFACE_VERSION, MYSQL_DAEMON_INTERFACE_VERSION, - MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION + MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, + 0x0000, /* audit plugins are supported in a later versions */ + 0x0000, /* replication plugins are supported in a later versions */ + MYSQL_AUTHENTICATION_INTERFACE_VERSION }; /* support for Services */ @@ -1013,6 +1023,9 @@ void plugin_unlock_list(THD *thd, plugin_ref *list, uint count) { LEX *lex= thd ? thd->lex : 0; DBUG_ENTER("plugin_unlock_list"); + if (count == 0) + DBUG_VOID_RETURN; + DBUG_ASSERT(list); pthread_mutex_lock(&LOCK_plugin); while (count--) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a6d8caac214..a2bd05756ff 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -56,6 +56,7 @@ int yylex(void *yylval, void *yythd); const LEX_STRING null_lex_str= {0,0}; +const LEX_STRING empty_lex_str= { (char*) "", 0 }; #define yyoverflow(A,B,C,D,E,F) \ { \ @@ -1260,8 +1261,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token VARIANCE_SYM %token VARYING /* SQL-2003-R */ %token VAR_SAMP_SYM -%token VIRTUAL_SYM +%token VIA_SYM %token VIEW_SYM /* SQL-2003-N */ +%token VIRTUAL_SYM %token WAIT_SYM %token WARNINGS %token WEEK_SYM @@ -11673,6 +11675,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, @@ -11685,6 +11690,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, @@ -12941,6 +12949,18 @@ grant_user: } | user IDENTIFIED_SYM BY PASSWORD TEXT_STRING { $$= $1; $1->password= $5; } + | user IDENTIFIED_SYM VIA_SYM ident_or_text + { + $$= $1; + $1->plugin= $4; + $1->auth= empty_lex_str; + } + | user IDENTIFIED_SYM VIA_SYM ident_or_text USING TEXT_STRING_sys + { + $$= $1; + $1->plugin= $4; + $1->auth= $6; + } | user { $$= $1; $1->password= null_lex_str; } ; diff --git a/sql/structs.h b/sql/structs.h index 0772cb4ff1d..cb81e3194fa 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -178,7 +178,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/table.cc b/sql/table.cc index 72db5e13add..b044186f916 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4163,11 +4163,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)) |