summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <peter@mysql.com>2002-11-24 17:07:53 +0300
committerunknown <peter@mysql.com>2002-11-24 17:07:53 +0300
commit8c8b97fdf4756a699346ae1688136624d76713bc (patch)
treedb7cb82e2166048d043214a331a5ad63bc7d0621
parent85bbdcf016dadf31335ad9ee98c3c8acfc167e2f (diff)
downloadmariadb-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.c22
-rw-r--r--include/mysql.h2
-rw-r--r--include/mysql_com.h9
-rw-r--r--libmysql/libmysql.c92
-rw-r--r--mysql-test/install_test_db.sh2
-rwxr-xr-xmysql-test/t/rpl000017-slave.sh2
-rw-r--r--scripts/mysql_fix_privilege_tables.sh1
-rw-r--r--scripts/mysql_install_db.sh2
-rw-r--r--sql/item_strfunc.cc4
-rw-r--r--sql/mini_client.cc90
-rw-r--r--sql/mysqld.cc1
-rw-r--r--sql/password.c539
-rw-r--r--sql/sql_acl.cc280
-rw-r--r--sql/sql_acl.h2
-rw-r--r--sql/sql_class.h2
-rw-r--r--sql/sql_parse.cc138
-rw-r--r--sql/sql_yacc.yy4
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,&current_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,&current_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,&current_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,&current_thd->rand);
$1->password.str=buff;
$1->password.length=HASH_PASSWORD_LENGTH;
}