Skip to content

Commit 5f00772

Browse files
committedDec 6, 2012
WL#6587 : Protocol support for password expiration
Initial implementation. * added a new capabilities flag CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS * Added a new server option (and a read only global variable) disconnect-on-expired-password. Defailt is on. When off it will cause old clients to get a connection against an expired password account and get an error for every query that they try to execute that's not SET PASSWORD. * server will check for expired passwords at login and will refuse the login if the client didn't set CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS in the client hello packet and if the disconnect_on_expired_password server side option is set. A detailed error (ER_MUST_CHANGE_PASSWORD) will be sent in this case. * server always sets CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS in the server hello packet. libmysql never checks for it. * new mysql_options() option MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS to set the CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS flag. * mysql binary sets MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS if run in interactive mode * mysqladmin sets MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS if the first command is 'password' or 'old_password' * mysqltest sets MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS * test cases added (both mtr tests and some C test code) * existing test case results updated to reflect the new server command line option. * fixed the linux compilation problems and test failures
1 parent 0d29906 commit 5f00772

17 files changed

+270
-28
lines changed
 

‎client/mysql.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4846,6 +4846,8 @@ sql_real_connect(char *host,char *database,char *user,char *password,
48464846
static void
48474847
init_connection_options(MYSQL *mysql)
48484848
{
4849+
my_bool interactive= status.batch ? FALSE : TRUE;
4850+
48494851
if (opt_init_command)
48504852
mysql_options(mysql, MYSQL_INIT_COMMAND, opt_init_command);
48514853

@@ -4915,7 +4917,8 @@ init_connection_options(MYSQL *mysql)
49154917

49164918
mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
49174919
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysql");
4918-
return;
4920+
4921+
mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &interactive);
49194922
}
49204923

49214924

