diff --git a/mysql-test/r/max_nonsuper_connections.result b/mysql-test/r/max_nonsuper_connections.result new file mode 100644 index 000000000000..f99b79284c9f --- /dev/null +++ b/mysql-test/r/max_nonsuper_connections.result @@ -0,0 +1,81 @@ +create user test_user@localhost; +grant all on test to test_user@localhost; +create user super_user@localhost; +grant all on *.* to super_user@localhost with grant option; +SET @start_value = @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = 10; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +10 +connection default; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +ERROR 08004: Too many connections +connect con_root, localhost, root,,test; +# connection con_root +connection con_root; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +10 +disconnect con_root; +connection default; +connect con_super, localhost, super_user,,test; +connection con_super; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +10 +mysqltest: At line 1: change user failed: Too many connections +disconnect con_super; +connection con10; +connect con11, localhost, test_user,,test; +disconnect con11; +connection con10; +ERROR 08004: Too many connections +connection default; +disconnect con10; +disconnect con9; +disconnect con8; +disconnect con7; +disconnect con6; +disconnect con5; +disconnect con4; +disconnect con3; +disconnect con2; +disconnect con1; +connection default; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +ERROR 08004: Too many connections +connection default; +SET @@global.max_nonsuper_connections = @start_value; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +drop user test_user@localhost; +drop user super_user@localhost; +disconnect con10; +disconnect con9; +disconnect con8; +disconnect con7; +disconnect con6; +disconnect con5; +disconnect con4; +disconnect con3; +disconnect con2; +disconnect con1; diff --git a/mysql-test/r/mysqld--help-notwin-profiling.result b/mysql-test/r/mysqld--help-notwin-profiling.result index 6c279cad2962..42cd57f2d68d 100644 --- a/mysql-test/r/mysqld--help-notwin-profiling.result +++ b/mysql-test/r/mysqld--help-notwin-profiling.result @@ -537,6 +537,9 @@ The following options may be given as the first argument: max_join_size records return an error --max-length-for-sort-data=# Max number of bytes in sorted records + --max-nonsuper-connections=# + The maximum number of total active connections for + non-super user (0 = no limit) --max-prepared-stmt-count=# Maximum number of prepared statements in the server --max-relay-log-size=# @@ -1745,6 +1748,7 @@ max-error-count 64 max-heap-table-size 16777216 max-join-size 18446744073709551615 max-length-for-sort-data 1024 +max-nonsuper-connections 0 max-prepared-stmt-count 16382 max-relay-log-size 0 max-running-queries 0 diff --git a/mysql-test/r/mysqld--help-notwin.result b/mysql-test/r/mysqld--help-notwin.result index 8939b943d30f..6a4430081c21 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -537,6 +537,9 @@ The following options may be given as the first argument: max_join_size records return an error --max-length-for-sort-data=# Max number of bytes in sorted records + --max-nonsuper-connections=# + The maximum number of total active connections for + non-super user (0 = no limit) --max-prepared-stmt-count=# Maximum number of prepared statements in the server --max-relay-log-size=# @@ -1743,6 +1746,7 @@ max-error-count 64 max-heap-table-size 16777216 max-join-size 18446744073709551615 max-length-for-sort-data 1024 +max-nonsuper-connections 0 max-prepared-stmt-count 16382 max-relay-log-size 0 max-running-queries 0 diff --git a/mysql-test/suite/sys_vars/r/max_nonsuper_connections_basic.result b/mysql-test/suite/sys_vars/r/max_nonsuper_connections_basic.result new file mode 100644 index 000000000000..77c7312db6ed --- /dev/null +++ b/mysql-test/suite/sys_vars/r/max_nonsuper_connections_basic.result @@ -0,0 +1,102 @@ +SET @start_value = @@global.max_nonsuper_connections; +SELECT @start_value; +@start_value +0 +SET @@global.max_nonsuper_connections = DEFAULT; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@global.max_nonsuper_connections = 100000; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +100000 +SET @@global.max_nonsuper_connections = 99999; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +99999 +SET @@global.max_nonsuper_connections = 65536; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +65536 +SET @@global.max_nonsuper_connections = 1; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +1 +SET @@global.max_nonsuper_connections = 2; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +2 +SET @@global.max_nonsuper_connections = TRUE; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +1 +SET @@global.max_nonsuper_connections = FALSE; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@global.max_nonsuper_connections = -1; +Warnings: +Warning 1292 Truncated incorrect max_nonsuper_connections value: '-1' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@global.max_nonsuper_connections = 100000000000; +Warnings: +Warning 1292 Truncated incorrect max_nonsuper_connections value: '100000000000' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +4294967295 +SET @@global.max_nonsuper_connections = 10000.01; +ERROR 42000: Incorrect argument type to variable 'max_nonsuper_connections' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +4294967295 +SET @@global.max_nonsuper_connections = -1024; +Warnings: +Warning 1292 Truncated incorrect max_nonsuper_connections value: '-1024' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@global.max_nonsuper_connections = ON; +ERROR 42000: Incorrect argument type to variable 'max_nonsuper_connections' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@global.max_nonsuper_connections = 'test'; +ERROR 42000: Incorrect argument type to variable 'max_nonsuper_connections' +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 +SET @@session.max_nonsuper_connections = 4096; +ERROR HY000: Variable 'max_nonsuper_connections' is a GLOBAL variable and should be set with SET GLOBAL +SELECT @@session.max_nonsuper_connections; +ERROR HY000: Variable 'max_nonsuper_connections' is a GLOBAL variable +SET max_nonsuper_connections = 6000; +ERROR HY000: Variable 'max_nonsuper_connections' is a GLOBAL variable and should be set with SET GLOBAL +SELECT @@max_nonsuper_connections; +@@max_nonsuper_connections +0 +SET local.max_nonsuper_connections = 7000; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'max_nonsuper_connections = 7000' at line 1 +SELECT local.max_nonsuper_connections; +ERROR 42S02: Unknown table 'local' in field list +SET global.max_nonsuper_connections = 8000; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'max_nonsuper_connections = 8000' at line 1 +SELECT global.max_nonsuper_connections; +ERROR 42S02: Unknown table 'global' in field list +SELECT max_nonsuper_connections = @@session.max_nonsuper_connections; +ERROR 42S22: Unknown column 'max_nonsuper_connections' in 'field list' +SELECT @@global.max_nonsuper_connections = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='max_nonsuper_connections'; +@@global.max_nonsuper_connections = VARIABLE_VALUE +1 +SELECT @@max_nonsuper_connections = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='max_nonsuper_connections'; +@@max_nonsuper_connections = VARIABLE_VALUE +1 +SET @@global.max_nonsuper_connections = @start_value; +SELECT @@global.max_nonsuper_connections; +@@global.max_nonsuper_connections +0 diff --git a/mysql-test/suite/sys_vars/t/max_nonsuper_connections_basic.test b/mysql-test/suite/sys_vars/t/max_nonsuper_connections_basic.test new file mode 100644 index 000000000000..e472c1283586 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/max_nonsuper_connections_basic.test @@ -0,0 +1,95 @@ +--source include/load_sysvars.inc + +# +# save original value +# +SET @start_value = @@global.max_nonsuper_connections; +SELECT @start_value; + + +# +# set default value +# +SET @@global.max_nonsuper_connections = DEFAULT; +SELECT @@global.max_nonsuper_connections; + + +# +# set various values +# +SET @@global.max_nonsuper_connections = 100000; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = 99999; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = 65536; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = 1; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = 2; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = TRUE; +SELECT @@global.max_nonsuper_connections; +SET @@global.max_nonsuper_connections = FALSE; +SELECT @@global.max_nonsuper_connections; + + +# +# set invalid values +# +# Value truncated +SET @@global.max_nonsuper_connections = -1; +SELECT @@global.max_nonsuper_connections; +# Value truncated +SET @@global.max_nonsuper_connections = 100000000000; +SELECT @@global.max_nonsuper_connections; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.max_nonsuper_connections = 10000.01; +SELECT @@global.max_nonsuper_connections; +# Value truncated +SET @@global.max_nonsuper_connections = -1024; +SELECT @@global.max_nonsuper_connections; + +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.max_nonsuper_connections = ON; +SELECT @@global.max_nonsuper_connections; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.max_nonsuper_connections = 'test'; +SELECT @@global.max_nonsuper_connections; + +--Error ER_GLOBAL_VARIABLE +SET @@session.max_nonsuper_connections = 4096; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@session.max_nonsuper_connections; + +--Error ER_GLOBAL_VARIABLE +SET max_nonsuper_connections = 6000; +SELECT @@max_nonsuper_connections; +--Error ER_PARSE_ERROR +SET local.max_nonsuper_connections = 7000; +--Error ER_UNKNOWN_TABLE +SELECT local.max_nonsuper_connections; +--Error ER_PARSE_ERROR +SET global.max_nonsuper_connections = 8000; +--Error ER_UNKNOWN_TABLE +SELECT global.max_nonsuper_connections; +--Error ER_BAD_FIELD_ERROR +SELECT max_nonsuper_connections = @@session.max_nonsuper_connections; + + +# +# Check if the value in GLOBAL & SESSION Tables matches values in variable +# +SELECT @@global.max_nonsuper_connections = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='max_nonsuper_connections'; + +SELECT @@max_nonsuper_connections = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='max_nonsuper_connections'; + + +# +# restore +# +SET @@global.max_nonsuper_connections = @start_value; +SELECT @@global.max_nonsuper_connections; diff --git a/mysql-test/t/max_nonsuper_connections.test b/mysql-test/t/max_nonsuper_connections.test new file mode 100644 index 000000000000..faaeb49853c2 --- /dev/null +++ b/mysql-test/t/max_nonsuper_connections.test @@ -0,0 +1,125 @@ +create user test_user@localhost; +grant all on test to test_user@localhost; + +create user super_user@localhost; +grant all on *.* to super_user@localhost with grant option; + +SET @start_value = @@global.max_nonsuper_connections; + +SET @@global.max_nonsuper_connections = 10; +SELECT @@global.max_nonsuper_connections; + +enable_connect_log; +connection default; + +# +# fill up max_nonsuper_connections +# +let $i = 10; +while ($i) +{ + connect (con$i, localhost, test_user,,test); + dec $i; +} + +# +# New non-admin connection will be rejected +# +disable_query_log; +--error ER_CON_COUNT_ERROR +connect (con11, localhost, test_user,,test); +enable_query_log; + +# +# admin user connection is not limited by max_nonsuper_connections +# +connect (con_root, localhost, root,,test); +--echo # connection con_root +connection con_root; +SELECT @@global.max_nonsuper_connections; +disconnect con_root; +connection default; + +# +# Test another admin super_user +# +connect (con_super, localhost, super_user,,test); +connection con_super; +SELECT @@global.max_nonsuper_connections; + +# +# change admin user to regular user in the current connection will fail +# because max_total_user_connection is already reached +# +--error 1 +--exec echo "--change_user test_user" | $MYSQL_TEST 2>&1 + +# +# change user to root is OK +# +change_user root; +disconnect con_super; + +# +# change regular user to root will free up the nonsuper_connections +# so we will be able to connect another regular user +# +connection con10; +change_user root; +connect (con11, localhost, test_user,,test); +disconnect con11; + +# +# change con10 back to regular user +# +connection con10; +# wait for con11 to be disconnected +let $wait_condition= + select count(*)=9 from information_schema.processlist where user='test_user'; +source include/wait_condition.inc; +# now change user in con10 +change_user test_user; +disable_query_log; +# no new regular connection can be accepted +--error ER_CON_COUNT_ERROR +connect (con11, localhost, test_user,,test); +enable_query_log; + +# +# decrement user connection counts +# +connection default; +let $i= 10; +while ($i) +{ + disconnect con$i; + dec $i; +} + +# able to refill up max_nonsuper_connections +connection default; +let $i = 10; +while ($i) +{ + connect (con$i, localhost, test_user,,test); + dec $i; +} +disable_query_log; +--error ER_CON_COUNT_ERROR +connect (con11, localhost, test_user,,test); +enable_query_log; + +# +# restore +# +connection default; +SET @@global.max_nonsuper_connections = @start_value; +SELECT @@global.max_nonsuper_connections; +drop user test_user@localhost; +drop user super_user@localhost; +let $i= 10; +while ($i) +{ + disconnect con$i; + dec $i; +} diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 263f73065d5e..4454dd585637 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -677,6 +677,7 @@ ulong specialflag=0; ulong binlog_cache_use= 0, binlog_cache_disk_use= 0; ulong binlog_stmt_cache_use= 0, binlog_stmt_cache_disk_use= 0; ulong max_connections, max_connect_errors; +uint max_nonsuper_connections; ulong opt_max_running_queries, opt_max_waiting_queries; AC *db_ac; ulong rpl_stop_slave_timeout= LONG_TIMEOUT; @@ -1501,6 +1502,7 @@ struct st_VioSSLFd *ssl_acceptor_fd; by LOCK_thread_count */ uint connection_count= 0; +uint nonsuper_connections= 0; mysql_cond_t COND_connection_count; /* Function declarations */ diff --git a/sql/mysqld.h b/sql/mysqld.h index 491fce1d355f..84e6d8de716f 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -289,6 +289,7 @@ extern bool in_bootstrap; extern my_bool opt_bootstrap; extern char *opt_rbr_idempotent_tables; extern uint connection_count; +extern uint nonsuper_connections; extern ulong opt_srv_fatal_semaphore_timeout; extern my_bool opt_safe_user_create; extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap; @@ -910,6 +911,7 @@ extern uint slave_net_timeout; extern ulong opt_mts_slave_parallel_workers; extern ulonglong opt_mts_pending_jobs_size_max; extern uint max_user_connections; +extern uint max_nonsuper_connections; extern ulong rpl_stop_slave_timeout; extern my_bool log_bin_use_v1_row_events; extern ulong what_to_log,flush_time; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index caa28eeb1e02..bd49db427401 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -11364,7 +11364,8 @@ acl_authenticate(THD *thd, uint com_change_user_pkt_len) bool global_max = false; if (uc && (uc->user_resources.conn_per_hour || uc->user_resources.user_conn || - global_system_variables.max_user_connections) && + global_system_variables.max_user_connections || + max_nonsuper_connections) && check_for_max_user_connections(thd, uc, &global_max)) { fix_user_conn(thd, global_max); // Undo work by get_or_create_user_conn diff --git a/sql/sql_class.h b/sql/sql_class.h index 594c3e4f7035..13f002ae57cb 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -663,6 +663,7 @@ typedef struct system_variables ulong updatable_views_with_limit; uint max_user_connections; ulong my_aes_mode; + uint max_nonsuper_connections; /** In slave thread we need to know in behalf of which diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 45693c9ed86f..44555cd4dbff 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -80,6 +80,11 @@ void fix_user_conn(THD *thd, bool global_max) if (thd->net.vio->type == VIO_TYPE_SSL) { us->connections_ssl_total.dec(); } + if (!(thd->main_security_ctx.master_access & SUPER_ACL)) + { + // this is non-super user, decrement nonsuper_connections + nonsuper_connections--; + } if (global_max) us->connections_denied_max_global.inc(); @@ -139,6 +144,11 @@ int get_or_create_user_conn(THD *thd, const char *user, if (thd->net.vio->type == VIO_TYPE_SSL) { uc->user_stats.connections_ssl_total.inc(); } + if (!(thd->main_security_ctx.master_access & SUPER_ACL)) + { + // this is non-super user, increment nonsuper_connections + nonsuper_connections++; + } end: mysql_mutex_unlock(&LOCK_user_conn); return return_val; @@ -172,6 +182,22 @@ int check_for_max_user_connections(THD *thd, USER_CONN *uc, bool *global_max) *global_max= false; mysql_mutex_lock(&LOCK_user_conn); + if (max_nonsuper_connections && + !(thd->main_security_ctx.master_access & SUPER_ACL) && + nonsuper_connections > max_nonsuper_connections) + { + DBUG_PRINT("info", + ("max_nonsuper_connections: %d, " + "nonsuper_connections: %d", + max_nonsuper_connections, + nonsuper_connections)); + + // max_nonsuper_connections limit reached + my_error(ER_CON_COUNT_ERROR, MYF(0)); + *global_max = true; + error=1; + goto end; + } if (global_system_variables.max_user_connections && !uc->user_resources.user_conn && global_system_variables.max_user_connections < (uint) uc->connections && @@ -263,6 +289,11 @@ void release_user_connection(THD *thd) mysql_mutex_lock(&LOCK_user_conn); DBUG_ASSERT(uc->connections > 0); thd->decrement_user_connections_counter(); + if (!(thd->main_security_ctx.master_access & SUPER_ACL)) + { + // this is non-super user, decrement nonsuper_connections + nonsuper_connections--; + } /* To preserve data in uc->user_stats, delete is no longer done */ mysql_mutex_unlock(&LOCK_user_conn); thd->set_user_connect(NULL); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 99e586b54b23..56d39e9be259 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1585,7 +1585,14 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #ifndef NO_EMBEDDED_ACCESS_CHECKS /* we've authenticated new user */ if (save_user_connect) - decrease_user_connections(save_user_connect); + decrease_user_connections(save_user_connect); + if (!(save_security_ctx.master_access & SUPER_ACL)) + { + // previous user was a non-super user, decrement nonsuper_connections + mysql_mutex_lock(&LOCK_user_conn); + nonsuper_connections--; + mysql_mutex_unlock(&LOCK_user_conn); + } #endif /* NO_EMBEDDED_ACCESS_CHECKS */ mysql_mutex_lock(&thd->LOCK_thd_data); my_free(save_db); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 9b9eaf519890..ae27f28cc48e 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2283,6 +2283,14 @@ static Sys_var_max_user_conn Sys_max_user_connections( VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(session_readonly)); +static Sys_var_uint Sys_max_nonsuper_connections( + "max_nonsuper_connections", + "The maximum number of total active connections for non-super user " + "(0 = no limit)", + GLOBAL_VAR(max_nonsuper_connections), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, + NOT_IN_BINLOG); + static Sys_var_ulong Sys_max_tmp_tables( "max_tmp_tables", "Maximum number of temporary tables a client can keep open at a time",