diff options
author | unknown <peter@mysql.com> | 2002-11-24 17:07:53 +0300 |
---|---|---|
committer | unknown <peter@mysql.com> | 2002-11-24 17:07:53 +0300 |
commit | 8c8b97fdf4756a699346ae1688136624d76713bc (patch) | |
tree | db7cb82e2166048d043214a331a5ad63bc7d0621 | |
parent | 85bbdcf016dadf31335ad9ee98c3c8acfc167e2f (diff) | |
download | mariadb-git-8c8b97fdf4756a699346ae1688136624d76713bc.tar.gz |
SCRUM: Main change for Secure connection handling. Still needs some more coding. Commit
done for merge with newer version of code.
client/mysqladmin.c:
Support for new password format
include/mysql.h:
Increase buffer as new scramble is larger
include/mysql_com.h:
Add new prototypes for new auth handling
libmysql/libmysql.c:
New handshake handling code
mysql-test/install_test_db.sh:
Extend password length to handle new passwords
mysql-test/t/rpl000017-slave.sh:
Adjust test to work with longer password
scripts/mysql_fix_privilege_tables.sh:
Increase password length at priv table convertion
scripts/mysql_install_db.sh:
Longer passwords
sql/item_strfunc.cc:
New password generation function needs seed for password generation
sql/mini_client.cc:
Change MiniClient to handle new auth in replication
sql/mysqld.cc:
Remove unneeded flag (was added earlier for same change)
sql/password.c:
A lot of changes to handle new authentication and doccumentation
sql/sql_acl.cc:
Password handling function adjustment
sql/sql_acl.h:
Change prototype
sql/sql_class.h:
Extend scramble length
sql/sql_parse.cc:
Handling server side of new authentication
sql/sql_yacc.yy:
Adjustment for new prototypes
-rw-r--r-- | client/mysqladmin.c | 22 | ||||
-rw-r--r-- | include/mysql.h | 2 | ||||
-rw-r--r-- | include/mysql_com.h | 9 | ||||
-rw-r--r-- | libmysql/libmysql.c | 92 | ||||
-rw-r--r-- | mysql-test/install_test_db.sh | 2 | ||||
-rwxr-xr-x | mysql-test/t/rpl000017-slave.sh | 2 | ||||
-rw-r--r-- | scripts/mysql_fix_privilege_tables.sh | 1 | ||||
-rw-r--r-- | scripts/mysql_install_db.sh | 2 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 4 | ||||
-rw-r--r-- | sql/mini_client.cc | 90 | ||||
-rw-r--r-- | sql/mysqld.cc | 1 | ||||
-rw-r--r-- | sql/password.c | 539 | ||||
-rw-r--r-- | sql/sql_acl.cc | 280 | ||||
-rw-r--r-- | sql/sql_acl.h | 2 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_parse.cc | 138 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 4 |
17 files changed, 948 insertions, 244 deletions
diff --git a/client/mysqladmin.c b/client/mysqladmin.c index e9572e0ad8d..cf6368cc707 100644 --- a/client/mysqladmin.c +++ b/client/mysqladmin.c @@ -87,7 +87,7 @@ enum commands { ADMIN_FLUSH_HOSTS, ADMIN_FLUSH_TABLES, ADMIN_PASSWORD, ADMIN_PING, ADMIN_EXTENDED_STATUS, ADMIN_FLUSH_STATUS, ADMIN_FLUSH_PRIVILEGES, ADMIN_START_SLAVE, ADMIN_STOP_SLAVE, - ADMIN_FLUSH_THREADS + ADMIN_FLUSH_THREADS, ADMIN_OLD_PASSWORD }; static const char *command_names[]= { "create", "drop", "shutdown", @@ -97,7 +97,7 @@ static const char *command_names[]= { "flush-hosts", "flush-tables", "password", "ping", "extended-status", "flush-status", "flush-privileges", "start-slave", "stop-slave", - "flush-threads", + "flush-threads","old-password", NullS }; @@ -419,7 +419,8 @@ static my_bool sql_connect(MYSQL *mysql, uint wait) static int execute_commands(MYSQL *mysql,int argc, char **argv) { const char *status; - + struct rand_struct rand_st; + for (; argc > 0 ; argv++,argc--) { switch (find_type(argv[0],&command_typelib,2)) { @@ -721,17 +722,23 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) } break; } + case ADMIN_OLD_PASSWORD: case ADMIN_PASSWORD: { - char buff[128],crypted_pw[33]; - + char buff[128],crypted_pw[64]; + time_t start_time; + /* Do initialization the same way as we do in mysqld */ + start_time=time((time_t*) 0); + randominit(&rand_st,(ulong) start_time,(ulong) start_time/2); + if (argc < 2) { my_printf_error(0,"Too few arguments to change password",MYF(ME_BELL)); return 1; } if (argv[1][0]) - make_scrambled_password(crypted_pw,argv[1],0); /* New passwords only */ + make_scrambled_password(crypted_pw,argv[1],(find_type(argv[0],&command_typelib,2) + ==ADMIN_OLD_PASSWORD),&rand_st); else crypted_pw[0]=0; /* No password */ sprintf(buff,"set password='%s',sql_log_off=0",crypted_pw); @@ -836,7 +843,8 @@ static void usage(void) kill id,id,... Kill mysql threads"); #if MYSQL_VERSION_ID >= 32200 puts("\ - password new-password Change old password to new-password"); + password new-password Change old password to new-password, MySQL 4.1 hashing.\n\ + old-password new-password Change old password to new-password in old format.\n"); #endif puts("\ ping Check if mysqld is alive\n\ diff --git a/include/mysql.h b/include/mysql.h index 710f5006724..1bdff5d4824 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -181,7 +181,7 @@ 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[9]; + char scramble_buff[21]; /* New protocol requires longer scramble*/ /* 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 397cfe0f387..4737c55dba5 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -280,10 +280,17 @@ extern unsigned long net_buffer_length; void randominit(struct rand_struct *,unsigned long seed1, unsigned long seed2); double rnd(struct rand_struct *); -void make_scrambled_password(char *to,const char *password,my_bool force_old_scramble); +void make_scrambled_password(char *to,const char *password,my_bool force_old_scramble,struct rand_struct *rand_st); uint get_password_length(my_bool force_old_scramble); uint8 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, ulong* 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(ulong* salt, uint8 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, uint8 password_version); char *scramble(char *to,const char *message,const char *password, my_bool old_ver); diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index f164582889e..db23caac5c7 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -67,7 +67,7 @@ ulong net_write_timeout= NET_WRITE_TIMEOUT; #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG \ | CLIENT_LOCAL_FILES | CLIENT_TRANSACTIONS \ - | CLIENT_PROTOCOL_41) + | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION) #ifdef __WIN__ @@ -1585,6 +1585,7 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, { char buff[NAME_LEN+USERNAME_LENGTH+100],charset_name_buff[16]; char *end,*host_info,*charset_name; + char password_hash[20]; /* Used for tmp storage of stage1 hash */ my_socket sock; uint32 ip_addr; struct sockaddr_in sock_addr; @@ -1789,7 +1790,7 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, if ((pkt_length=net_safe_read(mysql)) == packet_error) goto error; - /* Check if version of protocoll matches current one */ + /* Check if version of protocol matches current one */ mysql->protocol_version= net->read_pos[0]; DBUG_DUMP("packet",(char*) net->read_pos,10); @@ -1855,7 +1856,7 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, } goto error; } - + /* Save connection information */ if (!user) user=""; if (!passwd) passwd=""; @@ -1962,32 +1963,105 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user, DBUG_PRINT("info",("Server version = '%s' capabilites: %ld status: %d client_flag: %d", mysql->server_version,mysql->server_capabilities, mysql->server_status, client_flag)); - int3store(buff+2,max_allowed_packet); if (user && user[0]) strmake(buff+5,user,32); /* Max user name */ else read_user_name((char*) buff+5); + /* We have to handle different version of handshake here */ #ifdef _CUSTOMCONFIG_ #include "_cust_libmysql.h"; #endif DBUG_PRINT("info",("user: %s",buff+5)); - end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd, - (my_bool) (mysql->protocol_version == 9)); + /* + 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) + { + if (passwd[0]) + { + /* Use something for not empty password not to match it against empty one */ + end=scramble(strend(buff+5)+1, mysql->scramble_buff,"~MySQL#!", + (my_bool) (mysql->protocol_version == 9)); + } + else /* For empty password*/ + { + end=strend(buff+5)+1; + *end=0; /* Store zero length scramble */ + } + } + /* Real scramble is sent only for servers. This is to be blocked by option */ + else + end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd, + (my_bool) (mysql->protocol_version == 9)); + + /* Add database if needed */ if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) { - end=strmake(end+1,db,NAME_LEN); + end=strmake(end+1,db,NAME_LEN); mysql->db=my_strdup(db,MYF(MY_WME)); db=0; } + /* Write authentication package */ if (my_net_write(net,buff,(ulong) (end-buff)) || net_flush(net)) { - net->last_errno= CR_SERVER_LOST; + net->last_errno= CR_SERVER_LOST; strmov(net->last_error,ER(net->last_errno)); goto error; } - if (net_safe_read(mysql) == packet_error) + + /* We shall only query sever 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 basically always happen with new server unless empty password */ + if (pkt_length==24) /* We have new hash back */ + { + /* Old passwords will have zero 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,20); + /* Finally hash complete password using hash we got from server */ + password_hash_stage2(password_hash,net->read_pos); + /* Decypt and store scramble 4 = hash for stage2 */ + password_crypt(net->read_pos+4,mysql->scramble_buff,password_hash,20); + mysql->scramble_buff[20]=0; + /* Encode scramble with password. Recycle buffer */ + password_crypt(mysql->scramble_buff,buff,buff,20); + } + else + { + /* Create password to decode scramble */ + create_key_from_old_password(passwd,password_hash); + /* Decypt and store scramble 4 = hash for stage2 */ + password_crypt(net->read_pos+4,mysql->scramble_buff,password_hash,20); + mysql->scramble_buff[20]=0; + /* Finally scramble decoded scramble with password */ + scramble(buff, mysql->scramble_buff, passwd, + (my_bool) (mysql->protocol_version == 9)); + } + /* Write second package of authentication */ + if (my_net_write(net,buff,20) || net_flush(net)) + { + net->last_errno= CR_SERVER_LOST; + 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; + } + } + + /* End of authentication part of handshake */ + if (client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress=1; if (db && mysql_select_db(mysql,db)) diff --git a/mysql-test/install_test_db.sh b/mysql-test/install_test_db.sh index 8f90301d2d8..990c763efc8 100644 --- a/mysql-test/install_test_db.sh +++ b/mysql-test/install_test_db.sh @@ -123,7 +123,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(16) binary DEFAULT '' NOT NULL," + c_u="$c_u Password char(45) 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/mysql-test/t/rpl000017-slave.sh b/mysql-test/t/rpl000017-slave.sh index fa21fe7acc3..4dbbaec31ce 100755 --- a/mysql-test/t/rpl000017-slave.sh +++ b/mysql-test/t/rpl000017-slave.sh @@ -5,7 +5,7 @@ master-bin.000001 4 127.0.0.1 replicate -aaaaaaaaaaaaaaabthispartofthepasswordisnotused +aaaaaaaaaaaaaaab $MASTER_MYPORT 1 0 diff --git a/scripts/mysql_fix_privilege_tables.sh b/scripts/mysql_fix_privilege_tables.sh index 247e3399b8b..a65aca3c1fc 100644 --- a/scripts/mysql_fix_privilege_tables.sh +++ b/scripts/mysql_fix_privilege_tables.sh @@ -170,6 +170,7 @@ fi @bindir@/mysql -f --user=root --password="$root_password" --host="$host" mysql <<END_OF_DATA alter table user +change password password char(45) not null, add max_questions int(11) NOT NULL AFTER x509_subject, add max_updates int(11) unsigned NOT NULL AFTER max_questions, add max_connections int(11) unsigned NOT NULL AFTER max_updates; diff --git a/scripts/mysql_install_db.sh b/scripts/mysql_install_db.sh index 4822e816a12..5e139dc652b 100644 --- a/scripts/mysql_install_db.sh +++ b/scripts/mysql_install_db.sh @@ -213,7 +213,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(16) binary DEFAULT '' NOT NULL," + c_u="$c_u Password char(45) 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/sql/item_strfunc.cc b/sql/item_strfunc.cc index 189c0d22c15..e7d16387a00 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1279,7 +1279,7 @@ String *Item_func_password::val_str(String *str) return 0; if (res->length() == 0) return &empty_string; - make_scrambled_password(tmp_value,res->c_ptr(),opt_old_passwords); + make_scrambled_password(tmp_value,res->c_ptr(),opt_old_passwords,¤t_thd->rand); str->set(tmp_value,get_password_length(opt_old_passwords),res->charset()); return str; } @@ -1291,7 +1291,7 @@ String *Item_func_old_password::val_str(String *str) return 0; if (res->length() == 0) return &empty_string; - make_scrambled_password(tmp_value,res->c_ptr(),1); + make_scrambled_password(tmp_value,res->c_ptr(),1,¤t_thd->rand); str->set(tmp_value,16,res->charset()); return str; } diff --git a/sql/mini_client.cc b/sql/mini_client.cc index aa84a52eb0b..52725f2acc0 100644 --- a/sql/mini_client.cc +++ b/sql/mini_client.cc @@ -87,7 +87,9 @@ static MYSQL_DATA *mc_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, -#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES) +#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | \ + CLIENT_LOCAL_FILES | CLIENT_SECURE_CONNECTION) + #if defined(MSDOS) || defined(__WIN__) #define perror(A) @@ -488,6 +490,7 @@ mc_mysql_connect(MYSQL *mysql,const char *host, const char *user, uint net_read_timeout) { char buff[NAME_LEN+USERNAME_LENGTH+100],*end,*host_info; + char password_hash[20]; my_socket sock; ulong ip_addr; struct sockaddr_in sock_addr; @@ -510,7 +513,6 @@ mc_mysql_connect(MYSQL *mysql,const char *host, const char *user, user ? user : "(Null)", net_read_timeout, (uint) slave_net_timeout)); - net->vio = 0; /* If something goes wrong */ mysql->charset=default_charset_info; /* Set character set */ if (!port) @@ -799,22 +801,96 @@ mc_mysql_connect(MYSQL *mysql,const char *host, const char *user, } DBUG_PRINT("info",("user: %s",buff+5)); - end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd, - (my_bool) (mysql->protocol_version == 9)); - if (db) + + /* + 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) + { + if (passwd[0]) + { + /* Use something for not empty password not to match it against empty one */ + end=scramble(strend(buff+5)+1, mysql->scramble_buff,"~MySQL#!", + (my_bool) (mysql->protocol_version == 9)); + } + else /* For empty password*/ + { + end=strend(buff+5)+1; + *end=0; /* Store zero length scramble */ + } + } + /* Real scramble is sent only for old servers. This is to be blocked by option */ + else + end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd, + (my_bool) (mysql->protocol_version == 9)); + + /* Add database if needed */ + if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) { end=strmake(end+1,db,NAME_LEN); mysql->db=my_strdup(db,MYF(MY_WME)); db=0; } + /* Write authentication package */ if (my_net_write(net,buff,(ulong) (end-buff)) || net_flush(net)) { net->last_errno= CR_SERVER_LOST; - strmov(net->last_error,ER(net->last_errno)); + strmov(net->last_error,ER(net->last_errno)); goto error; } - if (mc_net_safe_read(mysql) == packet_error) + + /* We shall only query sever if it expect us to do so */ + + if ( (pkt_length=mc_net_safe_read(mysql)) == packet_error) goto error; + + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* This should basically always happen with new server unless empty password */ + if (pkt_length==24) /* We have new hash back */ + { + /* Old passwords will have zero 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,20); + /* Finally hash complete password using hash we got from server */ + password_hash_stage2(password_hash,(char*)net->read_pos); + /* Decypt and store scramble 4 = hash for stage2 */ + password_crypt((char*)net->read_pos+4,mysql->scramble_buff,password_hash,20); + mysql->scramble_buff[20]=0; + /* Encode scramble with password. Recycle buffer */ + password_crypt(mysql->scramble_buff,buff,buff,20); + } + else + { + /* Create password to decode scramble */ + create_key_from_old_password(passwd,password_hash); + /* Decypt and store scramble 4 = hash for stage2 */ + password_crypt((char*)net->read_pos+4,mysql->scramble_buff,password_hash,20); + mysql->scramble_buff[20]=0; + /* Finally scramble decoded scramble with password */ + scramble(buff, mysql->scramble_buff, passwd, + (my_bool) (mysql->protocol_version == 9)); + } + /* Write second package of authentication */ + if (my_net_write(net,buff,20) || net_flush(net)) + { + net->last_errno= CR_SERVER_LOST; + strmov(net->last_error,ER(net->last_errno)); + goto error; + } + /* Read What server thinks about out new auth message report */ + if (mc_net_safe_read(mysql) == packet_error) + goto error; + } + } + + /* End of authentication part of handshake */ + if (client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress=1; DBUG_PRINT("exit",("Mysql handler: %lx",mysql)); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 685e713f39c..1112d3e459a 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -317,7 +317,6 @@ uint volatile thread_count=0, thread_running=0, kill_cached_threads=0, ulong thd_startup_options=(OPTION_UPDATE_LOG | OPTION_AUTO_IS_NULL | OPTION_BIN_LOG | OPTION_QUOTE_SHOW_CREATE ); uint protocol_version=PROTOCOL_VERSION; -uint connection_auth_flag=0; /* Supported authentication mode */ struct system_variables global_system_variables; struct system_variables max_system_variables; ulong keybuff_size,table_cache_size, diff --git a/sql/password.c b/sql/password.c index 0d60b381e1b..13f8d593da6 100644 --- a/sql/password.c +++ b/sql/password.c @@ -32,6 +32,24 @@ Example: update user set password=PASSWORD("hello") where user="test" This saves a hashed number as a string in the password field. + + + 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. + + 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. + + *****************************************************************************/ #include <my_global.h> @@ -45,10 +63,23 @@ /* Character to use as version identifier for version 4.1 */ #define PVERSION41_CHAR '*' +/* Scramble length for new password version */ +#define SCRAMBLE41_LENGTH 20 - - +/* + 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.# */ #ifdef HAVE_purify @@ -60,6 +91,19 @@ void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) rand_st->seed2=seed2%rand_st->max_value; } + +/* + Old (MySQL 3.20) random generation structure initialization + + 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.# */ rand_st->max_value= 0x01FFFFFFL; @@ -68,6 +112,18 @@ static void old_randominit(struct rand_struct *rand_st,ulong seed1) rand_st->seed1=seed1 ; rand_st->seed2=seed1/2; } + +/* + Generate Random number + + SYNOPSIS + rnd() + rand_st INOUT Structure used for number generation + + RETURN + Generated pseudo random number +*/ + double rnd(struct rand_struct *rand_st) { rand_st->seed1=(rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; @@ -75,6 +131,75 @@ double rnd(struct rand_struct *rand_st) return (((double) rand_st->seed1)/rand_st->max_value_dbl); } + +/* + Generate String of printable random characters of requested length + String will not be zero terminated. + + 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. */ + while (target<end) + { + *target=rnd(rand_st)*94+33; + target++; + } +} + + +/* + 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 +*/ + +inline 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; + from++; + to++; + 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 +*/ + void hash_password(ulong *result, const char *password) { register ulong nr=1345345333L, add=7, nr2=0x12345671L; @@ -94,15 +219,75 @@ void hash_password(ulong *result, const char *password) } +/* + Stage one password hashing. + Used in MySQL 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 +*/ +inline void password_hash_stage1(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,(int8*)&password[0],1); + } + sha1_result(&context,(uint8*)to); +} -void make_scrambled_password(char *to,const char *password,my_bool force_old_scramble) +/* + Stage two password hashing. + Used in MySQL 4.1 password handling + + 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 + + RETURN + none +*/ + +inline void password_hash_stage2(char *to,const char *salt) +{ + SHA1_CONTEXT context; + sha1_reset(&context); + sha1_input(&context,(uint8*)salt,4); + sha1_input(&context,to,SHA1_HASH_SIZE); + sha1_result(&context,(uint8*)to); +} + + +/* + Create password to be stored in user database from raw string + Handles both MySQL 4.1 and Pre-MySQL 4.1 passwords + + 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 +*/ + +void make_scrambled_password(char *to,const char *password,my_bool force_old_scramble,struct rand_struct *rand_st) { - ulong hash_res[2]; /* Used for pre 4.1 password hashing */ - static uint salt=0; /* Salt for 4.1 version password */ - unsigned char* slt=(unsigned char*)&salt; - SHA1_CONTEXT context; + 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 */ { @@ -112,27 +297,16 @@ void make_scrambled_password(char *to,const char *password,my_bool force_old_scr else /* New password 4.1 password scrambling */ { to[0]=PVERSION41_CHAR; /* New passwords have version prefix */ - /* We do not need too strong salt generation so this should be enough */ - salt+=getpid()+time(NULL)+0x01010101; + /* Random returns number from 0 to 1 so this would be good salt generation.*/ + salt=rnd(rand_st)*65535+1; /* Use only 2 first bytes from it */ - sprintf(&(to[1]),"%02x%02x",slt[0],slt[1]); - sha1_reset(&context); - /* Use Salt for Hash */ - sha1_input(&context,(uint8*)&salt,2); - - for (; *password ; password++) - { - if (*password == ' ' || *password == '\t') - continue;/* skip space in password */ - sha1_input(&context,(int8*)&password[0],1); - } - sha1_result(&context,digest); - /* Hash one more time */ - sha1_reset(&context); - sha1_input(&context,digest,SHA1_HASH_SIZE); - sha1_result(&context,digest); + sprintf(to+1,"%04x",salt); + /* First hasing is done without salt */ + password_hash_stage1(digest,password); + /* Second stage is done with salt */ + password_hash_stage2(digest,(char*)to+1), /* Print resulting hash into the password*/ - sprintf(&(to[5]), + 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], @@ -140,14 +314,111 @@ void make_scrambled_password(char *to,const char *password,my_bool force_old_scr } } -uint get_password_length(my_bool force_old_scramble) + +/* + Convert password from binary string form to salt form + Used for MySQL 4.1 password handling + + 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 +*/ + +void get_salt_from_bin_password(ulong *res,unsigned char *password,ulong salt) +{ + unsigned char* password_end=password+SCRAMBLE41_LENGTH; + *res=salt; + res++; + bzero(res,5*sizeof(res[0])); + + /* Process password of known length*/ + while (password<password_end) + { + ulong val=0; + uint i; + for (i=0 ; i < 4 ; i++) + val=(val << 8)+(*password++); + *res++=val; + } +} + + +/* + Validate password for MySQL 4.1 password handling. + + 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 +*/ + +my_bool validate_password(const char* password, const char* message, ulong* salt) +{ + char buffer[SCRAMBLE41_LENGTH]; /* Used for password validation */ + char tmpsalt[8]; /* Temporary value to convert salt to string form */ + int i; + ulong salt_candidate[6]; /* Computed candidate salt */ + + /* 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,buffer,salt[0]); + + /* Now we shall get exactly the same password as we have stored for user */ + for(i=1;i<6;i++) + if (salt[i]!=salt_candidate[i]) return 1; + /* Or password correct*/ + return 0; +} + + +/* + Get length of password string which is stored in mysql.user table + + SYNOPSIS + get_password_length() + force_old_scramble IN If we wish to use pre 4.1 scramble format + + RETURN + password length >0 +*/ + +inline uint get_password_length(my_bool force_old_scramble) { if (force_old_scramble) return 16; else return SHA1_HASH_SIZE*2+4+1; } -uint8 get_password_version(const char* password) + +/* + 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 +*/ + +inline uint8 get_password_version(const char* password) { if (password==NULL) return 0; if (password[0]==PVERSION41_CHAR) return PVERSION41_CHAR; @@ -155,6 +426,19 @@ uint8 get_password_version(const char* password) } +/* + Get integer value of Hex character + + SYNOPSIS + char_val() + X IN Character to find value for + + RETURN + Appropriate integer value +*/ + + + inline uint char_val(char X) { return (uint) (X >= '0' && X <= '9' ? X-'0' : @@ -162,30 +446,45 @@ inline uint char_val(char X) X-'a'+10); } + /* -** This code detects new version password by leading char. -** Old password has to be divisible by 8 length -** do not forget to increase array length if you need longer passwords -** THIS FUNCTION DOES NOT HAVE ANY LENGTH CHECK + Get Binary salt from password as in mysql.user format + + 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) { bzero(res,6*sizeof(res[0])); - if (password) // zero salt corresponds to empty password + if (password) /* zero salt corresponds to empty password */ { - if (password[0]==PVERSION41_CHAR) // if new password + if (password[0]==PVERSION41_CHAR) /* if new password */ { uint val=0; uint i; - password++; // skip version identifier. + password++; /* skip version identifier */ - //get hashing salt from password and store in in the start of array + /*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 + /* 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; @@ -198,40 +497,168 @@ void get_salt_from_password(ulong *res,const char *password) return; } + +/* + Get string version as stored in mysql.user from salt form + + 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 +*/ + void make_password_from_salt(char *to, ulong *hash_res,uint8 password_version) { - if (!password_version) // Handling of old passwords. + 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",(uint)hash_res[0],hash_res[1], + 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 + else /* Just use empty password if we can't handle it. This should not happen */ to[0]='\0'; } /* - * Genererate a new message based on message and password - * The same thing is done in client and server and the results are checked. - */ + Convert password in salted form to binary string password and hash-salt + For old password this involes one more hashing + + 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 +*/ +void get_hash_and_password(ulong* salt, uint8 pversion, char* hash, unsigned char* bin_password) +{ + int t; + ulong* salt_end; + ulong val; + SHA1_CONTEXT context; + unsigned char* bp; /* Binary password loop pointer */ + + if (pversion) /* New password version assumed */ + { + salt_end=salt+6; + sprintf(hash,"%04x",(unsigned short)salt[0]); + salt++; /* position to the second element */ + while (salt<salt_end) /* Iterate over these elements*/ + { + val=*salt; + for(t=3;t>=0;t--) + { + bin_password[t]=val%256; + val>>=8; /* Scroll 8 bits to get next part*/ + } + bin_password+=4; /* Get to next 4 chars*/ + salt++; + } + } + else + { + /* Use zero starting hash as an indication of old password */ + hash[0]=0; + salt_end=salt+2; + bp=bin_password; + /* 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]=val%256; + + 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); + } +} + + +/* + Create key from old password to decode scramble + Used in 4.1 authentication with passwords stored old way + + SYNOPSIS + create_key_from_old_password() + passwd IN Password used for key generation + key OUT Created 20 bytes key + + RETURN + None +*/ + + +void create_key_from_old_password(const char* passwd, char* key) +{ + char buffer[20]; /* 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); +} + + +/* + Scramble string with password + Used at pre 4.1 authentication phase. + + 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 +*/ + char *scramble(char *to,const char *message,const char *password, my_bool old_ver) { 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); + 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 (*message++) + while (*msg++) *to++= (char) (floor(rnd(&rand_st)*31)+64); if (!old_ver) { /* Make it harder to break */ @@ -245,6 +672,22 @@ char *scramble(char *to,const char *message,const char *password, } +/* + Check scrambled message + Used for pre 4.1 password handling + + 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 + + RETURN + 0 Password correct + !0 Password invalid +*/ + my_bool check_scramble(const char *scrambled, const char *message, ulong *hash_pass, my_bool old_ver) { @@ -252,8 +695,12 @@ my_bool check_scramble(const char *scrambled, const char *message, ulong hash_message[2]; char buff[16],*to,extra; /* Big enough for check */ const char *pos; + char message_buffer[9]; /* Copy of message */ - hash_password(hash_message,message); + memcpy(message_buffer,message,8); /* Old auth uses 8 bytes at maximum */ + message_buffer[8]=0; + + hash_password(hash_message,message_buffer); if (old_ver) old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); else diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 8895cb84203..55a364bdb2b 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -32,7 +32,6 @@ #include <assert.h> #include <stdarg.h> -extern uint connection_auth_flag; // any better way to do it ? struct acl_host_and_ip { @@ -146,8 +145,6 @@ my_bool acl_init(bool dont_read_acl_tables) (void (*)(void*)) free); if (dont_read_acl_tables) { - /* If we do not read tables use old handshake to make it quick for all clients */ - connection_auth_flag=CLIENT_LONG_PASSWORD; DBUG_RETURN(0); /* purecov: tested */ } @@ -224,7 +221,6 @@ my_bool acl_init(bool dont_read_acl_tables) DBUG_PRINT("info",("user table fields: %d",table->fields)); allow_all_hosts=0; - connection_auth_flag=0; /* Reset flag as we're rereading the table */ while (!(read_record_info.read_record(&read_record_info))) { ACL_USER user; @@ -239,28 +235,20 @@ my_bool acl_init(bool dont_read_acl_tables) "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 if (length % 8 && length!=45) // This holds true for passwords + else /* non emptpy and not short passwords */ { - 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 */ + 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 */ + } } - get_salt_from_password(user.salt,user.password); - user.pversion=get_password_version(user.password); - /* - We check the version of passwords in database. If no old passwords found we can force new handshake - if there are only old password we will force new handshake. In case of both types of passwords - found we will perform 2 stage authentication. - */ - if (user.password && user.password[0]!=0) /* empty passwords are not counted */ - { - if (user.pversion) - connection_auth_flag|=CLIENT_SECURE_CONNECTION; - else - connection_auth_flag|=CLIENT_LONG_PASSWORD; - } + 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 ? @@ -319,17 +307,6 @@ my_bool acl_init(bool dont_read_acl_tables) end_read_record(&read_record_info); freeze_size(&acl_users); - /* - If database is empty or has no passwords use new connection protocol - unless we're running with --old-passwords option - */ - if (!connection_auth_flag) - { - if(!opt_old_passwords) - connection_auth_flag=CLIENT_SECURE_CONNECTION; - else connection_auth_flag=CLIENT_LONG_PASSWORD; - } - printf("Set flag after read: %d\n",connection_auth_flag); /* DEBUG to be removed */ init_read_record(&read_record_info,thd,table=tables[2].table,NULL,1,0); VOID(my_init_dynamic_array(&acl_dbs,sizeof(ACL_DB),50,100)); while (!(read_record_info.read_record(&read_record_info))) @@ -509,6 +486,26 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) return 0; } +/* + 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[20]; + /* Generate new long scramble for the thread */ + create_random_string(20,&thd->rand,thd->scramble); + thd->scramble[20]=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); + /* Finally encrypt password to get prepared scramble */ + password_crypt(thd->scramble,prepared_scramble+4,bin_password,20); +} + + + + /* Get master privilges for user (priviliges for all tables). @@ -517,10 +514,11 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, const char *password,const char *message,char **priv_user, - bool old_ver, USER_RESOURCES *mqh) + bool old_ver, USER_RESOURCES *mqh,char* prepared_scramble,int stage) { ulong user_access=NO_ACCESS; *priv_user=(char*) user; + bool password_correct=0; DBUG_ENTER("acl_getroot"); bzero(mqh,sizeof(USER_RESOURCES)); @@ -543,98 +541,130 @@ ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, if (compare_hostname(&acl_user->host,host,ip)) { if (!acl_user->password && !*password || - (acl_user->password && *password && - !check_scramble(password,message,acl_user->salt, - (my_bool) old_ver))) - { + (acl_user->password && *password)) + { + /* Quick check and accept for empty passwords*/ + if (!acl_user->password && !*password) + password_correct=1; + 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 /* Password incorrect */ + /* At the first stage - prepare scramble */ + if (!stage) + prepare_scramble(thd,acl_user,prepared_scramble); + } + } + /* If password correct continue with checking other limitations */ + if (password_correct) + { #ifdef HAVE_OPENSSL - Vio *vio=thd->net.vio; - /* - In 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. + Vio *vio=thd->net.vio; + /* + In 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 (SSL_get_peer_certificate(vio->ssl_)) + 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_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 (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 + 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. + */ + if (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 (acl_user->ssl_cipher) { - user_access=NO_ACCESS; - break; + 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 + { + 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)) + /* 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) { - user_access=NO_ACCESS; - free(ptr); - break; + 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)) + { + user_access=NO_ACCESS; + free(ptr); + break; + } + user_access=acl_user->access; + free(ptr); } - 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'", + 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)) - user_access=NO_ACCESS; - else - user_access=acl_user->access; - free(ptr); + if (strcmp(acl_user->x509_subject,ptr)) + user_access=NO_ACCESS; + else + user_access=acl_user->access; + free(ptr); + } + break; } - break; - } #else /* HAVE_OPENSSL */ - user_access=acl_user->access; + user_access=acl_user->access; #endif /* HAVE_OPENSSL */ - *mqh=acl_user->user_resource; - if (!acl_user->user) + *mqh=acl_user->user_resource; + if (!acl_user->user) *priv_user=(char*) ""; // Change to anonymous user /* purecov: inspected */ - break; - } + break; + } // correct password + } // found matching user + #ifndef ALLOW_DOWNGRADE_OF_USERS break; // Wrong password breaks loop /* purecov: inspected */ #endif @@ -704,12 +734,6 @@ static void acl_update_user(const char *user, const char *host, 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); - // We should allow connection with authentication method matching password - if (acl_user->pversion) - connection_auth_flag|=CLIENT_SECURE_CONNECTION; - else - connection_auth_flag|=CLIENT_LONG_PASSWORD; - printf("Debug: flag set to %d\n",connection_auth_flag); } } break; @@ -746,10 +770,6 @@ static void acl_insert_user(const char *user, const char *host, 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); - if (acl_user.pversion) - connection_auth_flag|=CLIENT_SECURE_CONNECTION; - else - connection_auth_flag|=CLIENT_LONG_PASSWORD; } VOID(push_dynamic(&acl_users,(gptr) &acl_user)); @@ -1124,14 +1144,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (!new_password[0]) acl_user->password=0; else - { acl_user->password=(char*) ""; // Point at something - /* Adjust global connection options depending of client password*/ - if (acl_user->pversion) - connection_auth_flag|=CLIENT_SECURE_CONNECTION; - else - connection_auth_flag|=CLIENT_LONG_PASSWORD; - } acl_cache->clear(1); // Clear locked hostname cache VOID(pthread_mutex_unlock(&acl_cache->lock)); @@ -2241,7 +2254,6 @@ int mysql_grant (THD *thd, const char *db, List <LEX_USER> &list, bool create_new_users=0; TABLE_LIST tables[2]; DBUG_ENTER("mysql_grant"); - if (!initialized) { send_error(thd, ER_UNKNOWN_COM_ERROR); /* purecov: tested */ diff --git a/sql/sql_acl.h b/sql/sql_acl.h index 326a55ddd0c..a62026ef03c 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -88,7 +88,7 @@ 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, - bool old_ver, USER_RESOURCES *max); + bool old_ver, USER_RESOURCES *max,char* prepared_scramble, int stage); 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.h b/sql/sql_class.h index 71f1625309f..1d9c395c623 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -496,7 +496,7 @@ public: uint select_number; //number of select (used for EXPLAIN) /* variables.transaction_isolation is reset to this after each commit */ enum_tx_isolation session_tx_isolation; - char scramble[9]; + char scramble[21]; // extend scramble to handle new auth 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 b3cc0ab29f5..365f7d2d110 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -45,13 +45,13 @@ #define MIN_HANDSHAKE_SIZE 6 #endif /* HAVE_OPENSSL */ #define SCRAMBLE_LENGTH 8 +#define SCRAMBLE41_LENGTH 20 #define MEM_ROOT_BLOCK_SIZE 8192 #define MEM_ROOT_PREALLOC 8192 #define TRANS_MEM_ROOT_BLOCK_SIZE 4096 #define TRANS_MEM_ROOT_PREALLOC 4096 -extern uint connection_auth_flag; extern int yyparse(void); extern "C" pthread_mutex_t THR_LOCK_keycache; @@ -180,40 +180,51 @@ end: */ static bool check_user(THD *thd,enum_server_command command, const char *user, - const char *passwd, const char *db, bool check_count) + const char *passwd, const char *db, bool check_count, + bool do_send_error, char* crypted_scramble,int stage, + bool had_password) { thd->db=0; thd->db_length=0; USER_RESOURCES ur; - - if (!(thd->user = my_strdup(user, MYF(0)))) - { - send_error(thd,ER_OUT_OF_RESOURCES); - return 1; - } + /* We shall avoid dupplicate user allocations here */ + if (!(thd->user)) + if (!(thd->user = my_strdup(user, MYF(0)))) + { + send_error(thd,ER_OUT_OF_RESOURCES); + return 1; + } thd->master_access=acl_getroot(thd, thd->host, thd->ip, thd->user, passwd, thd->scramble, &thd->priv_user, protocol_version == 9 || !(thd->client_capabilities & - CLIENT_LONG_PASSWORD),&ur); + CLIENT_LONG_PASSWORD),&ur,crypted_scramble,stage); DBUG_PRINT("info", ("Capabilities: %d packet_length: %d Host: '%s' User: '%s' Using password: %s Access: %u db: '%s'", thd->client_capabilities, thd->max_client_packet_length, thd->host_or_ip, thd->priv_user, - passwd[0] ? "yes": "no", + 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 */ if (thd->master_access & NO_ACCESS) { - net_printf(thd, ER_ACCESS_DENIED_ERROR, - thd->user, + if (do_send_error) + { + net_printf(thd, ER_ACCESS_DENIED_ERROR, + thd->user, thd->host_or_ip, - passwd[0] ? ER(ER_YES) : ER(ER_NO)); - mysql_log.write(thd,COM_CONNECT,ER(ER_ACCESS_DENIED_ERROR), + 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, - passwd[0] ? ER(ER_YES) : ER(ER_NO)); - return(1); // Error already given + had_password ? ER(ER_YES) : ER(ER_NO)); + return(1); // Error already given + } + else + return(-1); // do not report error in special handshake } + if (check_count) { VOID(pthread_mutex_lock(&LOCK_thread_count)); @@ -505,9 +516,10 @@ check_connections(THD *thd) ulong pkt_len=0; { /* buff[] needs to big enough to hold the server_version variable */ - char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH+32],*end; + char buff[SERVER_VERSION_LENGTH + + SCRAMBLE_LENGTH+64],*end; int client_flags = CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | - CLIENT_PROTOCOL_41 | connection_auth_flag; + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION; if (opt_using_transactions) client_flags|=CLIENT_TRANSACTIONS; @@ -529,6 +541,8 @@ check_connections(THD *thd) int2store(end+3,thd->server_status); bzero(end+5,13); end+=18; + + // 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 || @@ -581,19 +595,82 @@ check_connections(THD *thd) char *user= (char*) net->read_pos+5; char *passwd= strend(user)+1; char *db=0; - if (passwd[0] && strlen(passwd) != SCRAMBLE_LENGTH) - return ER_HANDSHAKE_ERROR; - if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB) - db=strend(passwd)+1; + if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB) + db=strend(passwd)+1; + + /* We can get only old hash at this point */ + if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH) + return ER_HANDSHAKE_ERROR; + if (thd->client_capabilities & CLIENT_INTERACTIVE) - thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; + thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && - opt_using_transactions) - thd->net.return_status= &thd->server_status; + opt_using_transactions) + thd->net.return_status= &thd->server_status; net->read_timeout=(uint) thd->variables.net_read_timeout; - if (check_user(thd,COM_CONNECT, user, passwd, db, 1)) - return (-1); - thd->password=test(passwd[0]); + + char prepared_scramble[SCRAMBLE41_LENGTH+4]; /* Buffer for scramble and hash */ + + /* Simple connect only for old clients. New clients always use secure auth */ + bool simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)); + + /* Store information if we used password. passwd will be dammaged */ + bool using_password=test(passwd[0]); + + /* Check user permissions. If password failure we'll get scramble back */ + if (check_user(thd,COM_CONNECT, user, passwd, db, 1, simple_connect, + prepared_scramble,0,using_password)) + { + /* If The client is old we just have to return error */ + if (simple_connect) + return -1; + + /* 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 (user) + { + strncpy(tmp_user,user,USERNAME_LENGTH+1); + /* Extra safety if we have too long data */ + tmp_user[USERNAME_LENGTH]=0; + } + else + tmp_user[0]=0; + if (db) + { + strncpy(tmp_db,db,NAME_LEN+1); + tmp_db[NAME_LEN]=0; + } + else + tmp_db[0]=0; + + /* 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, 1,prepared_scramble,1,using_password)) + return -1; + } + thd->password=using_password; return 0; } @@ -954,7 +1031,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, send_error(thd, ER_UNKNOWN_COM_ERROR); break; } - if (check_user(thd, COM_CHANGE_USER, user, passwd, db, 0)) + /* WARNING THIS HAS TO BE REWRITTEN */ + char tmp_buffer[64]; + printf("Change user called: %s %s %s\n",user,passwd,db); + if (check_user(thd, COM_CHANGE_USER, user, passwd, db, 0,1,tmp_buffer,0,1)) { // Restore old user x_free(thd->user); x_free(thd->db); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 1740c4c668e..d39ffeb5a6f 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -3745,7 +3745,7 @@ text_or_password: else { char *buff=(char*) sql_alloc(HASH_PASSWORD_LENGTH+1); - make_scrambled_password(buff,$3.str,opt_old_passwords); + make_scrambled_password(buff,$3.str,opt_old_passwords,¤t_thd->rand); $$=buff; } } @@ -4041,7 +4041,7 @@ grant_user: char *buff=(char*) sql_alloc(HASH_PASSWORD_LENGTH+1); if (buff) { - make_scrambled_password(buff,$4.str,opt_old_passwords); + make_scrambled_password(buff,$4.str,opt_old_passwords,¤t_thd->rand); $1->password.str=buff; $1->password.length=HASH_PASSWORD_LENGTH; } |