‎client/mysqladmin.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
310310
int main(int argc,char *argv[])
311311
{
312312
int error= 0, ho_error;
313+
int first_command;
314+
my_bool can_handle_passwords;
313315
MYSQL mysql;
314316
char **commands, **save_argv;
315317

@@ -385,6 +387,13 @@ int main(int argc,char *argv[])
385387
mysql_options(&mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN,
386388
(char*) &opt_enable_cleartext_plugin);
387389

390+
first_command= find_type(argv[0], &command_typelib, FIND_TYPE_BASIC);
391+
can_handle_passwords=
392+
(first_command == ADMIN_PASSWORD || first_command == ADMIN_OLD_PASSWORD) ?
393+
TRUE : FALSE;
394+
mysql_options(&mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
395+
&can_handle_passwords);
396+
388397
if (sql_connect(&mysql, option_wait))
389398
{
390399
/*

‎client/mysqltest.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer;
130130
#if !defined(HAVE_YASSL)
131131
static const char *opt_server_public_key= 0;
132132
#endif
133+
static my_bool can_handle_expired_passwords= TRUE;
133134

134135
/* Info on properties that can be set with --enable_X and --disable_X */
135136

@@ -5314,6 +5315,8 @@ void safe_connect(MYSQL* mysql, const char *name, const char *host,
53145315
mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
53155316
mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD,
53165317
"program_name", "mysqltest");
5318+
mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
5319+
&can_handle_expired_passwords);
53175320
while(!mysql_real_connect(mysql, host,user, pass, db, port, sock,
53185321
CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS))
53195322
{
@@ -5417,6 +5420,8 @@ int connect_n_handle_errors(struct st_command *command,
54175420

54185421
mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
54195422
mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest");
5423+
mysql_options(con, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
5424+
&can_handle_expired_passwords);
54205425
while (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock: 0,
54215426
CLIENT_MULTI_STATEMENTS))
54225427
{

‎include/mysql.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ enum mysql_option
174174
MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD,
175175
MYSQL_OPT_CONNECT_ATTR_DELETE,
176176
MYSQL_SERVER_PUBLIC_KEY,
177-
MYSQL_ENABLE_CLEARTEXT_PLUGIN
177+
MYSQL_ENABLE_CLEARTEXT_PLUGIN,
178+
MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS
178179
};
179180

180181
/**

‎include/mysql.h.pp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@
271271
MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD,
272272
MYSQL_OPT_CONNECT_ATTR_DELETE,
273273
MYSQL_SERVER_PUBLIC_KEY,
274-
MYSQL_ENABLE_CLEARTEXT_PLUGIN
274+
MYSQL_ENABLE_CLEARTEXT_PLUGIN,
275+
MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS
275276
};
276277
struct st_mysql_options_extention;
277278
struct st_mysql_options {

‎include/mysql_com.h

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ enum enum_server_command
180180
/* Enable authentication response packet to be larger than 255 bytes. */
181181
#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA (1UL << 21)
182182

183+
/* Don't close the connection for a connection with expired password. */
184+
#define CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS (1UL << 22)
185+
183186
#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30)
184187
#define CLIENT_REMEMBER_OPTIONS (1UL << 31)
185188

@@ -190,30 +193,32 @@ enum enum_server_command
190193
#endif
191194

192195
/* Gather all possible capabilites (flags) supported by the server */
193-
#define CLIENT_ALL_FLAGS (CLIENT_LONG_PASSWORD | \
194-
CLIENT_FOUND_ROWS | \
195-
CLIENT_LONG_FLAG | \
196-
CLIENT_CONNECT_WITH_DB | \
197-
CLIENT_NO_SCHEMA | \
198-
CLIENT_COMPRESS | \
199-
CLIENT_ODBC | \
200-
CLIENT_LOCAL_FILES | \
201-
CLIENT_IGNORE_SPACE | \
202-
CLIENT_PROTOCOL_41 | \
203-
CLIENT_INTERACTIVE | \
204-
CLIENT_SSL | \
205-
CLIENT_IGNORE_SIGPIPE | \
206-
CLIENT_TRANSACTIONS | \
207-
CLIENT_RESERVED | \
208-
CLIENT_SECURE_CONNECTION | \
209-
CLIENT_MULTI_STATEMENTS | \
210-
CLIENT_MULTI_RESULTS | \
211-
CLIENT_PS_MULTI_RESULTS | \
212-
CLIENT_SSL_VERIFY_SERVER_CERT | \
213-
CLIENT_REMEMBER_OPTIONS | \
214-
CLIENT_PLUGIN_AUTH | \
215-
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | \
216-
CLIENT_CONNECT_ATTRS)
196+
#define CLIENT_ALL_FLAGS (CLIENT_LONG_PASSWORD \
197+
| CLIENT_FOUND_ROWS \
198+
| CLIENT_LONG_FLAG \
199+
| CLIENT_CONNECT_WITH_DB \
200+
| CLIENT_NO_SCHEMA \
201+
| CLIENT_COMPRESS \
202+
| CLIENT_ODBC \
203+
| CLIENT_LOCAL_FILES \
204+
| CLIENT_IGNORE_SPACE \
205+
| CLIENT_PROTOCOL_41 \
206+
| CLIENT_INTERACTIVE \
207+
| CLIENT_SSL \
208+
| CLIENT_IGNORE_SIGPIPE \
209+
| CLIENT_TRANSACTIONS \
210+
| CLIENT_RESERVED \
211+
| CLIENT_SECURE_CONNECTION \
212+
| CLIENT_MULTI_STATEMENTS \
213+
| CLIENT_MULTI_RESULTS \
214+
| CLIENT_PS_MULTI_RESULTS \
215+
| CLIENT_SSL_VERIFY_SERVER_CERT \
216+
| CLIENT_REMEMBER_OPTIONS \
217+
| CLIENT_PLUGIN_AUTH \
218+
| CLIENT_CONNECT_ATTRS \
219+
| CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA \
220+
| CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS \
221+
)
217222

