diff options
-rw-r--r-- | .bzrignore | 2 | ||||
-rw-r--r-- | BitKeeper/etc/logging_ok | 1 | ||||
-rw-r--r-- | client/mysqladmin.c | 9 | ||||
-rw-r--r-- | include/mysql.h | 4 | ||||
-rw-r--r-- | include/mysql_com.h | 61 | ||||
-rw-r--r-- | libmysql/libmysql.c | 58 | ||||
-rw-r--r-- | scripts/mysql_create_system_tables.sh | 2 | ||||
-rw-r--r-- | scripts/mysql_fix_privilege_tables.sql | 2 | ||||
-rw-r--r-- | sql-common/client.c | 166 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 86 | ||||
-rw-r--r-- | sql/item_strfunc.h | 30 | ||||
-rw-r--r-- | sql/mysql_priv.h | 3 | ||||
-rw-r--r-- | sql/mysqld.cc | 6 | ||||
-rw-r--r-- | sql/password.c | 828 | ||||
-rw-r--r-- | sql/slave.cc | 6 | ||||
-rw-r--r-- | sql/slave.h | 2 | ||||
-rw-r--r-- | sql/sql_acl.cc | 667 | ||||
-rw-r--r-- | sql/sql_acl.h | 13 | ||||
-rw-r--r-- | sql/sql_class.cc | 1 | ||||
-rw-r--r-- | sql/sql_class.h | 14 | ||||
-rw-r--r-- | sql/sql_parse.cc | 582 | ||||
-rw-r--r-- | sql/sql_repl.h | 2 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 50 |
23 files changed, 1138 insertions, 1457 deletions
diff --git a/.bzrignore b/.bzrignore index 4e9ce4e41ab..a23384d4170 100644 --- a/.bzrignore +++ b/.bzrignore @@ -622,3 +622,5 @@ vio/test-ssl vio/test-sslclient vio/test-sslserver vio/viotest-ssl +start_mysqld.sh +mysys/main.cc diff --git a/BitKeeper/etc/logging_ok b/BitKeeper/etc/logging_ok index 794edcdd968..ac862cc9770 100644 --- a/BitKeeper/etc/logging_ok +++ b/BitKeeper/etc/logging_ok @@ -51,6 +51,7 @@ jcole@sarvik.tfr.cafe.ee jcole@tetra.spaceapes.com jorge@linux.jorge.mysql.com kaj@work.mysql.com +kostja@oak.local lenz@kallisto.mysql.com lenz@mysql.com miguel@hegel.(none) diff --git a/client/mysqladmin.c b/client/mysqladmin.c index 018bcbc1963..f263d321a7b 100644 --- a/client/mysqladmin.c +++ b/client/mysqladmin.c @@ -769,9 +769,12 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) return 1; } if (argv[1][0]) - make_scrambled_password(crypted_pw,argv[1], - (find_type(argv[0], &command_typelib, 2) == - ADMIN_OLD_PASSWORD), &rand_st); + { + if (find_type(argv[0], &command_typelib, 2) == ADMIN_OLD_PASSWORD) + make_scrambled_password_323(crypted_pw, argv[1]); + else + make_scrambled_password(crypted_pw, argv[1]); + } else crypted_pw[0]=0; /* No password */ sprintf(buff,"set password='%s',sql_log_off=0",crypted_pw); diff --git a/include/mysql.h b/include/mysql.h index bd63a10ba45..91ef481e7f7 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -227,7 +227,9 @@ typedef struct st_mysql enum mysql_status status; my_bool free_me; /* If free in mysql_close */ my_bool reconnect; /* set to 1 if automatic reconnect */ - char scramble_buff[21]; /* New protocol requires longer scramble*/ + + char scramble[SCRAMBLE_LENGTH+1]; /* for new servers */ + char scramble_323[SCRAMBLE_LENGTH_323+1]; /* for old servers */ /* Set if this is the original connection, not a master or a slave we have diff --git a/include/mysql_com.h b/include/mysql_com.h index e87001ff27d..c1f18160667 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -48,8 +48,15 @@ enum enum_server_command }; -#define SCRAMBLE_LENGTH 8 -#define SCRAMBLE41_LENGTH 20 +/* + Length of random string sent by server on handshake; this is also length of + obfuscated password, recieved from client +*/ +#define SCRAMBLE_LENGTH 20 +#define SCRAMBLE_LENGTH_323 8 +/* length of password stored in the db: new passwords are preceeded with '*' */ +#define SCRAMBLED_PASSWORD_CHAR_LENGTH (SCRAMBLE_LENGTH*2+1) +#define SCRAMBLED_PASSWORD_CHAR_LENGTH_323 (SCRAMBLE_LENGTH_323*2) #define NOT_NULL_FLAG 1 /* Field can't be NULL */ @@ -300,31 +307,35 @@ extern "C" { extern unsigned long max_allowed_packet; extern unsigned long net_buffer_length; -void randominit(struct rand_struct *,unsigned long seed1, - unsigned long seed2); +/* + These functions are used for authentication by client and server and + implemented in sql/password.c +*/ + +void randominit(struct rand_struct *, unsigned long seed1, + unsigned long seed2); double my_rnd(struct rand_struct *); -void make_scrambled_password(char *to,const char *password, - my_bool force_old_scramble,struct rand_struct *rand_st); -int get_password_length(my_bool force_old_scramble); -char get_password_version(const char* password); -void create_random_string(int length,struct rand_struct *rand_st,char* target); -my_bool validate_password(const char* password, const char* message, - unsigned long* salt); -void password_hash_stage1(char *to, const char *password); -void password_hash_stage2(char *to,const char *salt); -void password_crypt(const char* from,char* to, const char* password,int length); -void get_hash_and_password(unsigned long* salt, unsigned char pversion,char* hash, - unsigned char* bin_password); -void get_salt_from_password(unsigned long *res,const char *password); -void create_key_from_old_password(const char* password,char* key); -void make_password_from_salt(char *to, unsigned long *hash_res, - unsigned char password_version); -char *scramble(char *to,const char *message,const char *password, - my_bool old_ver); -my_bool check_scramble(const char *, const char *message, - unsigned long *salt,my_bool old_ver); +void create_random_string(char *to, uint length, struct rand_struct *rand_st); + +void hash_password(ulong *to, const char *password); +void make_scrambled_password_323(char *to, const char *password); +char *scramble_323(char *to, const char *message, const char *password, + my_bool old_ver); +my_bool check_scramble_323(const char *, const char *message, + unsigned long *salt, my_bool old_ver); +void get_salt_from_password_323(unsigned long *res, const char *password); +void make_password_from_salt_323(char *to, const unsigned long *salt); + +void make_scrambled_password(char *to, const char *password); +char *scramble(char *to, const char *message, const char *password); +my_bool check_scramble(const char *reply, const char *message, + const unsigned char *hash_stage2); +void get_salt_from_password(unsigned char *res, const char *password); +void make_password_from_salt(char *to, const unsigned char *hash_stage2); + +/* end of password.c */ + char *get_tty_password(char *opt_message); -void hash_password(unsigned long *result, const char *password); const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); /* Some other useful functions */ diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 771278c1dbb..6bc38abb060 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -616,41 +616,51 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, /* Store user into the buffer */ end=strmov(end,user)+1; - /* - We always start with old type handshake the only difference is message sent - If server handles secure connection type we'll not send the real scramble - */ - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + /* write scrambled password according to server capabilities */ + if (passwd[0]) { - if (passwd[0]) - { - /* Prepare false scramble */ - bfill(end, SCRAMBLE_LENGTH, 'x'); - end+=SCRAMBLE_LENGTH; - *end=0; - - } - else /* For empty password */ - *end=0; /* zero length scramble */ + /* Write NULL-terminated scrambled password: */ + end= mysql->server_capabilities & CLIENT_SECURE_CONNECTION ? + scramble(end, mysql->scramble, passwd) : + scramble_323(end, mysql->scramble_323, passwd, + (my_bool) (mysql->protocol_version == 9)); } else - { - /* - Real scramble is only sent to old servers. This can be blocked - by calling mysql_options(MYSQL *, MYSQL_SECURE_CONNECT, (char*) &1); - */ - end=scramble(end, mysql->scramble_buff, passwd, - (my_bool) (mysql->protocol_version == 9)); - } + *end= '\0'; // empty password /* Add database if needed */ end=strmov(end+1,db ? db : ""); /* Write authentication package */ simple_command(mysql,COM_CHANGE_USER, buff,(ulong) (end-buff),1); - if (mysql_autenticate(mysql, passwd)) + NET *net= &mysql->net; + ulong pkt_length= net_safe_read(mysql); + + if (pkt_length == packet_error) goto error; + if (net->read_pos[0] == mysql->scramble_323[0] && + pkt_length == SCRAMBLE_LENGTH_323 + 1 && + mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + By sending this very specific reply server asks us to send scrambled + password in old format. The reply contains scramble_323. + */ + scramble_323(buff, mysql->scramble_323, passwd, + (my_bool) (mysql->protocol_version == 9)); + if (my_net_write(net, buff, SCRAMBLE_LENGTH_323 + 1) || net_flush(net)) + { + net->last_errno= CR_SERVER_LOST; + strmov(net->sqlstate, unknown_sqlstate); + strmov(net->last_error,ER(net->last_errno)); + goto error; + } + /* Read what server thinks about out new auth message report */ + if (net_safe_read(mysql) == packet_error) + goto error; + } + /* Free old connect information */ my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); diff --git a/scripts/mysql_create_system_tables.sh b/scripts/mysql_create_system_tables.sh index 2739c45e750..c4cdc7b52d7 100644 --- a/scripts/mysql_create_system_tables.sh +++ b/scripts/mysql_create_system_tables.sh @@ -108,7 +108,7 @@ then c_u="$c_u CREATE TABLE user (" c_u="$c_u Host char(60) binary DEFAULT '' NOT NULL," c_u="$c_u User char(16) binary DEFAULT '' NOT NULL," - c_u="$c_u Password char(45) binary DEFAULT '' NOT NULL," + c_u="$c_u Password char(41) binary DEFAULT '' NOT NULL," c_u="$c_u Select_priv enum('N','Y') DEFAULT 'N' NOT NULL," c_u="$c_u Insert_priv enum('N','Y') DEFAULT 'N' NOT NULL," c_u="$c_u Update_priv enum('N','Y') DEFAULT 'N' NOT NULL," diff --git a/scripts/mysql_fix_privilege_tables.sql b/scripts/mysql_fix_privilege_tables.sql index 43dc6d89481..8c5b29a49ff 100644 --- a/scripts/mysql_fix_privilege_tables.sql +++ b/scripts/mysql_fix_privilege_tables.sql @@ -4,7 +4,7 @@ ALTER TABLE host type=MyISAM; ALTER TABLE func type=MyISAM; ALTER TABLE columns_priv type=MyISAM; ALTER TABLE tables_priv type=MyISAM; -ALTER TABLE user change Password Password char(45) not null; +ALTER TABLE user change Password Password char(41) not null; ALTER TABLE user add File_priv enum('N','Y') NOT NULL; CREATE TABLE IF NOT EXISTS func ( name char(64) DEFAULT '' NOT NULL, diff --git a/sql-common/client.c b/sql-common/client.c index 721164c8301..35dea62edc3 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -1335,76 +1335,6 @@ mysql_ssl_free(MYSQL *mysql __attribute__((unused))) /* - Handle password authentication -*/ - -my_bool mysql_autenticate(MYSQL *mysql, const char *passwd) -{ - ulong pkt_length; - NET *net= &mysql->net; - char buff[SCRAMBLE41_LENGTH]; - char password_hash[SCRAMBLE41_LENGTH]; /* Used for storage of stage1 hash */ - - /* We shall only query server if it expect us to do so */ - if ((pkt_length=net_safe_read(mysql)) == packet_error) - goto error; - - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - /* - This should always happen with new server unless empty password - OK/Error packets have zero as the first char - */ - if (pkt_length == 24 && net->read_pos[0]) - { - /* Old passwords will have '*' at the first byte of hash */ - if (net->read_pos[0] != '*') - { - /* Build full password hash as it is required to decode scramble */ - password_hash_stage1(buff, passwd); - /* Store copy as we'll need it later */ - memcpy(password_hash,buff,SCRAMBLE41_LENGTH); - /* Finally hash complete password using hash we got from server */ - password_hash_stage2(password_hash,(const char*) net->read_pos); - /* Decypt and store scramble 4 = hash for stage2 */ - password_crypt((const char*) net->read_pos+4,mysql->scramble_buff, - password_hash, SCRAMBLE41_LENGTH); - mysql->scramble_buff[SCRAMBLE41_LENGTH]=0; - /* Encode scramble with password. Recycle buffer */ - password_crypt(mysql->scramble_buff,buff,buff,SCRAMBLE41_LENGTH); - } - else - { - /* Create password to decode scramble */ - create_key_from_old_password(passwd,password_hash); - /* Decypt and store scramble 4 = hash for stage2 */ - password_crypt((const char*) net->read_pos+4,mysql->scramble_buff, - password_hash, SCRAMBLE41_LENGTH); - mysql->scramble_buff[SCRAMBLE41_LENGTH]=0; - /* Finally scramble decoded scramble with password */ - scramble(buff, mysql->scramble_buff, passwd,0); - } - /* Write second package of authentication */ - if (my_net_write(net,buff,SCRAMBLE41_LENGTH) || net_flush(net)) - { - net->last_errno= CR_SERVER_LOST; - strmov(net->sqlstate, unknown_sqlstate); - strmov(net->last_error,ER(net->last_errno)); - goto error; - } - /* Read what server thinks about out new auth message report */ - if (net_safe_read(mysql) == packet_error) - goto error; - } - } - return 0; - -error: - return 1; -} - - -/* Note that the mysql argument must be initialized with mysql_init() before calling mysql_real_connect ! */ @@ -1481,7 +1411,7 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, mysql->server_status=SERVER_STATUS_AUTOCOMMIT; /* - Grab a socket and connect it to the server + Part 0: Grab a socket and connect it to the server */ #if defined(HAVE_SMEM) if ((!mysql->options.protocol || @@ -1682,6 +1612,11 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, strmov(net->last_error,ER(net->last_errno)); goto error; } + + /* + Part 1: Connection established, read and parse first packet + */ + if ((pkt_length=net_safe_read(mysql)) == packet_error) goto error; @@ -1702,8 +1637,14 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, end=strend((char*) net->read_pos+1); mysql->thread_id=uint4korr(end+1); end+=5; - strmake(mysql->scramble_buff,end,8); - end+=9; + /* + Scramble is split into two parts because old clients does not understand + long scrambles; here goes the first part. + */ + strmake(mysql->scramble_323, end, SCRAMBLE_LENGTH_323); + end+= SCRAMBLE_LENGTH_323+1; + memcpy(mysql->scramble, mysql->scramble_323, SCRAMBLE_LENGTH_323); + if (pkt_length >= (uint) (end+1 - (char*) net->read_pos)) mysql->server_capabilities=uint2korr(end); if (pkt_length >= (uint) (end+18 - (char*) net->read_pos)) @@ -1712,6 +1653,13 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, mysql->server_language=end[2]; mysql->server_status=uint2korr(end+3); } + end+= 18; + if (pkt_length >= (uint) (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 - + (char *) net->read_pos)) + strmake(mysql->scramble+SCRAMBLE_LENGTH_323, end, + SCRAMBLE_LENGTH-SCRAMBLE_LENGTH_323); + else + mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION; /* Set character set */ if ((charset_name=mysql->options.charset_name)) @@ -1783,9 +1731,12 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, mysql->unix_socket=0; strmov(mysql->server_version,(char*) net->read_pos+1); mysql->port=port; - client_flag|=mysql->options.client_flag; - /* Send client information for access check */ + /* + Part 2: format and send client info to the server for access check + */ + + client_flag|=mysql->options.client_flag; client_flag|=CLIENT_CAPABILITIES; if (client_flag & CLIENT_MULTI_QUERIES) client_flag|= CLIENT_MULTI_RESULTS; @@ -1881,35 +1832,18 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, #include "_cust_libmysql.h" #endif DBUG_PRINT("info",("user: %s",end)); - /* - We always start with old type handshake the only difference is message sent - If server handles secure connection type we'll not send the real scramble - */ - if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + end= strend(end) + 1; + if (passwd[0]) { - if (passwd[0]) - { - /* Prepare false scramble */ - end=strend(end)+1; - bfill(end, SCRAMBLE_LENGTH, 'x'); - end+=SCRAMBLE_LENGTH; - *end=0; - } - else /* For empty password*/ - { - end=strend(end)+1; - *end=0; /* Store zero length scramble */ - } + /* Write NULL-terminated scrambled password: */ + end= mysql->server_capabilities & CLIENT_SECURE_CONNECTION ? + scramble(end, mysql->scramble, passwd) : + scramble_323(end, mysql->scramble_323, passwd, + (my_bool) (mysql->protocol_version == 9)); } else - { - /* - Real scramble is only sent to old servers. This can be blocked - by calling mysql_options(MYSQL *, MYSQL_SECURE_CONNECT, (char*) &1); - */ - end=scramble(strend(end)+1, mysql->scramble_buff, passwd, - (my_bool) (mysql->protocol_version == 9)); - } + *end= '\0'; /* empty password */ + /* Add database if needed */ if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) { @@ -1925,10 +1859,38 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, strmov(net->last_error,ER(net->last_errno)); goto error; } + + /* + Part 3: Authorization data's been sent. Now server can reply with + OK-packet, or re-request scrambled password. + */ - if (mysql_autenticate(mysql, passwd)) + if ((pkt_length=net_safe_read(mysql)) == packet_error) goto error; + if (net->read_pos[0] == mysql->scramble_323[0] && + pkt_length == SCRAMBLE_LENGTH_323 + 1 && + mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + By sending this very specific reply server asks us to send scrambled + password in old format. The reply contains scramble_323. + */ + scramble_323(buff, mysql->scramble_323, passwd, + (my_bool) (mysql->protocol_version == 9)); + if (my_net_write(net, buff, SCRAMBLE_LENGTH_323 + 1) || net_flush(net)) + { + net->last_errno= CR_SERVER_LOST; + strmov(net->sqlstate, unknown_sqlstate); + strmov(net->last_error,ER(net->last_errno)); + goto error; + } + /* Read what server thinks about out new auth message report */ + if (net_safe_read(mysql) == packet_error) + goto error; + } + + if (client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress=1; diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index ae63ac85d4d..f8488565b75 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1346,97 +1346,35 @@ void Item_func_trim::fix_length_and_dec() } - - -void Item_func_password::fix_length_and_dec() -{ - /* - If PASSWORD() was called with only one argument, it depends on a random - number so we need to save this random number into the binary log. - If called with two arguments, it is repeatable. - */ - if (arg_count == 1) - { - THD *thd= current_thd; - thd->rand_used= 1; - thd->rand_saved_seed1= thd->rand.seed1; - thd->rand_saved_seed2= thd->rand.seed2; - } - max_length= get_password_length(use_old_passwords); -} - -/* - Password() function has 2 arguments. Second argument can be used - to make results repeatable -*/ +/* Item_func_password */ String *Item_func_password::val_str(String *str) { - struct rand_struct rand_st; // local structure for 2 param version - ulong seed=0; // seed to initialise random generator to - - String *res =args[0]->val_str(str); + String *res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) return 0; - - if (arg_count == 1) - { - if (res->length() == 0) - return &empty_string; - make_scrambled_password(tmp_value,res->c_ptr(),use_old_passwords, - ¤t_thd->rand); - str->set(tmp_value,get_password_length(use_old_passwords),res->charset()); - return str; - } - else - { - /* We'll need the buffer to get second parameter */ - char key_buff[80]; - String tmp_key_value(key_buff, sizeof(key_buff), system_charset_info); - String *key =args[1]->val_str(&tmp_key_value); - - /* Check second argument for NULL value. First one is already checked */ - if ((null_value=args[1]->null_value)) - return 0; - - /* This shall be done after checking for null for proper results */ - if (res->length() == 0) - return &empty_string; - - /* Generate the seed first this allows to avoid double allocation */ - char* seed_ptr=key->c_ptr(); - while (*seed_ptr) - { - seed=(seed*211+*seed_ptr) & 0xffffffffL; /* Use simple hashing */ - seed_ptr++; - } - - /* Use constants which allow nice random values even with small seed */ - randominit(&rand_st, - (ulong) ((ulonglong) seed*111111+33333333L) & (ulong) 0xffffffff, - (ulong) ((ulonglong) seed*1111+55555555L) & (ulong) 0xffffffff); - - make_scrambled_password(tmp_value,res->c_ptr(),use_old_passwords, - &rand_st); - str->set(tmp_value,get_password_length(use_old_passwords),res->charset()); - return str; - } + if (res->length() == 0) + return &empty_string; + make_scrambled_password(tmp_value, res->c_ptr()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, res->charset()); + return str; } +/* Item_func_old_password */ + String *Item_func_old_password::val_str(String *str) { - String *res =args[0]->val_str(str); + String *res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) return 0; if (res->length() == 0) return &empty_string; - make_scrambled_password(tmp_value,res->c_ptr(),1,¤t_thd->rand); - str->set(tmp_value,16,res->charset()); + make_scrambled_password_323(tmp_value, res->c_ptr()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, res->charset()); return str; } - #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') String *Item_func_encrypt::val_str(String *str) diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 6cc6d730627..11f5a66b3d1 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -254,30 +254,44 @@ public: }; +/* + Item_func_password -- new (4.1.1) PASSWORD() function implementation. + Returns strcat('*', octet2hex(sha1(sha1(password)))). '*' stands for new + password format, sha1(sha1(password) is so-called hash_stage2 value. + Length of returned string is always 41 byte. To find out how entire + authentification procedure works, see comments in password.c. +*/ + class Item_func_password :public Item_str_func { - char tmp_value[64]; /* This should be enough for new password format */ + char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; public: Item_func_password(Item *a) :Item_str_func(a) {} - Item_func_password(Item *a, Item *b) :Item_str_func(a,b) {} - String *val_str(String *); - void fix_length_and_dec(); + String *val_str(String *str); + void fix_length_and_dec() { max_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; } const char *func_name() const { return "password"; } }; +/* + Item_func_old_password -- PASSWORD() implementation used in MySQL 3.21 - 4.0 + compatibility mode. This item is created in sql_yacc.yy when + 'use_old_passwords' session variable is set, and to handle OLD_PASSWORD() + function. +*/ + class Item_func_old_password :public Item_str_func { - char tmp_value[17]; /* old password length +1 */ + char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1]; public: Item_func_old_password(Item *a) :Item_str_func(a) {} - String *val_str(String *); - void fix_length_and_dec() { max_length = get_password_length(1); } + String *val_str(String *str); + void fix_length_and_dec() { max_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } const char *func_name() const { return "old_password"; } + unsigned int size_of() { return sizeof(*this);} }; - class Item_func_des_encrypt :public Item_str_func { String tmp_value; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index aca84f1bcb3..13ff168e553 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -74,9 +74,6 @@ extern CHARSET_INFO *national_charset_info, *table_alias_charset; ****************************************************************************/ #define ACL_CACHE_SIZE 256 -/* Password lengh for 4.1 version previous versions had 16 bytes password hash */ -#define HASH_PASSWORD_LENGTH 45 -#define HASH_OLD_PASSWORD_LENGTH 16 #define HOST_CACHE_SIZE 128 #define MAX_ACCEPT_RETRY 10 // Test accept this many times #define MAX_FIELDS_BEFORE_HASH 32 diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 72ee3e30c63..2677973ff0e 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2743,12 +2743,6 @@ static void create_new_thread(THD *thd) if (thread_count-delayed_insert_threads > max_used_connections) max_used_connections=thread_count-delayed_insert_threads; thd->thread_id=thread_id++; - for (uint i=0; i < 8 ; i++) // Generate password teststring - thd->scramble[i]= (char) (my_rnd(&sql_rand)*94+33); - thd->scramble[8]=0; - // Back it up as old clients may need it - memcpy(thd->old_scramble,thd->scramble,9); - thd->real_id=pthread_self(); // Keep purify happy diff --git a/sql/password.c b/sql/password.c index 257547671e5..be6514d89c6 100644 --- a/sql/password.c +++ b/sql/password.c @@ -29,28 +29,33 @@ The password is saved (in user.password) by using the PASSWORD() function in mysql. + This is .c file because it's used in libmysqlclient, which is entirely in C. + (we need it to be portable to a variety of systems). Example: update user set password=PASSWORD("hello") where user="test" This saves a hashed number as a string in the password field. + The new autentication is performed in following manner: - New in MySQL 4.1 authentication works even more secure way. - At the first step client sends user name to the sever, and password if - it is empty. So in case of empty password authentication is as fast as before. - At the second stap servers sends scramble to client, which is encoded with - password stage2 hash stored in the password database as well as salt, needed - for client to build stage2 password to decrypt scramble. - Client decrypts the scramble and encrypts it once again with stage1 password. - This information is sent to server. - Server decrypts the scramble to get stage1 password and hashes it to get - stage2 hash. This hash is when compared to hash stored in the database. + SERVER: public_seed=create_random_string() + send(public_seed) - This authentication needs 2 packet round trips instead of one but it is much - stronger. Now if one will steal mysql database content he will not be able - to break into MySQL. + CLIENT: recv(public_seed) + hash_stage1=sha1("password") + hash_stage2=sha1(hash_stage1) + reply=xor(hash_stage1, sha1(public_seed,hash_stage2) - New Password handling functions by Peter Zaitsev + // this three steps are done in scramble() + send(reply) + + + SERVER: recv(reply) + hash_stage1=xor(reply, sha1(public_seed,hash_stage2)) + candidate_hash2=sha1(hash_stage1) + check(candidate_hash2==hash_stage2) + + // this three steps are done in check_scramble() *****************************************************************************/ @@ -60,31 +65,21 @@ #include <sha1.h> #include "mysql.h" - - -/* Character to use as version identifier for version 4.1 */ -#define PVERSION41_CHAR '*' - -/* Scramble length for new password version */ - +/************ MySQL 3.23-4.0 authentification routines: untouched ***********/ /* New (MySQL 3.21+) random generation structure initialization - SYNOPSIS randominit() rand_st OUT Structure to initialize seed1 IN First initialization parameter seed2 IN Second initialization parameter - - RETURN - none */ -void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) -{ /* For mysql 3.21.# */ +void randominit(struct rand_struct *rand_st, ulong seed1, ulong seed2) +{ /* For mysql 3.21.# */ #ifdef HAVE_purify - bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ + bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ #endif rand_st->max_value= 0x3FFFFFFFL; rand_st->max_value_dbl=(double) rand_st->max_value; @@ -95,18 +90,15 @@ void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) /* Old (MySQL 3.20) random generation structure initialization - + XXX: is to be deleted very soon! SYNOPSIS old_randominit() rand_st OUT Structure to initialize seed1 IN First initialization parameter - - RETURN - none */ -static void old_randominit(struct rand_struct *rand_st,ulong seed1) -{ /* For mysql 3.20.# */ +static void old_randominit(struct rand_struct *rand_st, ulong seed1) +{ /* For mysql 3.20.# */ rand_st->max_value= 0x01FFFFFFL; rand_st->max_value_dbl=(double) rand_st->max_value; seed1%=rand_st->max_value; @@ -115,14 +107,12 @@ static void old_randominit(struct rand_struct *rand_st,ulong seed1) /* - Generate Random number - + Generate random number. SYNOPSIS my_rnd() rand_st INOUT Structure used for number generation - - RETURN - Generated pseudo random number + RETURN VALUE + generated pseudo random number */ double my_rnd(struct rand_struct *rand_st) @@ -134,63 +124,12 @@ double my_rnd(struct rand_struct *rand_st) /* - Generate String of printable random characters of requested length - String will not be zero terminated. - + Generate binary hash from raw text string + Used for Pre-4.1 password handling SYNOPSIS - create_random_string() - length IN Lenght of - rand_st INOUT Structure used for number generation - target OUT Buffer for generation - - RETURN - none -*/ - -void create_random_string(int length,struct rand_struct *rand_st,char *target) -{ - char *end=target+length; - /* Use pointer arithmetics as it is faster way to do so. */ - for (; target<end ; target++) - *target= (char) (my_rnd(rand_st)*94+33); -} - - -/* - Encrypt/Decrypt function used for password encryption in authentication - Simple XOR is used here but it is OK as we crypt random strings - - SYNOPSIS - password_crypt() - from IN Data for encryption - to OUT Encrypt data to the buffer (may be the same) - password IN Password used for encryption (same length) - length IN Length of data to encrypt - - RETURN - none -*/ - -void password_crypt(const char *from,char *to, const char *password,int length) -{ - const char *from_end=from+length; - - while (from < from_end) - *to++= *(from++) ^* (password++); -} - - -/* - Generate binary hash from raw text password - Used for Pre-4.1 Password handling - - SYNOPSIS - hash_pasword() - result OUT Store hash in this location - password IN Plain text password to build hash - - RETURN - none + hash_password() + result OUT store hash in this location + password IN plain text password to build hash */ void hash_password(ulong *result, const char *password) @@ -200,7 +139,7 @@ void hash_password(ulong *result, const char *password) for (; *password ; password++) { if (*password == ' ' || *password == '\t') - continue; /* skipp space in password */ + continue; /* skip space in password */ tmp= (ulong) (uchar) *password; nr^= (((nr & 63)+add)*tmp)+ (nr << 8); nr2+=(nr2 << 8) ^ nr; @@ -213,514 +152,407 @@ void hash_password(ulong *result, const char *password) /* - Stage one password hashing. - Used in MySQL 4.1 password handling - + Create password to be stored in user database from raw string + Used for pre-4.1 password handling SYNOPSIS - password_hash_stage1() - to OUT Store stage one hash to this location - password IN Plain text password to build hash - - RETURN - none + make_scrambled_password_323() + to OUT store scrambled password here + password IN user-supplied password */ -void password_hash_stage1(char *to, const char *password) +void make_scrambled_password_323(char *to, const char *password) { - SHA1_CONTEXT context; - sha1_reset(&context); - for (; *password ; password++) - { - if (*password == ' ' || *password == '\t') - continue;/* skip space in password */ - sha1_input(&context,(uint8*) &password[0],1); - } - sha1_result(&context,(uint8*)to); + ulong hash_res[2]; + hash_password(hash_res, password); + sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]); } /* - Stage two password hashing. - Used in MySQL 4.1 password handling - + Scramble string with password. + Used in pre 4.1 authentication phase. SYNOPSIS - password_hash_stage2() - to INOUT Use this as stage one hash and store stage two hash here - salt IN Salt used for stage two hashing - + scramble_323() + to OUT Store scrambled message here. Buffer must be at least + SCRAMBLE_LENGTH_323+1 bytes long + message IN Message to scramble. Message must be exactly + SRAMBLE_LENGTH_323 long and NULL terminated. + password IN Password to use while scrambling + old_ver IN Force old version random number generator RETURN - none + End of scrambled string */ -void password_hash_stage2(char *to, const char *salt) +char *scramble_323(char *to, const char *message, const char *password, + my_bool old_ver) { - SHA1_CONTEXT context; - sha1_reset(&context); - sha1_input(&context,(uint8*) salt, 4); - sha1_input(&context,(uint8*) to, SHA1_HASH_SIZE); - sha1_result(&context,(uint8*) to); + struct rand_struct rand_st; + ulong hash_pass[2], hash_message[2]; + + if (password && password[0]) + { + char *to_start=to; + hash_password(hash_pass,password); + hash_password(hash_message, message); + if (old_ver) + old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); + else + randominit(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + while (*message++) + *to++= (char) (floor(my_rnd(&rand_st)*31)+64); + if (!old_ver) + { /* Make it harder to break */ + char extra=(char) (floor(my_rnd(&rand_st)*31)); + while (to_start != to) + *(to_start++)^=extra; + } + } + *to=0; + return to; } /* - Create password to be stored in user database from raw string - Handles both MySQL 4.1 and Pre-MySQL 4.1 passwords - + Check scrambled message + Used in pre 4.1 password handling SYNOPSIS - make_scramble_password() - to OUT Store scrambled password here - password IN Raw string password - force_old_scramle - IN Force generation of old scramble variant - rand_st INOUT Structure for temporary number generation. - RETURN - none + check_scramble_323() + scrambled IN scrambled message to check. + message IN original random message which was used for scrambling; must + be exactly SCRAMBLED_LENGTH_323 bytes long and + NULL-terminated. + hash_pass IN password which should be used for scrambling + old_ver IN force old (3.20) version random number generator + RETURN VALUE + 0 - password correct + !0 - password invalid */ -void make_scrambled_password(char *to,const char *password, - my_bool force_old_scramble, - struct rand_struct *rand_st) +my_bool +check_scramble_323(const char *scrambled, const char *message, + ulong *hash_pass, my_bool old_ver) { - ulong hash_res[2]; /* Used for pre 4.1 password hashing */ - unsigned short salt; /* Salt for 4.1 version password */ - uint8 digest[SHA1_HASH_SIZE]; - if (force_old_scramble) /* Pre 4.1 password encryption */ - { - hash_password(hash_res,password); - sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); - } - else /* New password 4.1 password scrambling */ + struct rand_struct rand_st; + ulong hash_message[2]; + char buff[16],*to,extra; /* Big enough for check */ + const char *pos; + + /* Check if this exactly N bytes. Overwise this is something fishy */ + if (strlen(message) != SCRAMBLE_LENGTH_323) + return 1; /* Wrong password */ + + hash_password(hash_message,message); + if (old_ver) + old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); + else + randominit(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + to=buff; + for (pos=scrambled ; *pos ; pos++) + *to++=(char) (floor(my_rnd(&rand_st)*31)+64); + if (old_ver) + extra=0; + else + extra=(char) (floor(my_rnd(&rand_st)*31)); + to=buff; + while (*scrambled) { - to[0]=PVERSION41_CHAR; /* New passwords have version prefix */ - /* Rnd returns number from 0 to 1 so this would be good salt generation.*/ - salt=(unsigned short) (my_rnd(rand_st)*65535+1); - /* Use only 2 first bytes from it */ - sprintf(to+1,"%04x",salt); - /* First hasing is done without salt */ - password_hash_stage1((char*) digest, password); - /* Second stage is done with salt */ - password_hash_stage2((char*) digest,(char*)to+1), - /* Print resulting hash into the password*/ - sprintf(to+5, - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6], - digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13], - digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]); + if (*scrambled++ != (char) (*to++ ^ extra)) + return 1; /* Wrong password */ } + return 0; +} + +static uint8 char_val(uint8 X) +{ + return (uint) (X >= '0' && X <= '9' ? X-'0' : + X >= 'A' && X <= 'Z' ? X-'A'+10 : X-'a'+10); } /* - Convert password from binary string form to salt form - Used for MySQL 4.1 password handling - + Convert password from hex string (as stored in mysql.user) to binary form. SYNOPSIS - get_salt_from_bin_password() - res OUT Store salt form password here - password IN Binary password to be converted - salt IN hashing-salt to be used for salt form generation - - RETURN - none + get_salt_from_password_323() + res OUT store salt here + password IN password string as stored in mysql.user + NOTE + This function does not have length check for passwords. It will just crash + Password hashes in old format must have length divisible by 8 */ -void get_salt_from_bin_password(ulong *res,unsigned char *password,ulong salt) +void get_salt_from_password_323(ulong *res, const char *password) { - unsigned char *password_end=password+SCRAMBLE41_LENGTH; - *res=salt; - res++; - - /* Process password of known length*/ - while (password<password_end) + res[0]= res[1]= 0; + if (password) { - ulong val=0; - uint i; - for (i=0 ; i < 4 ; i++) - val=(val << 8)+(*password++); - *res++=val; + while (*password) + { + ulong val=0; + uint i; + for (i=0 ; i < 8 ; i++) + val=(val << 4)+char_val(*password++); + *res++=val; + } } } /* - Validate password for MySQL 4.1 password handling. - + Convert scrambled password from binary form to asciiz hex string. SYNOPSIS - validate_password() - password IN Encrypted Scramble which we got from the client - message IN Original scramble which we have sent to the client before - salt IN Password in the salted form to match to - - RETURN - 0 for correct password - !0 for invalid password + make_password_from_salt_323() + to OUT store resulting string password here, at least 17 bytes + salt IN password in salt format, 2 ulongs */ -my_bool validate_password(const char *password, const char *message, - ulong *salt) +void make_password_from_salt_323(char *to, const ulong *salt) { - char buffer[SCRAMBLE41_LENGTH]; /* Used for password validation */ - char tmpsalt[8]; /* Temporary value to convert salt to string form */ - ulong salt_candidate[6]; /* Computed candidate salt */ - ulong *sc=salt_candidate; /* we need to be able to increment */ - ulong *salt_end; - - /* Now we shall get stage1 encrypted password in buffer*/ - password_crypt(password,buffer,message,SCRAMBLE41_LENGTH); - - /* For compatibility reasons we use ulong to store salt while we need char */ - sprintf(tmpsalt,"%04x",(unsigned short)salt[0]); - - password_hash_stage2(buffer,tmpsalt); - /* Convert password to salt to compare */ - get_salt_from_bin_password(salt_candidate,(uchar*) buffer,salt[0]); - - /* Now we shall get exactly the same password as we have stored for user */ - for (salt_end=salt+5 ; salt < salt_end; ) - if (*++salt != *++sc) - return 1; - - /* Or password correct*/ - return 0; + sprintf(to,"%08lx%08lx", salt[0], salt[1]); } +/******************* MySQL 4.1.1 authentification routines ******************/ /* - Get length of password string which is stored in mysql.user table - + Generate string of printable random characters of requested length SYNOPSIS - get_password_length() - force_old_scramble IN If we wish to use pre 4.1 scramble format - - RETURN - password length >0 + create_random_string() + to OUT buffer for generation; must be at least length+1 bytes + long; result string is always null-terminated + length IN how many random characters to put in buffer + rand_st INOUT structure used for number generation */ -int get_password_length(my_bool force_old_scramble) +void create_random_string(char *to, uint length, struct rand_struct *rand_st) { - return (force_old_scramble) ? 16 : SHA1_HASH_SIZE*2+4+1; + char *end= to + length; + /* Use pointer arithmetics as it is faster way to do so. */ + for (; to < end; to++) + *to= (char) (my_rnd(rand_st)*94+33); + *to= '\0'; } -/* - Get version of the password based on mysql.user password string - - SYNOPSIS - get_password_version() - password IN Password string as stored in mysql.user - - RETURN - 0 for pre 4.1 passwords - !0 password version char for newer passwords -*/ +/* Character to use as version identifier for version 4.1 */ -char get_password_version(const char *password) -{ - if (password==NULL) return 0; - if (password[0]==PVERSION41_CHAR) return PVERSION41_CHAR; - return 0; -} +#define PVERSION41_CHAR '*' /* - Get integer value of Hex character - + Convert given octet sequence to asciiz string of hex characters; + str..str+len and 'to' may not overlap. SYNOPSIS - char_val() - X IN Character to find value for - - RETURN - Appropriate integer value + octet2hex() + buf OUT output buffer. Must be at least 2*len+1 bytes + str, len IN the beginning and the length of the input string */ - - -static inline unsigned int char_val(char X) +static +void +octet2hex(char *to, const uint8 *str, uint len) { - return (uint) (X >= '0' && X <= '9' ? X-'0' : - X >= 'A' && X <= 'Z' ? X-'A'+10 : - X-'a'+10); + static const char alphabet[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const uint8 *str_end= str + len; + for (; str != str_end; ++str) + { + *to++= alphabet[(*str & 0xF0) >> 4]; + *to++= alphabet[*str & 0x0F]; + } + *to++= '\0'; } /* - Get Binary salt from password as in mysql.user format - + Convert given asciiz string of hex (0..9 a..f) characters to octet + sequence. SYNOPSIS - get_salt_from_password() - res OUT Store binary salt here - password IN Password string as stored in mysql.user - - RETURN - none - - NOTE - This function does not have length check for passwords. It will just crash - Password hashes in old format must have length divisible by 8 -*/ - -void get_salt_from_password(ulong *res,const char *password) + hex2octet() + to OUT buffer to place result; must be at least len/2 bytes + str, len IN begin, length for character string; str and to may not + overlap; len % 2 == 0 +*/ + +static +void +hex2octet(uint8 *to, const char *str, uint len) { - if (password) /* zero salt corresponds to empty password */ + const char *str_end= str + len; + while (str < str_end) { - if (password[0]==PVERSION41_CHAR) /* if new password */ - { - uint val=0; - uint i; - password++; /* skip version identifier */ - - /*get hashing salt from password and store in in the start of array */ - for (i=0 ; i < 4 ; i++) - val=(val << 4)+char_val(*password++); - *res++=val; - } - /* We process old passwords the same way as new ones in other case */ -#ifdef EXTRA_DEBUG - if (strlen(password)%8!=0) - fprintf(stderr,"Warning: Incorrect password length for salting: %d\n", - strlen(password)); -#endif - while (*password) - { - ulong val=0; - uint i; - for (i=0 ; i < 8 ; i++) - val=(val << 4)+char_val(*password++); - *res++=val; - } + *to= char_val(*str++) << 4; + *to++|= char_val(*str++); } - return; } /* - Get string version as stored in mysql.user from salt form - + Encrypt/Decrypt function used for password encryption in authentication. + Simple XOR is used here but it is OK as we crypt random strings. Note, + that XOR(s1, XOR(s1, s2)) == s2, XOR(s1, s2) == XOR(s2, s1) SYNOPSIS - make_password_from_salt() - to OUT Store resulting string password here - hash_res IN Password in salt format - password_version - IN According to which version salt should be treated - - RETURN - none + my_crypt() + to OUT buffer to hold crypted string; must be at least len bytes + long; to and s1 (or s2) may be the same. + s1, s2 IN input strings (of equal length) + len IN length of s1 and s2 */ -void make_password_from_salt(char *to, ulong *hash_res,uint8 password_version) +static +void +my_crypt(char *to, const uint8 *s1, const uint8 *s2, uint len) { - if (!password_version) /* Handling of old passwords. */ - sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); - else - if (password_version==PVERSION41_CHAR) - sprintf(to,"%c%04x%08lx%08lx%08lx%08lx%08lx",PVERSION41_CHAR,(unsigned short)hash_res[0],hash_res[1], - hash_res[2],hash_res[3],hash_res[4],hash_res[5]); - else /* Just use empty password if we can't handle it. This should not happen */ - to[0]='\0'; + const uint8 *s1_end= s1 + len; + while (s1 < s1_end) + *to++= *s1++ ^ *s2++; } /* - Convert password in salted form to binary string password and hash-salt - For old password this involes one more hashing - + MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice + applied to the password string, and then produced octet sequence is + converted to hex string. + The result of this function is used as return value from PASSWORD() and + is stored in the database. SYNOPSIS - get_hash_and_password() - salt IN Salt to convert from - pversion IN Password version to use - hash OUT Store zero ended hash here - bin_password OUT Store binary password here (no zero at the end) - - RETURN - 0 for pre 4.1 passwords - !0 password version char for newer passwords + make_scrambled_password() + buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string + password IN NULL-terminated password string */ -void get_hash_and_password(ulong *salt, uint8 pversion, char *hash, - unsigned char *bin_password) +void +make_scrambled_password(char *to, const char *password) { - int t; - ulong* salt_end; - ulong val; - SHA1_CONTEXT context; - - if (pversion) /* New password version assumed */ - { - salt_end=salt+5; - sprintf(hash,"%04x",(unsigned short)salt[0]); - while (salt<salt_end) - { - val=*(++salt); - for (t=3; t>=0; t--) - { - bin_password[t]= (char) (val & 255); - val>>=8; /* Scroll 8 bits to get next part*/ - } - bin_password+=4; /* Get to next 4 chars*/ - } - } - else - { - unsigned char *bp= bin_password; /* Binary password loop pointer */ - - /* Use zero starting hash as an indication of old password */ - hash[0]=0; - salt_end=salt+2; - /* Encode salt using SHA1 here */ - sha1_reset(&context); - while (salt<salt_end) /* Iterate over these elements*/ - { - val= *salt; - for (t=3;t>=0;t--) - { - bp[t]= (uchar) (val & 255); - val>>=8; /* Scroll 8 bits to get next part*/ - } - bp+= 4; /* Get to next 4 chars*/ - salt++; - } - /* Use 8 bytes of binary password for hash */ - sha1_input(&context,(uint8*)bin_password,8); - sha1_result(&context,(uint8*)bin_password); - } + SHA1_CONTEXT sha1_context; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* stage 1: hash password */ + sha1_input(&sha1_context, (uint8 *) password, strlen(password)); + sha1_result(&sha1_context, (uint8 *) to); + /* stage 2: hash stage1 output */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE); + /* separate buffer is used to pass 'to' in octet2hex */ + sha1_result(&sha1_context, hash_stage2); + /* convert hash_stage2 to hex string */ + *to++= PVERSION41_CHAR; + octet2hex(to, hash_stage2, SHA1_HASH_SIZE); } - + /* - Create key from old password to decode scramble - Used in 4.1 authentication with passwords stored old way - + Produce an obscure octet sequence from password and random + string, recieved from the server. This sequence corresponds to the + password, but password can not be easily restored from it. The sequence + is then sent to the server for validation. Trailing zero is stored in + the buf. + This function is used by client to create authenticated reply to the + server's greeting. SYNOPSIS - create_key_from_old_password() - passwd IN Password used for key generation - key OUT Created 20 bytes key - - RETURN - None + scramble() + buf OUT store scrambled string here. The buf must be at least + SHA1_HASH_SIZE+1 bytes long. + message IN random message, must be exactly SCRAMBLE_LENGTH long and + NULL-terminated. + password IN users' password + RETURN VALUE + end of scrambled string */ - -void create_key_from_old_password(const char *passwd, char *key) +char * +scramble(char *to, const char *message, const char *password) { - char buffer[SCRAMBLE41_LENGTH]; /* Buffer for various needs */ - ulong salt[6]; /* Salt (large for safety) */ - /* At first hash password to the string stored in password */ - make_scrambled_password(buffer,passwd,1,(struct rand_struct *)NULL); - /* Now convert it to the salt form */ - get_salt_from_password(salt,buffer); - /* Finally get hash and bin password from salt */ - get_hash_and_password(salt,0,buffer,(unsigned char*) key); + SHA1_CONTEXT sha1_context; + uint8 hash_stage1[SHA1_HASH_SIZE]; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* stage 1: hash password */ + sha1_input(&sha1_context, (uint8 *) password, strlen(password)); + sha1_result(&sha1_context, hash_stage1); + /* stage 2: hash stage 1; note that hash_stage2 is stored in the database */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE); + sha1_result(&sha1_context, hash_stage2); + /* create crypt string as sha1(message, hash_stage2) */; + sha1_reset(&sha1_context); + sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + /* xor allows 'from' and 'to' overlap: lets take advantage of it */ + sha1_result(&sha1_context, (uint8 *) to); + my_crypt(to, (const uint8 *) to, hash_stage1, SCRAMBLE_LENGTH); + to[SHA1_HASH_SIZE]= '\0'; + return to + SHA1_HASH_SIZE; } /* - Scramble string with password - Used at pre 4.1 authentication phase. - + Check that scrambled message corresponds to the password; the function + is used by server to check that recieved reply is authentic. + This function does not check lengths of given strings: message must be + null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE + long (if not, something fishy is going on). SYNOPSIS - scramble() - to OUT Store scrambled message here - message IN Message to scramble - password IN Password to use while scrambling - old_ver IN Forse old version random number generator - - RETURN - End of scrambled string + check_scramble() + scramble IN clients' reply, presumably produced by scramble() + message IN original random string, previously sent to client + (presumably second argument of scramble()), must be + exactly SCRAMBLE_LENGTH long and NULL-terminated. + hash_stage2 IN hex2octet-decoded database entry + RETURN VALUE + 0 password is correct + !0 password is invalid */ -char *scramble(char *to,const char *message,const char *password, - my_bool old_ver) +my_bool +check_scramble(const char *scramble, const char *message, + const uint8 *hash_stage2) { - struct rand_struct rand_st; - ulong hash_pass[2],hash_message[2]; - char message_buffer[9]; /* Real message buffer */ - char *msg=message_buffer; - - /* We use special message buffer now as new server can provide longer hash */ - - memcpy(message_buffer,message,8); - message_buffer[8]=0; - - if (password && password[0]) - { - char *to_start=to; - hash_password(hash_pass,password); - hash_password(hash_message,message_buffer); - if (old_ver) - old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); - else - randominit(&rand_st,hash_pass[0] ^ hash_message[0], - hash_pass[1] ^ hash_message[1]); - while (*msg++) - *to++= (char) (floor(my_rnd(&rand_st)*31)+64); - if (!old_ver) - { /* Make it harder to break */ - char extra=(char) (floor(my_rnd(&rand_st)*31)); - while (to_start != to) - *(to_start++)^=extra; - } - } - *to=0; - return to; + SHA1_CONTEXT sha1_context; + uint8 buf[SHA1_HASH_SIZE]; + uint8 hash_stage2_reassured[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* create key to encrypt scramble */ + sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + sha1_result(&sha1_context, buf); + /* encrypt scramble */ + my_crypt((char *) buf, buf, (const uint8 *) scramble, SCRAMBLE_LENGTH); + /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, buf, SHA1_HASH_SIZE); + sha1_result(&sha1_context, hash_stage2_reassured); + return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } /* - Check scrambled message - Used for pre 4.1 password handling - + Convert scrambled password from asciiz hex string to binary form. SYNOPSIS - scramble() - scrambled IN Scrambled message to check - message IN Original message which was scramble - hash_pass IN Password which should be used for scrambling - old_ver IN Forse old version random number generator + get_salt_from_password() + res OUT buf to hold password. Must be at least SHA1_HASH_SIZE + bytes long. + password IN 4.1.1 version value of user.password +*/ + +void get_salt_from_password(uint8 *hash_stage2, const char *password) +{ + hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2); +} - RETURN - 0 Password correct - !0 Password invalid +/* + Convert scrambled password from binary form to asciiz hex string. + SYNOPSIS + make_password_from_salt() + to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes + salt IN password in salt format */ -my_bool check_scramble(const char *scrambled, const char *message, - ulong *hash_pass, my_bool old_ver) +void make_password_from_salt(char *to, const uint8 *hash_stage2) { - struct rand_struct rand_st; - ulong hash_message[2]; - char buff[16],*to,extra; /* Big enough for check */ - const char *pos; - char message_buffer[SCRAMBLE_LENGTH+1]; /* Copy of message */ - - /* We need to copy the message as this function can be called for MySQL 4.1 - scramble which is not zero ended and can have zeroes inside - We could just write zero to proper place in original message but - this would make it harder to understand code for next generations - */ - - memcpy(message_buffer,message,SCRAMBLE_LENGTH); /* Ignore the rest */ - message_buffer[SCRAMBLE_LENGTH]=0; - - /* Check if this exactly N bytes. Overwise this is something fishy */ - if (strlen(message_buffer)!=SCRAMBLE_LENGTH) - return 1; /* Wrong password */ - - hash_password(hash_message,message_buffer); - if (old_ver) - old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); - else - randominit(&rand_st,hash_pass[0] ^ hash_message[0], - hash_pass[1] ^ hash_message[1]); - to=buff; - for (pos=scrambled ; *pos ; pos++) - *to++=(char) (floor(my_rnd(&rand_st)*31)+64); - if (old_ver) - extra=0; - else - extra=(char) (floor(my_rnd(&rand_st)*31)); - to=buff; - while (*scrambled) - { - if (*scrambled++ != (char) (*to++ ^ extra)) - return 1; /* Wrong password */ - } - return 0; + *to++= PVERSION41_CHAR; + octet2hex(to, hash_stage2, SHA1_HASH_SIZE); } diff --git a/sql/slave.cc b/sql/slave.cc index c45c11f8bef..91376df5590 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1459,7 +1459,7 @@ int init_master_info(MASTER_INFO* mi, const char* master_info_fname, if (master_user) strmake(mi->user, master_user, sizeof(mi->user) - 1); if (master_password) - strmake(mi->password, master_password, HASH_PASSWORD_LENGTH); + strmake(mi->password, master_password, SCRAMBLED_PASSWORD_CHAR_LENGTH); mi->port = master_port; mi->connect_retry = master_connect_retry; } @@ -1483,8 +1483,8 @@ int init_master_info(MASTER_INFO* mi, const char* master_info_fname, master_host) || init_strvar_from_file(mi->user, sizeof(mi->user), &mi->file, master_user) || - init_strvar_from_file(mi->password, HASH_PASSWORD_LENGTH+1, &mi->file, - master_password) || + init_strvar_from_file(mi->password, SCRAMBLED_PASSWORD_CHAR_LENGTH+1, + &mi->file, master_password) || init_intvar_from_file(&port, &mi->file, master_port) || init_intvar_from_file(&connect_retry, &mi->file, master_connect_retry)) diff --git a/sql/slave.h b/sql/slave.h index 429456eb0bb..d3565565ded 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -292,7 +292,7 @@ typedef struct st_master_info /* the variables below are needed because we can change masters on the fly */ char host[HOSTNAME_LENGTH+1]; char user[USERNAME_LENGTH+1]; - char password[HASH_PASSWORD_LENGTH+1]; + char password[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; pthread_mutex_t data_lock,run_lock; pthread_cond_t data_cond,start_cond,stop_cond; THD *io_thd; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 1bdca7167e8..bbc6b74c3a9 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -68,12 +68,37 @@ static ulong get_sort(uint count,...); static void init_check_host(void); static ACL_USER *find_acl_user(const char *host, const char *user); static bool update_user_table(THD *thd, const char *host, const char *user, - const char *new_password); + const char *new_password, uint new_password_len); static void update_hostname(acl_host_and_ip *host, const char *hostname); static bool compare_hostname(const acl_host_and_ip *host,const char *hostname, const char *ip); /* + Convert scrambled password to binary form, according to scramble type, + Binary form is stored in user.salt. +*/ + +static +void +set_user_salt(ACL_USER *acl_user, const char *password, uint password_len) +{ + if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH) + { + get_salt_from_password(acl_user->salt, password); + acl_user->salt_len= SCRAMBLE_LENGTH; + } + else if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323 + || password_len == 8 && protocol_version == 9) + { + get_salt_from_password_323((ulong *) acl_user->salt, password); + acl_user->salt_len= password_len/2; + } + else + acl_user->salt_len= 0; +} + + +/* Read grant privileges from the privilege tables in the 'mysql' database. SYNOPSIS @@ -175,16 +200,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) if (table->field[2]->field_length == 8 && protocol_version == PROTOCOL_VERSION) { - sql_print_error( - "Old 'user' table. (Check README or the Reference manual). Continuing --old-protocol"); /* purecov: tested */ + sql_print_error("Old 'user' table. " + "(Check README or the Reference manual). " + "Continuing --old-protocol"); /* purecov: tested */ protocol_version=9; /* purecov: tested */ } DBUG_PRINT("info",("user table fields: %d, password length: %d", table->fields, table->field[2]->field_length)); - if (table->field[2]->field_length < 45 && !use_old_passwords) + if (table->field[2]->field_length < 41 && !use_old_passwords) { - sql_print_error("mysql.user table is not updated to new password format; Disabling new password usage until mysql_fix_privilege_tables is run"); + sql_print_error("mysql.user table is not updated to new password format; " + "Disabling new password usage until " + "mysql_fix_privilege_tables is run"); use_old_passwords= 1; } @@ -192,83 +220,88 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) while (!(read_record_info.read_record(&read_record_info))) { ACL_USER user; - uint length=0; - update_hostname(&user.host,get_field(&mem, table->field[0])); - user.user=get_field(&mem, table->field[1]); - user.password=get_field(&mem, table->field[2]); - if (user.password && (length=(uint) strlen(user.password)) == 8 && - protocol_version == PROTOCOL_VERSION) - { - sql_print_error( - "Found old style password for user '%s'. Ignoring user. (You may want to restart mysqld using --old-protocol)", - user.user ? user.user : ""); /* purecov: tested */ - } - else /* non empty and not short passwords */ - { - user.pversion=get_password_version(user.password); - /* Only passwords of specific lengths depending on version are allowed */ - if ( (!user.pversion && length % 8) || (user.pversion && length!=45 )) - { - sql_print_error( - "Found invalid password for user: '%s@%s'; Ignoring user", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); /* purecov: tested */ - continue; /* purecov: tested */ + update_hostname(&user.host, get_field(&mem, table->field[0])); + user.user= get_field(&mem, table->field[1]); + const char *password= get_field(&mem, table->field[2]); + uint password_len= password ? strlen(password) : 0; + set_user_salt(&user, password, password_len); + if (user.salt_len == 0 && password_len != 0) + { + switch (password_len) { + case 8: /* 3.20: to be removed */ + sql_print_error("Found old style password for user '%s'. " + "Ignoring user. (You may want to restart mysqld " + "using --old-protocol) ", + user.user ? user.user : ""); + break; + case 45: /* 4.1: to be removed */ + sql_print_error("Found 4.1 style password for user '%s'. " + "Ignoring user. " + "You should change password for this user.", + user.user ? user.user : ""); + break; + default: + sql_print_error("Found invalid password for user: '%s@%s'; " + "Ignoring user", user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); + break; } } - get_salt_from_password(user.salt,user.password); - user.access=get_access(table,3) & GLOBAL_ACLS; - user.sort=get_sort(2,user.host.hostname,user.user); - user.hostname_length= (user.host.hostname ? - (uint) strlen(user.host.hostname) : 0); - if (table->fields >= 31) /* Starting from 4.0.2 we have more fields */ - { - char *ssl_type=get_field(&mem, table->field[24]); - if (!ssl_type) - user.ssl_type=SSL_TYPE_NONE; - else if (!strcmp(ssl_type, "ANY")) - user.ssl_type=SSL_TYPE_ANY; - else if (!strcmp(ssl_type, "X509")) - user.ssl_type=SSL_TYPE_X509; - else /* !strcmp(ssl_type, "SPECIFIED") */ - user.ssl_type=SSL_TYPE_SPECIFIED; - - user.ssl_cipher= get_field(&mem, table->field[25]); - user.x509_issuer= get_field(&mem, table->field[26]); - user.x509_subject= get_field(&mem, table->field[27]); - - char *ptr = get_field(&mem, table->field[28]); - user.user_resource.questions=atoi(ptr); - ptr = get_field(&mem, table->field[29]); - user.user_resource.updates=atoi(ptr); - ptr = get_field(&mem, table->field[30]); - user.user_resource.connections=atoi(ptr); - if (user.user_resource.questions || user.user_resource.updates || - user.user_resource.connections) - mqh_used=1; - } - else + else // password is correct { - user.ssl_type=SSL_TYPE_NONE; - bzero(&(user.user_resource),sizeof(user.user_resource)); -#ifndef TO_BE_REMOVED - if (table->fields <= 13) - { // Without grant - if (user.access & CREATE_ACL) - user.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; + user.access= get_access(table,3) & GLOBAL_ACLS; + user.sort= get_sort(2,user.host.hostname,user.user); + user.hostname_length= (user.host.hostname ? + (uint) strlen(user.host.hostname) : 0); + if (table->fields >= 31) /* Starting from 4.0.2 we have more fields */ + { + char *ssl_type=get_field(&mem, table->field[24]); + if (!ssl_type) + user.ssl_type=SSL_TYPE_NONE; + else if (!strcmp(ssl_type, "ANY")) + user.ssl_type=SSL_TYPE_ANY; + else if (!strcmp(ssl_type, "X509")) + user.ssl_type=SSL_TYPE_X509; + else /* !strcmp(ssl_type, "SPECIFIED") */ + user.ssl_type=SSL_TYPE_SPECIFIED; + + user.ssl_cipher= get_field(&mem, table->field[25]); + user.x509_issuer= get_field(&mem, table->field[26]); + user.x509_subject= get_field(&mem, table->field[27]); + + char *ptr = get_field(&mem, table->field[28]); + user.user_resource.questions=atoi(ptr); + ptr = get_field(&mem, table->field[29]); + user.user_resource.updates=atoi(ptr); + ptr = get_field(&mem, table->field[30]); + user.user_resource.connections=atoi(ptr); + if (user.user_resource.questions || user.user_resource.updates || + user.user_resource.connections) + mqh_used=1; } - /* Convert old privileges */ - user.access|= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL; - if (user.access & FILE_ACL) - user.access|= REPL_CLIENT_ACL | REPL_SLAVE_ACL; - if (user.access & PROCESS_ACL) - user.access|= SUPER_ACL | EXECUTE_ACL; + else + { + user.ssl_type=SSL_TYPE_NONE; + bzero(&(user.user_resource),sizeof(user.user_resource)); +#ifndef TO_BE_REMOVED + if (table->fields <= 13) + { // Without grant + if (user.access & CREATE_ACL) + user.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; + } + /* Convert old privileges */ + user.access|= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL; + if (user.access & FILE_ACL) + user.access|= REPL_CLIENT_ACL | REPL_SLAVE_ACL; + if (user.access & PROCESS_ACL) + user.access|= SUPER_ACL | EXECUTE_ACL; #endif + } + VOID(push_dynamic(&acl_users,(gptr) &user)); + if (!user.host.hostname || user.host.hostname[0] == wild_many && + !user.host.hostname[1]) + allow_all_hosts=1; // Anyone can connect } - VOID(push_dynamic(&acl_users,(gptr) &user)); - if (!user.host.hostname || user.host.hostname[0] == wild_many && - !user.host.hostname[1]) - allow_all_hosts=1; // Anyone can connect } qsort((gptr) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements, sizeof(ACL_USER),(qsort_cmp) acl_compare); @@ -462,247 +495,203 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) /* - Prepare crypted scramble to be sent to the client -*/ - -void prepare_scramble(THD *thd, ACL_USER *acl_user,char* prepared_scramble) -{ - /* Binary password format to be used for generation*/ - char bin_password[SCRAMBLE41_LENGTH]; - /* Generate new long scramble for the thread */ - create_random_string(SCRAMBLE41_LENGTH,&thd->rand,thd->scramble); - thd->scramble[SCRAMBLE41_LENGTH]=0; - /* Get binary form, First 4 bytes of prepared scramble is salt */ - get_hash_and_password(acl_user->salt,acl_user->pversion,prepared_scramble, - (unsigned char*) bin_password); - /* Store "*" as identifier for old passwords */ - if (!acl_user->pversion) - prepared_scramble[0]='*'; - /* Finally encrypt password to get prepared scramble */ - password_crypt(thd->scramble, prepared_scramble+4, bin_password, - SCRAMBLE41_LENGTH); -} - - -/* - Get master privilges for user (priviliges for all tables). - Required before connecting to MySQL - - As we have 2 stage handshake now we cache user not to lookup - it second time. At the second stage we do not lookup user in case - we already know it; - + Seek ACL entry for a user, check password, SSL cypher, and if + everything is OK, update THD user data and USER_RESOURCES struct. + 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. + SYNOPSIS + thd INOUT thread handle. If all checks are OK, + thd->priv_user, thd->master_access are updated. + thd->host, thd->ip, thd->user are used for checks. + mqh OUT user resources; on success mqh is reset, else + unchanged + passwd IN scrambled & crypted password, recieved from client + (to check): thd->scramble or thd->scramble_323 is + used to decrypt passwd, so they must contain + original random string, + passwd_len IN length of passwd, must be one of 0, 8, + SCRAMBLE_LENGTH_323, SCRAMBLE_LENGTH + old_version IN if old (3.20) protocol is used + RETURN VALUE + 0 success: thread data and mqh are updated + 1 user not found or authentification failure + -1 user found, has short (3.23) salt, but passwd is in new (4.1.1) format. */ -ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, - const char *password,const char *message,char **priv_user, - char *priv_host, bool old_ver, USER_RESOURCES *mqh, - char *prepared_scramble, uint *cur_priv_version, - ACL_USER **cached_user) +int +acl_getroot(THD *thd, USER_RESOURCES *mqh, + const char *passwd, uint passwd_len, bool old_version) { - ulong user_access=NO_ACCESS; - *priv_user= (char*) user; - bool password_correct= 0; - int stage= (*cached_user != NULL); /* NULL passed as first stage */ - ACL_USER *acl_user= NULL; DBUG_ENTER("acl_getroot"); - bzero(mqh,sizeof(USER_RESOURCES)); - if (!initialized) - { - // If no data allow anything - DBUG_RETURN((ulong) ~NO_ACCESS); /* purecov: tested */ + if (!initialized) /* if no data allow anything */ + { + DBUG_RETURN(1); } + + int res= 1; + VOID(pthread_mutex_lock(&acl_cache->lock)); /* - Get possible access from user_list. This is or'ed to others not - fully specified - - If we have cached user use it, in other case look it up. + 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 */ - if (stage && (*cur_priv_version == priv_version)) - acl_user= *cached_user; - else + ACL_USER *acl_user= 0; + for (uint i=0 ; i < acl_users.elements ; i++) { - for (uint i=0 ; i < acl_users.elements ; i++) + ACL_USER *user_i = dynamic_element(&acl_users,i,ACL_USER*); + if (!user_i->user || !strcmp(thd->user, user_i->user)) { - ACL_USER *acl_user_search=dynamic_element(&acl_users,i,ACL_USER*); - if (!acl_user_search->user || !strcmp(user,acl_user_search->user)) + if (compare_hostname(&user_i->host, thd->host, thd->ip)) { - if (compare_hostname(&acl_user_search->host,host,ip)) + /* check password: it should be empty or valid */ + if (passwd_len == user_i->salt_len) { - /* Found mathing user */ - acl_user= acl_user_search; - /* Store it as a cache */ - *cached_user= acl_user; - *cur_priv_version= priv_version; - break; + if (user_i->salt_len == 0 || + user_i->salt_len == SCRAMBLE_LENGTH && + check_scramble(passwd, thd->scramble, user_i->salt) == 0 || + check_scramble_323(passwd, thd->scramble_323, + (ulong *) user_i->salt, old_version) == 0) + { + acl_user= user_i; + res= 0; + } } + else if (passwd_len == SCRAMBLE_LENGTH && + user_i->salt_len == SCRAMBLE_LENGTH_323) + res= -1; + /* linear search complete: */ + break; } } } - - /* Now we have acl_user found and may start our checks */ + /* + This was moved to separate tree because of heavy HAVE_OPENSSL case. + If acl_user is not null, res is 0. + */ if (acl_user) { - /* Password should present for both or absend for both */ - if (!acl_user->password && !*password) - password_correct=1; - else if (!acl_user->password || !*password) - { - *cached_user= 0; // Impossible to connect - } - else - { - /* New version password is checked differently */ - if (acl_user->pversion) - { - if (stage) /* We check password only on the second stage */ - { - if (!validate_password(password,message,acl_user->salt)) - password_correct=1; - } - else /* First stage - just prepare scramble */ - prepare_scramble(thd,acl_user,prepared_scramble); - } - /* Old way to check password */ - else - { - /* Checking the scramble at any stage. First - old clients */ - if (!check_scramble(password,message,acl_user->salt, - (my_bool) old_ver)) - password_correct=1; - else if (!stage) /* Here if password incorrect */ - { - /* At the first stage - prepare scramble */ - prepare_scramble(thd,acl_user,prepared_scramble); - } - } - } - } - - /* If user not found password_correct will also be zero */ - if (!password_correct) - goto unlock_and_exit; - - /* OK. User found and password checked continue validation */ - + /* OK. User found and password checked continue validation */ #ifdef HAVE_OPENSSL - { - Vio *vio=thd->net.vio; - /* - 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 to connect */ - user_access=acl_user->access; - break; - case SSL_TYPE_ANY: /* Any kind of SSL is good enough */ - 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_peer_certificate(vio->ssl_)) - user_access=acl_user->access; - break; - case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ + { + ulong user_access= NO_ACCESS; + Vio *vio=thd->net.vio; /* - 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. + 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 */ - if (vio_type(vio) != VIO_TYPE_SSL) - break; - if (acl_user->ssl_cipher) - { - DBUG_PRINT("info",("comparing ciphers: '%s' and '%s'", - acl_user->ssl_cipher,SSL_get_cipher(vio->ssl_))); - if (!strcmp(acl_user->ssl_cipher,SSL_get_cipher(vio->ssl_))) - user_access=acl_user->access; - else - { - if (global_system_variables.log_warnings) - sql_print_error("X509 ciphers mismatch: should be '%s' but is '%s'", - acl_user->ssl_cipher, - SSL_get_cipher(vio->ssl_)); - user_access=NO_ACCESS; - break; - } - } - /* Prepare certificate (if exists) */ - DBUG_PRINT("info",("checkpoint 1")); - X509* cert=SSL_get_peer_certificate(vio->ssl_); - DBUG_PRINT("info",("checkpoint 2")); - /* If X509 issuer is speified, 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_error("X509 issuer mismatch: should be '%s' but is '%s'", - acl_user->x509_issuer, ptr); - user_access=NO_ACCESS; - free(ptr); - 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_error("X509 subject mismatch: '%s' vs '%s'", - acl_user->x509_subject, ptr); - user_access=NO_ACCESS; - } - else - user_access=acl_user->access; - free(ptr); + switch (acl_user->ssl_type) { + case SSL_TYPE_NOT_SPECIFIED: // Impossible + case SSL_TYPE_NONE: /* SSL is not required to connect */ + user_access=acl_user->access; + break; + case SSL_TYPE_ANY: /* Any kind of SSL is good enough */ + 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_peer_certificate(vio->ssl_)) + user_access=acl_user->access; + 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) + break; + if (acl_user->ssl_cipher) + { + DBUG_PRINT("info",("comparing ciphers: '%s' and '%s'", + acl_user->ssl_cipher,SSL_get_cipher(vio->ssl_))); + if (!strcmp(acl_user->ssl_cipher,SSL_get_cipher(vio->ssl_))) + user_access=acl_user->access; + else + { + if (global_system_variables.log_warnings) + sql_print_error("X509 ciphers mismatch: should be '%s'" + "but is '%s'", acl_user->ssl_cipher, + SSL_get_cipher(vio->ssl_)); + user_access=NO_ACCESS; + break; + } + } + /* Prepare certificate (if exists) */ + DBUG_PRINT("info",("checkpoint 1")); + X509* cert=SSL_get_peer_certificate(vio->ssl_); + DBUG_PRINT("info",("checkpoint 2")); + /* If X509 issuer is speified, 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_error("X509 issuer mismatch: should be '%s' " + "but is '%s'", acl_user->x509_issuer, ptr); + user_access=NO_ACCESS; + free(ptr); + 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_error("X509 subject mismatch: '%s' vs '%s'", + acl_user->x509_subject, ptr); + user_access=NO_ACCESS; + } + else + user_access=acl_user->access; + free(ptr); + } + break; } - break; + /* end of SSL stuff: assign result */ + thd->master_access= user_access; } - } #else /* HAVE_OPENSSL */ - user_access=acl_user->access; + thd->master_access= acl_user->access; #endif /* HAVE_OPENSSL */ - *mqh=acl_user->user_resource; - if (!acl_user->user) - *priv_user=(char*) ""; // Change to anonymous user /* purecov: inspected */ - - if (acl_user->host.hostname) - strmake(priv_host, acl_user->host.hostname, MAX_HOSTNAME); - else - *priv_host= 0; + thd->priv_user= acl_user->user ? thd->user : (char *) ""; + *mqh= acl_user->user_resource; -unlock_and_exit: + if (acl_user->host.hostname) + strmake(thd->priv_host, acl_user->host.hostname, MAX_HOSTNAME); + else + *thd->priv_host= 0; + } VOID(pthread_mutex_unlock(&acl_cache->lock)); - DBUG_RETURN(user_access); + DBUG_RETURN(res); } @@ -713,8 +702,9 @@ static byte* check_get_key(ACL_USER *buff,uint *length, return (byte*) buff->host.hostname; } + static void acl_update_user(const char *user, const char *host, - const char *password, + const char *password, uint password_len, enum SSL_type ssl_type, const char *ssl_cipher, const char *x509_issuer, @@ -750,20 +740,9 @@ static void acl_update_user(const char *user, const char *host, acl_user->x509_subject= (x509_subject ? strdup_root(&mem,x509_subject) : 0); } - if (password) - { - if (!password[0]) /* If password is empty set it to null */ - { - acl_user->password=0; - acl_user->pversion=0; // just initialize - } - else - { - acl_user->password=(char*) ""; // Just point at something - get_salt_from_password(acl_user->salt,password); - acl_user->pversion=get_password_version(acl_user->password); - } - } + + set_user_salt(acl_user, password, password_len); + /* search complete: */ break; } } @@ -772,7 +751,7 @@ static void acl_update_user(const char *user, const char *host, static void acl_insert_user(const char *user, const char *host, - const char *password, + const char *password, uint password_len, enum SSL_type ssl_type, const char *ssl_cipher, const char *x509_issuer, @@ -783,7 +762,6 @@ static void acl_insert_user(const char *user, const char *host, ACL_USER acl_user; acl_user.user=strdup_root(&mem,user); update_hostname(&acl_user.host,strdup_root(&mem,host)); - acl_user.password=0; acl_user.access=privileges; acl_user.user_resource = *mqh; acl_user.sort=get_sort(2,acl_user.host.hostname,acl_user.user); @@ -793,12 +771,8 @@ static void acl_insert_user(const char *user, const char *host, acl_user.ssl_cipher= ssl_cipher ? strdup_root(&mem,ssl_cipher) : 0; acl_user.x509_issuer= x509_issuer ? strdup_root(&mem,x509_issuer) : 0; acl_user.x509_subject=x509_subject ? strdup_root(&mem,x509_subject) : 0; - if (password) - { - acl_user.password=(char*) ""; // Just point at something - get_salt_from_password(acl_user.salt,password); - acl_user.pversion=get_password_version(password); - } + + set_user_salt(&acl_user, password, password_len); VOID(push_dynamic(&acl_users,(gptr) &acl_user)); if (!acl_user.host.hostname || acl_user.host.hostname[0] == wild_many @@ -1135,7 +1109,6 @@ bool check_change_password(THD *thd, const char *host, const char *user) bool change_password(THD *thd, const char *host, const char *user, char *new_password) { - uint length=0; DBUG_ENTER("change_password"); DBUG_PRINT("enter",("host: '%s' user: '%s' new_password: '%s'", host,user,new_password)); @@ -1144,37 +1117,27 @@ bool change_password(THD *thd, const char *host, const char *user, if (check_change_password(thd, host, user)) DBUG_RETURN(1); - /* - password should always be 0,16 or 45 chars; - Simple hack to avoid cracking - */ - length=(uint) strlen(new_password); - if (length != 45) - new_password[length & 16]=0; - VOID(pthread_mutex_lock(&acl_cache->lock)); ACL_USER *acl_user; - if (!(acl_user= find_acl_user(host,user))) + if (!(acl_user= find_acl_user(host, user))) { - send_error(thd, ER_PASSWORD_NO_MATCH); VOID(pthread_mutex_unlock(&acl_cache->lock)); + send_error(thd, ER_PASSWORD_NO_MATCH); DBUG_RETURN(1); } + /* update loaded acl entry: */ + uint new_password_len= new_password ? strlen(new_password) : 0; + set_user_salt(acl_user, new_password, new_password_len); + if (update_user_table(thd, acl_user->host.hostname ? acl_user->host.hostname : "", acl_user->user ? acl_user->user : "", - new_password)) + new_password, new_password_len)) { VOID(pthread_mutex_unlock(&acl_cache->lock)); /* purecov: deadcode */ send_error(thd,0); /* purecov: deadcode */ DBUG_RETURN(1); /* purecov: deadcode */ } - get_salt_from_password(acl_user->salt,new_password); - acl_user->pversion=get_password_version(new_password); - if (!new_password[0]) - acl_user->password=0; - else - acl_user->password=(char*) ""; // Point at something acl_cache->clear(1); // Clear locked hostname cache VOID(pthread_mutex_unlock(&acl_cache->lock)); @@ -1210,7 +1173,7 @@ find_acl_user(const char *host, const char *user) if (!acl_user->user && !user[0] || acl_user->user && !strcmp(user,acl_user->user)) { - if (compare_hostname(&(acl_user->host),host,host)) + if (compare_hostname(&acl_user->host,host,host)) { DBUG_RETURN(acl_user); } @@ -1280,7 +1243,7 @@ static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, ****************************************************************************/ static bool update_user_table(THD *thd, const char *host, const char *user, - const char *new_password) + const char *new_password, uint new_password_len) { TABLE_LIST tables; TABLE *table; @@ -1304,7 +1267,7 @@ static bool update_user_table(THD *thd, const char *host, const char *user, DBUG_RETURN(1); /* purecov: deadcode */ } store_record(table,record[1]); - table->field[2]->store(new_password,(uint) strlen(new_password), &my_charset_latin1); + table->field[2]->store(new_password, new_password_len, &my_charset_latin1); if ((error=table->file->update_row(table->record[1],table->record[0]))) { table->file->print_error(error,MYF(0)); /* purecov: deadcode */ @@ -1352,24 +1315,24 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, { int error = -1; bool old_row_exists=0; - char *password,empty_string[1]; + char empty_string[]= { '\0' }; + char *password= empty_string; + uint password_len= 0; char what= (revoke_grant) ? 'N' : 'Y'; DBUG_ENTER("replace_user_table"); safe_mutex_assert_owner(&acl_cache->lock); - password=empty_string; - empty_string[0]=0; - if (combo.password.str && combo.password.str[0]) { - if ((combo.password.length != HASH_PASSWORD_LENGTH) - && combo.password.length != HASH_OLD_PASSWORD_LENGTH) + if (combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH && + combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) { my_printf_error(ER_PASSWORD_NO_MATCH, "Password hash should be a %d-digit hexadecimal number", - MYF(0),HASH_PASSWORD_LENGTH); + MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); DBUG_RETURN(-1); } + password_len= combo.password.length; password=combo.password.str; } @@ -1394,17 +1357,20 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, goto end; } old_row_exists = 0; - restore_record(table,default_values); // cp empty row from default_values - table->field[0]->store(combo.host.str,combo.host.length, &my_charset_latin1); - table->field[1]->store(combo.user.str,combo.user.length, &my_charset_latin1); - table->field[2]->store(password,(uint) strlen(password), &my_charset_latin1); + restore_record(table,default_values); // cp empty row from default_values + table->field[0]->store(combo.host.str,combo.host.length, + &my_charset_latin1); + table->field[1]->store(combo.user.str,combo.user.length, + &my_charset_latin1); + table->field[2]->store(password, password_len, + &my_charset_latin1); } else { old_row_exists = 1; store_record(table,record[1]); // Save copy for update if (combo.password.str) // If password given - table->field[2]->store(password,(uint) strlen(password), &my_charset_latin1); + table->field[2]->store(password, password_len, &my_charset_latin1); } /* Update table columns with new privileges */ @@ -1501,10 +1467,8 @@ end: if (!error) { acl_cache->clear(1); // Clear privilege cache - if (!combo.password.str) - password=0; // No password given on command if (old_row_exists) - acl_update_user(combo.user.str,combo.host.str,password, + acl_update_user(combo.user.str, combo.host.str, password, password_len, thd->lex.ssl_type, thd->lex.ssl_cipher, thd->lex.x509_issuer, @@ -1512,7 +1476,7 @@ end: &thd->lex.mqh, rights); else - acl_insert_user(combo.user.str,combo.host.str,password, + acl_insert_user(combo.user.str, combo.host.str, password, password_len, thd->lex.ssl_type, thd->lex.ssl_cipher, thd->lex.x509_issuer, @@ -2915,12 +2879,15 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) global.append ("'@'",3); global.append(lex_user->host.str,lex_user->host.length); global.append ('\''); - if (acl_user->password) + if (acl_user->salt_len) { - char passd_buff[HASH_PASSWORD_LENGTH+1]; - make_password_from_salt(passd_buff,acl_user->salt,acl_user->pversion); + char passwd_buff[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; + if (acl_user->salt_len == SCRAMBLE_LENGTH) + make_password_from_salt(passwd_buff, acl_user->salt); + else + make_password_from_salt_323(passwd_buff, (ulong *) acl_user->salt); global.append(" IDENTIFIED BY PASSWORD '",25); - global.append(passd_buff); + global.append(passwd_buff); global.append('\''); } /* "show grants" SSL related stuff */ diff --git a/sql/sql_acl.h b/sql/sql_acl.h index e6c6771253c..3370797820a 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -111,9 +111,9 @@ public: acl_host_and_ip host; uint hostname_length; USER_RESOURCES user_resource; - char *user,*password; - ulong salt[6]; // New password has longer length - uint8 pversion; // password version + 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; }; @@ -135,11 +135,8 @@ void acl_reload(THD *thd); void acl_free(bool end=0); ulong acl_get(const char *host, const char *ip, const char *bin_ip, const char *user, const char *db); -ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, - const char *password,const char *scramble, - char **priv_user, char *priv_host, - bool old_ver, USER_RESOURCES *max,char* prepared_scramble, - uint *cur_priv_version, ACL_USER **cached_user); +int acl_getroot(THD *thd, USER_RESOURCES *mqh, + const char *passwd, uint passwd_len, bool old_ver); bool acl_check_host(const char *host, const char *ip); bool check_change_password(THD *thd, const char *host, const char *user); bool change_password(THD *thd, const char *host, const char *user, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index c233ffd422a..ebb3e819ddc 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -139,6 +139,7 @@ THD::THD():user_time(0), is_fatal_error(0), set_query_id=1; db_access=NO_ACCESS; version=refresh_version; // For boot + *scramble= *scramble_323= '\0'; init(); /* Initialize sub structures */ diff --git a/sql/sql_class.h b/sql/sql_class.h index ccfe2555518..d962cc8086e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -529,10 +529,16 @@ public: enum_tx_isolation session_tx_isolation; /* for user variables replication*/ DYNAMIC_ARRAY user_var_events; - // extend scramble to handle new auth - char scramble[SCRAMBLE41_LENGTH+1]; - // old scramble is needed to handle old clients - char old_scramble[SCRAMBLE_LENGTH+1]; + + /* scramble - random string sent to client on handshake */ + char scramble[SCRAMBLE_LENGTH+1]; + /* + The same as scramble but for old password checking routines. It always + contains first N bytes of scramble. + See check_connection() at sql_parse.cc for authentification details. + */ + char scramble_323[SCRAMBLE_LENGTH_323+1]; + uint8 query_cache_type; // type of query cache processing bool slave_thread; bool set_query_id,locked,count_cuted_fields,some_tables_deleted; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 5070466007e..384ec2bd4dd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -178,152 +178,119 @@ end: /* - Check if user is ok - + Check if user exist and password supplied is correct. SYNOPSIS check_user() - thd Thread handle - command Command for connection (for log) - user Name of user trying to connect - passwd Scrambled password sent from client - db Database to connect to - check_count If set to 1, don't allow too many connection - simple_connect If 1 then client is of old type and we should connect - using the old method (no challange) - do_send_error Set to 1 if we should send error to user - prepared_scramble Buffer to store hash password of new connection - had_password Set to 1 if the user gave a password - cur_priv_version Check flag to know if someone flushed the privileges - since last code - hint_user Pointer used by acl_getroot() to remmeber user for - next call - - RETURN - 0 ok - thd->user, thd->master_access, thd->priv_user, thd->db and - thd->db_access are updated - 1 Access denied; Error sent to client - -1 If do_send_error == 1: Failed connect, error sent to client - If do_send_error == 0: Prepare for stage of connect + thd INOUT thread handle, thd->{host,user,ip} are used + command IN originator of the check: now check_user is called + during connect and change user procedures; used for + logging. + passwd IN scrambled password recieved from client + passwd_len IN length of scrambled password + db IN database name to connect to, may be NULL + check_count IN dont know exactly + Note, that host, user and passwd may point to communication buffer. + Current implementation does not depened on that, but future changes + should be done with this in mind. + RETURN VALUE + 0 OK; thd->user, thd->master_access, thd->priv_user, thd->db and + thd->db_access are updated; OK is sent to client; + 1 access denied or internal error; error is sent to client + Note, that this return semantics differs from check_connection, + which returns -1 if message was already sent. + -1 acl entry for this user contains old scramble, but passwd contains + new one, error is not sent to client */ -static int check_user(THD *thd,enum_server_command command, const char *user, - const char *passwd, const char *db, bool check_count, - bool simple_connect, bool do_send_error, - char *prepared_scramble, bool had_password, - uint *cur_priv_version, ACL_USER** hint_user) +static int check_user(THD *thd, enum enum_server_command command, + const char *passwd, uint passwd_len, const char *db, + bool check_count) { - thd->db=0; - thd->db_length=0; - USER_RESOURCES ur; - char tmp_passwd[SCRAMBLE41_LENGTH]; DBUG_ENTER("check_user"); - - /* - Move password to temporary buffer as it may be stored in communication - buffer - */ - strmake(tmp_passwd, passwd, sizeof(tmp_passwd)); - passwd= tmp_passwd; // Use local copy - - /* We shall avoid dupplicate user allocations here */ - if (!thd->user && !(thd->user = my_strdup(user, MYF(0)))) - { - send_error(thd,ER_OUT_OF_RESOURCES); - DBUG_RETURN(1); - } - thd->master_access=acl_getroot(thd, thd->host, thd->ip, thd->user, - passwd, thd->scramble, - &thd->priv_user, thd->priv_host, - (protocol_version == 9 || - !(thd->client_capabilities & - CLIENT_LONG_PASSWORD)), - &ur,prepared_scramble, - cur_priv_version,hint_user); - - DBUG_PRINT("info", - ("Capabilities: %d packet_length: %ld Host: '%s' Login user: '%s' Priv_user: '%s' Using password: %s Access: %u db: '%s'", - thd->client_capabilities, thd->max_client_packet_length, - thd->host_or_ip, thd->user, thd->priv_user, - had_password ? "yes": "no", - thd->master_access, thd->db ? thd->db : "*none*")); /* - In case we're going to retry we should not send error message at this - point + Why this is set here? - probably to reset current DB to 'no database + selected' in case of 'change user' failure. */ - if (thd->master_access & NO_ACCESS) + thd->db= 0; + thd->db_length= 0; + + USER_RESOURCES ur; + int res= acl_getroot(thd, &ur, passwd, passwd_len, + protocol_version == 9 || + !(thd->client_capabilities & CLIENT_LONG_PASSWORD)); + if (res == 0 && !(thd->master_access & NO_ACCESS)) // authentification is OK { - if (do_send_error || !had_password || !*hint_user) + DBUG_PRINT("info", + ("Capabilities: %d packet_length: %ld Host: '%s' " + "Login user: '%s' Priv_user: '%s' Using password: %s " + "Access: %u db: '%s'", + thd->client_capabilities, thd->max_client_packet_length, + thd->host_or_ip, thd->user, thd->priv_user, + passwd_len ? "yes": "no", + thd->master_access, thd->db ? thd->db : "*none*")); + + if (check_count) { - DBUG_PRINT("info",("Access denied")); - /* - Old client should get nicer error message if password version is - not supported - */ - if (simple_connect && *hint_user && (*hint_user)->pversion) - { - net_printf(thd, ER_NOT_SUPPORTED_AUTH_MODE); - mysql_log.write(thd,COM_CONNECT,ER(ER_NOT_SUPPORTED_AUTH_MODE)); + VOID(pthread_mutex_lock(&LOCK_thread_count)); + bool count_ok= thread_count < max_connections + delayed_insert_threads || + thd->master_access & SUPER_ACL; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + if (!count_ok) + { // too many connections + send_error(thd, ER_CON_COUNT_ERROR); + DBUG_RETURN(1); } - else - { - net_printf(thd, ER_ACCESS_DENIED_ERROR, - thd->user, - thd->host_or_ip, - had_password ? ER(ER_YES) : ER(ER_NO)); - mysql_log.write(thd,COM_CONNECT,ER(ER_ACCESS_DENIED_ERROR), - thd->user, - thd->host_or_ip, - had_password ? ER(ER_YES) : ER(ER_NO)); - } - DBUG_RETURN(1); // Error already given } - DBUG_PRINT("info",("Prepare for second part of handshake")); - DBUG_RETURN(-1); // no report error in special handshake - } - if (check_count) - { - VOID(pthread_mutex_lock(&LOCK_thread_count)); - bool tmp=(thread_count - delayed_insert_threads >= max_connections && - !(thd->master_access & SUPER_ACL)); - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - if (tmp) - { // Too many connections - send_error(thd, ER_CON_COUNT_ERROR); + /* Why logging is performed before all checks've passed? */ + mysql_log.write(thd,command, + (thd->priv_user == thd->user ? + (char*) "%s@%s on %s" : + (char*) "%s@%s as anonymous on %s"), + thd->user, thd->host_or_ip, + db ? db : (char*) ""); + + /* Why is it set here? */ + thd->db_access=0; + + /* Don't allow user to connect if he has done too many queries */ + if ((ur.questions || ur.updates || ur.connections) && + get_or_create_user_conn(thd,thd->user,thd->host_or_ip,&ur)) + DBUG_RETURN(1); + if (thd->user_connect && thd->user_connect->user_resources.connections && + check_for_max_user_connections(thd, thd->user_connect)) DBUG_RETURN(1); + + /* Change database if necessary: OK or FAIL is sent in mysql_change_db */ + if (db && db[0]) + { + if (mysql_change_db(thd, db)) + { + if (thd->user_connect) + decrease_user_connections(thd->user_connect); + DBUG_RETURN(1); + } } + else + send_ok(thd); + thd->password= test(passwd_len); // remember for error messages + /* Ready to handle queries */ } - mysql_log.write(thd,command, - (thd->priv_user == thd->user ? - (char*) "%s@%s on %s" : - (char*) "%s@%s as anonymous on %s"), - user, - thd->host_or_ip, - db ? db : (char*) ""); - thd->db_access=0; - /* Don't allow user to connect if he has done too many queries */ - if ((ur.questions || ur.updates || ur.connections) && - get_or_create_user_conn(thd,user,thd->host_or_ip,&ur)) - DBUG_RETURN(1); - if (thd->user_connect && thd->user_connect->user_resources.connections && - check_for_max_user_connections(thd, thd->user_connect)) - DBUG_RETURN(1); - - if (db && db[0]) + else if (res != -1) // authentication failure { - int error= test(mysql_change_db(thd,db)); - if (error && thd->user_connect) - decrease_user_connections(thd->user_connect); - DBUG_RETURN(error); - } - send_ok(thd); // Ready to handle questions - thd->password= test(passwd[0]); // Remember for error messages - DBUG_RETURN(0); // ok + net_printf(thd, ER_ACCESS_DENIED_ERROR, + thd->user, + thd->host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + mysql_log.write(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), + thd->user, + thd->host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + } + DBUG_RETURN(res); } - /* Check for maximum allowable user connections, if the mysqld server is started with corresponding variable that is greater then 0. @@ -525,48 +492,93 @@ static void reset_mqh(THD *thd, LEX_USER *lu, bool get_them= 0) /* - Check connnectionn and get priviliges - + Perform check for scrambled password, re-request scrambled password + from client if necessary. See also help for check_user. SYNOPSIS - check_connections - thd Thread handle + authenticate() + RETURN VALUE + 0 success, OK sent to client + -1 error, sent to client + > 0 error, not sent to client +*/ + +static +int +authenticate(THD *thd, enum enum_server_command command, + const char *passwd, uint passwd_len, const char *db, + bool check_count) +{ + if (passwd_len != 0 && + passwd_len != SCRAMBLE_LENGTH && + passwd_len != SCRAMBLE_LENGTH_323) + return 1; + int res= check_user(thd, COM_CONNECT, passwd, passwd_len, db, check_count); + if (res < 0) + { + /* + 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. + */ + char buff[NAME_LEN + 1]; + /* save db because network buffer is to hold new packet */ + if (db) + { + strmake(buff, db, NAME_LEN); + db= buff; + } + NET *net= &thd->net; + if (my_net_write(net, thd->scramble_323, SCRAMBLE_LENGTH_323 + 1) || + net_flush(net) || + my_net_read(net) != SCRAMBLE_LENGTH_323 + 1) // We have to read very + { // specific packet size + inc_host_errors(&thd->remote.sin_addr); + return ER_HANDSHAKE_ERROR; + } + /* Final attempt to check the user based on reply */ + /* So as passwd is short, errcode is always sent to user and res >= 0 */ + res= check_user(thd, COM_CONNECT, (char *) net->read_pos, + SCRAMBLE_LENGTH_323, db, check_count); + } + return res > 0 ? -1 : 0; +} + +/* + Perform handshake, authorize client and update thd ACL variables. + SYNOPSIS + check_connection() + thd INOUT thread handle RETURN - 0 ok - -1 Error, which is sent to user - > 0 Error code (not sent to user) + 0 success, OK is sent to user + -1 error, which is sent to user + > 0 error code (not sent to user) */ #ifndef EMBEDDED_LIBRARY static int -check_connections(THD *thd) +check_connection(THD *thd) { - int res; - uint connect_errors=0; - uint cur_priv_version; - bool using_password; + uint connect_errors= 0; NET *net= &thd->net; - char *end, *user, *passwd, *db; - char prepared_scramble[SCRAMBLE41_LENGTH+4]; /* Buffer for scramble&hash */ - ACL_USER* cached_user=NULL; /* Initialise to NULL for first stage */ - DBUG_PRINT("info",("New connection received on %s", - vio_description(net->vio))); - - /* Remove warning from valgrind. TODO: Fix it in password.c */ - bzero((char*) &prepared_scramble[0], sizeof(prepared_scramble)); + + DBUG_PRINT("info", + ("New connection received on %s", vio_description(net->vio))); + if (!thd->host) // If TCP/IP connection { char ip[30]; if (vio_peer_addr(net->vio, ip, &thd->peer_port)) return (ER_BAD_HOST_ERROR); - if (!(thd->ip = my_strdup(ip,MYF(0)))) + if (!(thd->ip= my_strdup(ip,MYF(0)))) return (ER_OUT_OF_RESOURCES); - thd->host_or_ip=thd->ip; + thd->host_or_ip= thd->ip; #if !defined(HAVE_SYS_UN_H) || defined(HAVE_mit_thread) /* Fast local hostname resolve for Win32 */ if (!strcmp(thd->ip,"127.0.0.1")) - thd->host=(char*) localhost; + thd->host= (char *) localhost; else #endif { @@ -595,15 +607,16 @@ check_connections(THD *thd) DBUG_PRINT("info",("Host: %s",thd->host)); thd->host_or_ip= thd->host; thd->ip= 0; - bzero((char*) &thd->remote,sizeof(struct sockaddr)); + bzero((char*) &thd->remote, sizeof(struct sockaddr)); } /* Ensure that wrong hostnames doesn't cause buffer overflows */ vio_keepalive(net->vio, TRUE); - ulong pkt_len=0; + ulong pkt_len= 0; + char *end; { /* buff[] needs to big enough to hold the server_version variable */ - char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH+64]; + char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH + 64]; ulong client_flags = (CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION); @@ -617,19 +630,36 @@ check_connections(THD *thd) client_flags |= CLIENT_SSL; /* Wow, SSL is avalaible! */ #endif /* HAVE_OPENSSL */ - end=strnmov(buff,server_version,SERVER_VERSION_LENGTH)+1; - int4store((uchar*) end,thd->thread_id); - end+=4; - memcpy(end,thd->scramble,SCRAMBLE_LENGTH+1); - end+=SCRAMBLE_LENGTH +1; - int2store(end,client_flags); - end[2]=(char) default_charset_info->number; - int2store(end+3,thd->server_status); - bzero(end+5,13); - end+=18; + 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); + strmake(thd->scramble_323, thd->scramble, SCRAMBLE_LENGTH_323); - // At this point we write connection message and read reply - if (net_write_command(net,(uchar) protocol_version, "", 0, buff, + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of scramble is placed here, and second + part at the end of packet. + */ + end= strmake(end, thd->scramble_323, SCRAMBLE_LENGTH_323) + 1; + + int2store(end, client_flags); + /* 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, "", 0, buff, (uint) (end-buff)) || (pkt_len= my_net_read(net)) == packet_error || pkt_len < MIN_HANDSHAKE_SIZE) @@ -702,7 +732,7 @@ check_connections(THD *thd) return(ER_HANDSHAKE_ERROR); } DBUG_PRINT("info", ("Reading user information over SSL layer")); - if ((pkt_len=my_net_read(net)) == packet_error || + 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)", @@ -719,16 +749,7 @@ check_connections(THD *thd) return(ER_HANDSHAKE_ERROR); } - user= end; - passwd= strend(user)+1; - db=0; - using_password= test(passwd[0]); - if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB) - db=strend(passwd)+1; - - /* We can get only old hash at this point */ - if (using_password && strlen(passwd) != SCRAMBLE_LENGTH) - return ER_HANDSHAKE_ERROR; + /* why has it been put here? */ if (thd->client_capabilities & CLIENT_INTERACTIVE) thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; @@ -737,60 +758,19 @@ check_connections(THD *thd) net->return_status= &thd->server_status; net->read_timeout=(uint) thd->variables.net_read_timeout; - /* Simple connect only for old clients. New clients always use secure auth */ - bool simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)); - - /* Check user permissions. If password failure we'll get scramble back */ - if ((res=check_user(thd, COM_CONNECT, user, passwd, db, 1, simple_connect, - simple_connect, prepared_scramble, using_password, - &cur_priv_version, - &cached_user)) < 0) - { - /* Store current used and database as they are erased with next packet */ - char tmp_user[USERNAME_LENGTH+1]; - char tmp_db[NAME_LEN+1]; - - /* If the client is old we just have to return error */ - if (simple_connect) - return -1; + char *user= end; + char *passwd= strend(user)+1; + uint passwd_len= strlen(passwd); - DBUG_PRINT("info",("password challenge")); + char *db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? + passwd+passwd_len+1 : 0; - tmp_user[0]= tmp_db[0]= 0; - if (user) - strmake(tmp_user,user,USERNAME_LENGTH); - if (db) - strmake(tmp_db,db,NAME_LEN); - - /* Write hash and encrypted scramble to client */ - if (my_net_write(net,prepared_scramble,SCRAMBLE41_LENGTH+4) || - net_flush(net)) - { - inc_host_errors(&thd->remote.sin_addr); - return ER_HANDSHAKE_ERROR; - } - /* Reading packet back */ - if ((pkt_len= my_net_read(net)) == packet_error) - { - inc_host_errors(&thd->remote.sin_addr); - return ER_HANDSHAKE_ERROR; - } - /* We have to get very specific packet size */ - if (pkt_len != SCRAMBLE41_LENGTH) - { - inc_host_errors(&thd->remote.sin_addr); - return ER_HANDSHAKE_ERROR; - } - /* Final attempt to check the user based on reply */ - if (check_user(thd,COM_CONNECT, tmp_user, (char*)net->read_pos, - tmp_db, 1, 0, 1, prepared_scramble, using_password, - &cur_priv_version, - &cached_user)) - return -1; - } - else if (res) - return -1; // Error sent from check_user() - return 0; + if (thd->user) + x_free(thd->user); + thd->user= my_strdup(user, MYF(0)); + if (!thd->user) + return(ER_OUT_OF_RESOURCES); + return authenticate(thd, COM_CONNECT, passwd, passwd_len, db, true); } @@ -847,7 +827,7 @@ pthread_handler_decl(handle_one_connection,arg) NET *net= &thd->net; thd->thread_stack= (char*) &thd; - if ((error=check_connections(thd))) + if ((error=check_connection(thd))) { // Wrong permissions if (error > 0) net_printf(thd,error,thd->host_or_ip); @@ -1152,116 +1132,60 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_CHANGE_USER: { thd->change_user(); - thd->clear_error(); // If errors from rollback + thd->clear_error(); // if errors from rollback - statistic_increment(com_other,&LOCK_status); - char *user= (char*) packet; + statistic_increment(com_other, &LOCK_status); + char *user= (char*) packet; char *passwd= strend(user)+1; - char *db= strend(passwd)+1; + uint passwd_len= strlen(passwd); + char *db= passwd + passwd_len + 1; - /* Save user and privileges */ - uint save_master_access=thd->master_access; - uint save_db_access= thd->db_access; - uint save_db_length= thd->db_length; - char *save_user= thd->user; - thd->user=NULL; /* Needed for check_user to allocate new user */ - char *save_priv_user= thd->priv_user; - char *save_db= thd->db; - USER_CONN *save_uc= thd->user_connect; - bool simple_connect; - bool using_password; - char prepared_scramble[SCRAMBLE41_LENGTH+4];/* Buffer for scramble,hash */ - char tmp_user[USERNAME_LENGTH+1]; - char tmp_db[NAME_LEN+1]; - ACL_USER* cached_user ; /* Cached user */ - uint cur_priv_version; /* Cached grant version */ - int res; - ulong pkt_len= 0; /* Length of reply packet */ - - bzero((char*) prepared_scramble, sizeof(prepared_scramble)); /* Small check for incomming packet */ - if ((uint) ((uchar*) db - net->read_pos) > packet_length) - goto restore_user_err; - - /* Now we shall basically perform authentication again */ - - /* We can get only old hash at this point */ - if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH) - goto restore_user_err; - - cached_user= NULL; - - /* Simple connect only for old clients. New clients always use sec. auth*/ - simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)); - - /* Store information if we used password. passwd will be dammaged */ - using_password=test(passwd[0]); - - if (simple_connect) /* Restore scramble for old clients */ - memcpy(thd->scramble,thd->old_scramble,9); + { + send_error(thd, ER_UNKNOWN_COM_ERROR); + break; + } - /* - Check user permissions. If password failure we'll get scramble back - Do not retry if we already have sent error (result>0) - */ - if ((res=check_user(thd,COM_CHANGE_USER, user, passwd, db, 0, - simple_connect, simple_connect, prepared_scramble, - using_password, &cur_priv_version, &cached_user)) < 0) + /* Save user and privileges */ + uint save_master_access= thd->master_access; + uint save_db_access= thd->db_access; + uint save_db_length= thd->db_length; + char *save_user= thd->user; + char *save_priv_user= thd->priv_user; + char *save_db= thd->db; + USER_CONN *save_uc= thd->user_connect; + thd->user= my_strdup(user, MYF(0)); + if (!thd->user) { - /* If the client is old we just have to have auth failure */ - if (simple_connect) - goto restore_user; /* Error is already reported */ - - /* Store current used and database as they are erased with next packet */ - tmp_user[0]= tmp_db[0]= 0; - if (user) - strmake(tmp_user,user,USERNAME_LENGTH); - if (db) - strmake(tmp_db,db,NAME_LEN); - - /* Write hash and encrypted scramble to client */ - if (my_net_write(net,prepared_scramble,SCRAMBLE41_LENGTH+4) || - net_flush(net)) - goto restore_user_err; - - /* Reading packet back */ - if ((pkt_len=my_net_read(net)) == packet_error) - goto restore_user_err; - - /* We have to get very specific packet size */ - if (pkt_len != SCRAMBLE41_LENGTH) - goto restore_user; - - /* Final attempt to check the user based on reply */ - if (check_user(thd,COM_CHANGE_USER, tmp_user, (char*) net->read_pos, - tmp_db, 0, 0, 1, prepared_scramble, using_password, - &cur_priv_version, &cached_user)) - goto restore_user; + thd->user= save_user; + send_error(thd, ER_OUT_OF_RESOURCES); + break; } - else if (res) - goto restore_user; - - /* Finally we've authenticated new user */ - if (max_connections && save_uc) - decrease_user_connections(save_uc); - x_free((gptr) save_db); - x_free((gptr) save_user); - thd->password=using_password; - break; - /* Bad luck we shall restore old user */ -restore_user_err: - send_error(thd, ER_UNKNOWN_COM_ERROR); + int res= authenticate(thd, COM_CHANGE_USER, passwd, passwd_len, db, false); -restore_user: - x_free(thd->user); - thd->master_access=save_master_access; - thd->db_access=save_db_access; - thd->db=save_db; - thd->db_length=save_db_length; - thd->user=save_user; - thd->priv_user=save_priv_user; + if (res) + { + /* authentification failure, we shall restore old user */ + if (res > 0) + send_error(thd, ER_UNKNOWN_COM_ERROR); + x_free(thd->user); + thd->user= save_user; + thd->priv_user= save_priv_user; + thd->master_access= save_master_access; + thd->db_access= save_db_access; + thd->db= save_db; + thd->db_length= save_db_length; + } + else + { + /* we've authenticated new user */ + if (max_connections && save_uc) + decrease_user_connections(save_uc); + x_free((gptr) save_db); + x_free((gptr) save_user); + } break; } #endif /* EMBEDDED_LIBRARY */ @@ -3158,7 +3082,7 @@ error: Check grants for commands which work only with one table and all other tables belong to subselects. - SYNOPSYS + SYNOPSIS single_table_command_access() thd - Thread handler privilege - asked privelage diff --git a/sql/sql_repl.h b/sql/sql_repl.h index e3d600b9798..b53551845bc 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -7,7 +7,7 @@ typedef struct st_slave_info uint32 rpl_recovery_rank, master_id; char host[HOSTNAME_LENGTH+1]; char user[USERNAME_LENGTH+1]; - char password[HASH_PASSWORD_LENGTH+1]; + char password[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; uint16 port; THD* thd; } SLAVE_INFO; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 1d605abe8a3..c8c9eb97a6a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2515,9 +2515,10 @@ simple_expr: | NOW_SYM '(' expr ')' { $$= new Item_func_now($3); Lex->safe_to_cache_query=0;} | PASSWORD '(' expr ')' - { $$= new Item_func_password($3); } - | PASSWORD '(' expr ',' expr ')' - { $$= new Item_func_password($3,$5); } + { + $$= use_old_passwords ? (Item *) new Item_func_old_password($3) : + (Item *) new Item_func_password($3); + } | POINT_SYM '(' expr ',' expr ')' { $$= new Item_func_point($3,$5); } | POINTFROMTEXT '(' expr ')' @@ -4604,13 +4605,22 @@ text_or_password: { if (!$3.length) $$=$3.str; - else + else if (use_old_passwords) { - char *buff=(char*) YYTHD->alloc(HASH_PASSWORD_LENGTH+1); - make_scrambled_password(buff,$3.str,use_old_passwords, - &YYTHD->rand); + char *buff= (char *) + YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); + if (buff) + make_scrambled_password_323(buff, $3.str); $$=buff; } + else + { + char *buff= (char *) + YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); + if (buff) + make_scrambled_password(buff, $3.str); + $$=buff; + } } ; @@ -4918,14 +4928,24 @@ grant_user: $$=$1; $1->password=$4; if ($4.length) { - char *buff=(char*) YYTHD->alloc(HASH_PASSWORD_LENGTH+1); - if (buff) - { - make_scrambled_password(buff,$4.str,use_old_passwords, - &YYTHD->rand); - $1->password.str=buff; - $1->password.length=HASH_PASSWORD_LENGTH; - } + if (use_old_passwords) + { + char *buff= + (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); + if (buff) + make_scrambled_password_323(buff, $4.str); + $1->password.str= buff; + $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + } + else + { + char *buff= + (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); + if (buff) + make_scrambled_password(buff, $4.str); + $1->password.str= buff; + $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + } } } | user IDENTIFIED_SYM BY PASSWORD TEXT_STRING |