218223
/*
219224
Switch off the flags that are optional and depending on build flags

‎mysql-test/r/connect.result

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,21 @@ DROP PROCEDURE test_t1;
393393
DROP FUNCTION last_t1;
394394
DROP TABLE t1;
395395
DROP USER must_change@localhost;
396+
#
397+
# WL#6587: Protocol support for password expiration
398+
#
399+
CREATE USER wl6587@localhost IDENTIFIED BY 'wl6587';
400+
ALTER USER wl6587@localhost PASSWORD EXPIRE;
401+
# non-interactive mysql should fail
402+
Warning: Using a password on the command line interface can be insecure.
403+
ERROR 1820 (HY000): You must SET PASSWORD before executing this statement
404+
# mysqladmin non-password should fail
405+
Warning: Using a password on the command line interface can be insecure.
406+
mysqladmin: connect to server at 'localhost' failed
407+
error: 'You must SET PASSWORD before executing this statement'
408+
# mysqladmin password should work
409+
Warning: Using a password on the command line interface can be insecure.
410+
DROP USER wl6587@localhost;
396411
# ------------------------------------------------------------------
397412
# -- End of 5.6 tests
398413
# ------------------------------------------------------------------

‎mysql-test/r/mysqld--help-notwin.result

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ The following options may be given as the first argument:
165165
client that does INSERT DELAYED will wait until there is
166166
room in the queue again. This variable is deprecated
167167
along with INSERT DELAYED.
168+
--disconnect-on-expired-password
169+
Give clients that don't signal password expiration
170+
support execution time error(s) instead of connection
171+
error
172+
(Defaults to on; use --skip-disconnect-on-expired-password to disable.)
168173
--disconnect-slave-event-count=#
169174
Option used by mysql-test for debugging and testing of
170175
replication.
@@ -1007,6 +1012,7 @@ delay-key-write ON
10071012
delayed-insert-limit 100
10081013
delayed-insert-timeout 300
10091014
delayed-queue-size 1000
1015+
disconnect-on-expired-password TRUE
10101016
disconnect-slave-event-count 0
10111017
div-precision-increment 4
10121018
end-markers-in-json FALSE

‎mysql-test/r/mysqld--help-win.result

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ The following options may be given as the first argument:
165165
client that does INSERT DELAYED will wait until there is
166166
room in the queue again. This variable is deprecated
167167
along with INSERT DELAYED.
168+
--disconnect-on-expired-password
169+
Give clients that don't signal password expiration
170+
support execution time error(s) instead of connection
171+
error
172+
(Defaults to on; use --skip-disconnect-on-expired-password to disable.)
168173
--disconnect-slave-event-count=#
169174
Option used by mysql-test for debugging and testing of
170175
replication.
@@ -1015,6 +1020,7 @@ delay-key-write ON
10151020
delayed-insert-limit 100
10161021
delayed-insert-timeout 300
10171022
delayed-queue-size 1000
1023+
disconnect-on-expired-password TRUE
10181024
disconnect-slave-event-count 0
10191025
div-precision-increment 4
10201026
end-markers-in-json FALSE
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
SELECT @@GLOBAL.disconnect_on_expired_password;
2+
@@GLOBAL.disconnect_on_expired_password
3+
1
4+
1 Expected
5+
SET @@GLOBAL.disconnect_on_expired_password=0;
6+
ERROR HY000: Variable 'disconnect_on_expired_password' is a read only variable
7+
Expected error 'Read only variable'
8+
SELECT @@GLOBAL.disconnect_on_expired_password;
9+
@@GLOBAL.disconnect_on_expired_password
10+
1
11+
1 Expected
12+
SELECT @@disconnect_on_expired_password = @@GLOBAL.disconnect_on_expired_password;
13+
@@disconnect_on_expired_password = @@GLOBAL.disconnect_on_expired_password
14+
1
15+
1 Expected
16+
SELECT COUNT(@@local.disconnect_on_expired_password);
17+
ERROR HY000: Variable 'disconnect_on_expired_password' is a GLOBAL variable
18+
Expected error 'Variable is a GLOBAL variable'
19+
SELECT COUNT(@@SESSION.disconnect_on_expired_password);
20+
ERROR HY000: Variable 'disconnect_on_expired_password' is a GLOBAL variable
21+
Expected error 'Variable is a GLOBAL variable'
22+
SELECT @@GLOBAL.disconnect_on_expired_password;
23+
@@GLOBAL.disconnect_on_expired_password
24+
1
25+
1 Expected
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
SELECT @@GLOBAL.disconnect_on_expired_password;
2+
--echo 1 Expected
3+
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
4+
SET @@GLOBAL.disconnect_on_expired_password=0;
5+
--echo Expected error 'Read only variable'
6+
SELECT @@GLOBAL.disconnect_on_expired_password;
7+
--echo 1 Expected
8+
SELECT @@disconnect_on_expired_password = @@GLOBAL.disconnect_on_expired_password;
9+
--echo 1 Expected
10+
--Error ER_INCORRECT_GLOBAL_LOCAL_VAR
11+
SELECT COUNT(@@local.disconnect_on_expired_password);
12+
--echo Expected error 'Variable is a GLOBAL variable'
13+
--Error ER_INCORRECT_GLOBAL_LOCAL_VAR
14+
SELECT COUNT(@@SESSION.disconnect_on_expired_password);
15+
--echo Expected error 'Variable is a GLOBAL variable'
16+
SELECT @@GLOBAL.disconnect_on_expired_password;
17+
--echo 1 Expected

‎mysql-test/t/connect.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,29 @@ DROP FUNCTION last_t1;
465465
DROP TABLE t1;
466466
DROP USER must_change@localhost;
467467

468+
469+
--echo #
470+
--echo # WL#6587: Protocol support for password expiration
471+
--echo #
472+
473+
CREATE USER wl6587@localhost IDENTIFIED BY 'wl6587';
474+
ALTER USER wl6587@localhost PASSWORD EXPIRE;
475+
476+
--echo # non-interactive mysql should fail
477+
--error 1
478+
--exec $MYSQL -uwl6587 --password=wl6587 test -e "SELECT USER()" 2>&1
479+
480+
--echo # mysqladmin non-password should fail
481+
--replace_regex /.*mysqladmin.*: connect/mysqladmin: connect/
482+
--error 1
483+
--exec $MYSQLADMIN -S $MASTER_MYSOCK -P $MASTER_MYPORT -uwl6587 --password=wl6587 status 2>&1
484+
485+
--echo # mysqladmin password should work
486+
--exec $MYSQLADMIN -S $MASTER_MYSOCK -P $MASTER_MYPORT -uwl6587 --password=wl6587 password wl6587-2 2>&1
487+
488+
DROP USER wl6587@localhost;
489+
490+
468491
--echo # ------------------------------------------------------------------
469492
--echo # -- End of 5.6 tests
470493
--echo # ------------------------------------------------------------------

‎sql-common/client.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4420,7 +4420,12 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg)
44204420
mysql->options.extension->enable_cleartext_plugin=
44214421
(*(my_bool*) arg) ? TRUE : FALSE;
44224422
break;
4423-
4423+
case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
4424+
if (*(my_bool*) arg)
4425+
mysql->options.client_flag|= CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
4426+
else
4427+
mysql->options.client_flag&= ~CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
4428+
break;
44244429

44254430
default:
44264431
DBUG_RETURN(1);

‎sql/sql_acl.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ using std::min;
6464
using std::max;
6565

6666
bool mysql_user_table_is_in_short_password_format= false;
67+
my_bool disconnect_on_expired_password= TRUE;
6768
bool auth_plugin_is_built_in(const char *plugin_name);
6869
bool auth_plugin_supports_expiration(const char *plugin_name);
6970
void optimize_plugin_compare_by_pointer(LEX_STRING *plugin_name);
@@ -10851,6 +10852,24 @@ acl_authenticate(THD *thd, uint com_change_user_pkt_len)
1085110852
mpvio.db.str ? mpvio.db.str : (char*) "");
1085210853
}
1085310854

10855+
if (unlikely(acl_user && acl_user->password_expired
10856+
&& !(mpvio.client_capabilities &
10857+
CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)
10858+
&& disconnect_on_expired_password))
10859+
{
10860+
/*
10861+
Clients that don't signal password expiration support
10862+
get a connect error.
10863+
*/
10864+
res= CR_ERROR;
10865+
mpvio.status= MPVIO_EXT::FAILURE;
10866+
10867+
my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
10868+
general_log_print(thd, COM_CONNECT, ER(ER_MUST_CHANGE_PASSWORD));
10869+
if (log_warnings > 1)
10870+
sql_print_warning("%s", ER(ER_MUST_CHANGE_PASSWORD));
10871+
}
10872+
1085410873
if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS)
1085510874
{
1085610875
Host_errors errors;

‎sql/sql_acl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ enum mysql_user_table_field
228228

229229
extern const TABLE_FIELD_DEF mysql_db_table_def;
230230
extern bool mysql_user_table_is_in_short_password_format;
231+
extern my_bool disconnect_on_expired_password;
231232
extern const char *command_array[];
232233
extern uint command_lengths[];
233234

‎sql/sys_vars.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "sql_time.h" // known_date_time_formats
5252
#include "sql_acl.h" // SUPER_ACL,
5353
// mysql_user_table_is_in_short_password_format
54+
// disconnect_on_expired_password
5455
#include "derror.h" // read_texts
5556
#include "sql_base.h" // close_cached_tables
5657
#include "debug_sync.h" // DEBUG_SYNC
@@ -4551,3 +4552,10 @@ static Sys_var_enum Sys_gtid_mode(
45514552
#endif
45524553

45534554
#endif // HAVE_REPLICATION
4555+
4556+
4557+
static Sys_var_mybool Sys_disconnect_on_expired_password(
4558+
"disconnect_on_expired_password",
4559+
"Give clients that don't signal password expiration support execution time error(s) instead of connection error",
4560+
READ_ONLY GLOBAL_VAR(disconnect_on_expired_password),
4561+
CMD_LINE(OPT_ARG), DEFAULT(TRUE));

0 commit comments

Comments
 (0)