Commit 05ba93c2 authored by unknown's avatar unknown

SCRUM: Secure auth

Implement mysql_change_user
Get rid of double user search at authentication
Some cleanups


client/mysqladmin.c:
  Fix long line
include/mysql_com.h:
  Fix long lines
libmysql/libmysql.c:
  mysql_change_user() for new auth + some fixes
sql/password.c:
  Add author info so who is guilty in errors would be known :)
sql/sql_acl.cc:
  Move class definitions to .h
sql/sql_acl.h:
  Add class definitions
sql/sql_parse.cc:
  Get rid of double user search. Implement mysql_change_user
parent 14754ce1
......@@ -768,8 +768,9 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv)
return 1;
}
if (argv[1][0])
make_scrambled_password(crypted_pw,argv[1],(find_type(argv[0],&command_typelib,2)
==ADMIN_OLD_PASSWORD),&rand_st);
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);
......
......@@ -280,18 +280,22 @@ 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,struct rand_struct *rand_st);
void make_scrambled_password(char *to,const char *password,
my_bool force_old_scramble,struct rand_struct *rand_st);
int get_password_length(my_bool force_old_scramble);
char get_password_version(const char* password);
void create_random_string(int length,struct rand_struct *rand_st,char* target);
my_bool validate_password(const char* password, const char* message, ulong* salt);
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_hash_and_password(ulong* salt, unsigned char pversion,char* hash,
unsigned char* bin_password);
void get_salt_from_password(unsigned long *res,const char *password);
void create_key_from_old_password(const char* password,char* key);
void make_password_from_salt(char *to, unsigned long *hash_res, uint8 password_version);
void make_password_from_salt(char *to, unsigned long *hash_res,
unsigned char password_version);
char *scramble(char *to,const char *message,const char *password,
my_bool old_ver);
my_bool check_scramble(const char *, const char *message,
......
......@@ -2220,7 +2220,7 @@ Try also with PIPE or TCP/IP
#include "_cust_libmysql.h";
#endif
DBUG_PRINT("info",("user: %s",buff+5));
/*
/*
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
*/
......@@ -2228,10 +2228,10 @@ Try also with PIPE or TCP/IP
{
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));
}
/* Use something for not empty password not to match against empty one */
end=scramble(strend(buff+5)+1, mysql->scramble_buff,"\1~MySQL#!\2",
(my_bool) (mysql->protocol_version == 9));
}
else /* For empty password*/
{
end=strend(buff+5)+1;
......@@ -2242,11 +2242,11 @@ Try also with PIPE or TCP/IP
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;
}
......@@ -2409,21 +2409,96 @@ static my_bool mysql_reconnect(MYSQL *mysql)
my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user,
const char *passwd, const char *db)
{
char buff[512],*pos=buff;
char buff[512],*end=buff;
ulong pkt_length;
char password_hash[20]; /* Used for tmp storage of stage1 hash */
NET *net= &mysql->net;
DBUG_ENTER("mysql_change_user");
if (!user)
user="";
if (!passwd)
passwd="";
/* Store user into the buffer */
end=strmov(end,user)+1;
/*
We always start with old type handshake the only difference is message sent
If server handles secure connection type we'll not send the real scramble
*/
if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION)
{
if (passwd[0])
{
/* Use something for not empty password not to match it against empty one */
end=scramble(end, mysql->scramble_buff,"\1~MySQL#!\2",
(my_bool) (mysql->protocol_version == 9));
}
else /* For empty password*/
*end=0; /* Store zero length scramble */
}
/* Real scramble is sent only for servers. This is to be blocked by option */
else
end=scramble(end, mysql->scramble_buff, passwd,
(my_bool) (mysql->protocol_version == 9));
/* Add database if needed */
end=strmov(end+1,db ? db : "");
/* Write authentication package */
simple_command(mysql,COM_CHANGE_USER, buff,(ulong) (end-buff),1);
/* We shall only query sever if it expect us to do so */
if ( (pkt_length=net_safe_read(mysql)) == packet_error)
goto error;
pos=strmov(pos,user)+1;
pos=scramble(pos, mysql->scramble_buff, passwd,
(my_bool) (mysql->protocol_version == 9));
pos=strmov(pos+1,db ? db : "");
if (simple_command(mysql,COM_CHANGE_USER, buff,(ulong) (pos-buff),0))
DBUG_RETURN(1);
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;
}
}
my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR));
my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR));
my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR));
......@@ -2432,6 +2507,10 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user,
mysql->passwd=my_strdup(passwd,MYF(MY_WME));
mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0;
DBUG_RETURN(0);
error:
DBUG_RETURN(1);
}
......
......@@ -48,6 +48,8 @@
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.
New Password handling functions by Peter Zaitsev
*****************************************************************************/
......
This diff is collapsed.
......@@ -79,6 +79,55 @@
#define fix_rights_for_column(A) (((A) & COL_ACLS) | ((A & ~COL_ACLS) << 7))
#define get_rights_for_column(A) (((A) & COL_ACLS) | ((A & ~COL_ACLS) >> 7))
/* Classes */
struct acl_host_and_ip
{
char *hostname;
long ip,ip_mask; // Used with masked ip:s
};
class ACL_ACCESS {
public:
ulong sort;
ulong access;
};
/* ACL_HOST is used if no host is specified */
class ACL_HOST :public ACL_ACCESS
{
public:
acl_host_and_ip host;
char *db;
};
class ACL_USER :public ACL_ACCESS
{
public:
acl_host_and_ip host;
uint hostname_length;
USER_RESOURCES user_resource;
char *user,*password;
ulong salt[6]; // New password has longer length
uint8 pversion; // password version
enum SSL_type ssl_type;
const char *ssl_cipher, *x509_issuer, *x509_subject;
};
class ACL_DB :public ACL_ACCESS
{
public:
acl_host_and_ip host;
char *user,*db;
};
/* prototypes */
my_bool acl_init(THD *thd, bool dont_read_acl_tables);
......@@ -88,7 +137,8 @@ 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,char* prepared_scramble, int stage);
bool old_ver, USER_RESOURCES *max,char* prepared_scramble,
int stage, uint *cur_priv_version, ACL_USER **cached_user);
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,
......
......@@ -188,14 +188,16 @@ static int get_or_create_user_conn(THD *thd, const char *user,
thd->user, thd->master_access, thd->priv_user, thd->db, thd->db_access
*/
static bool check_user(THD *thd,enum_server_command command, const char *user,
static int check_user(THD *thd,enum_server_command command, const char *user,
const char *passwd, const char *db, bool check_count,
bool do_send_error, char* crypted_scramble,int stage,
bool had_password)
bool had_password,uint *cur_priv_version,
ACL_USER** hint_user)
{
thd->db=0;
thd->db_length=0;
USER_RESOURCES ur;
/* We shall avoid dupplicate user allocations here */
if (!(thd->user))
if (!(thd->user = my_strdup(user, MYF(0))))
......@@ -207,7 +209,9 @@ static bool check_user(THD *thd,enum_server_command command, const char *user,
passwd, thd->scramble, &thd->priv_user,
protocol_version == 9 ||
!(thd->client_capabilities &
CLIENT_LONG_PASSWORD),&ur,crypted_scramble,stage);
CLIENT_LONG_PASSWORD),&ur,crypted_scramble,
stage,cur_priv_version,hint_user);
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,
......@@ -233,7 +237,7 @@ static bool check_user(THD *thd,enum_server_command command, const char *user,
else
return(-1); // do not report error in special handshake
}
if (check_count)
{
VOID(pthread_mutex_lock(&LOCK_thread_count));
......@@ -261,12 +265,13 @@ static bool check_user(THD *thd,enum_server_command command, const char *user,
if (thd->user_connect && thd->user_connect->user_resources.connections &&
check_for_max_user_connections(thd, thd->user_connect))
return -1;
if (db && db[0])
{
bool error=test(mysql_change_db(thd,db));
if (error && thd->user_connect)
decrease_user_connections(thd->user_connect);
return error;
return error;
}
else
send_ok(thd); // Ready to handle questions
......@@ -616,7 +621,7 @@ check_connections(THD *thd)
/* 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;
if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
......@@ -625,6 +630,9 @@ check_connections(THD *thd)
net->read_timeout=(uint) thd->variables.net_read_timeout;
char prepared_scramble[SCRAMBLE41_LENGTH+4]; /* Buffer for scramble and hash */
ACL_USER* cached_user;
uint cur_priv_version;
/* Simple connect only for old clients. New clients always use secure auth */
bool simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION));
......@@ -634,7 +642,7 @@ check_connections(THD *thd)
/* 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))
prepared_scramble,0,using_password,&cur_priv_version,&cached_user)<0)
{
/* If The client is old we just have to return error */
if (simple_connect)
......@@ -682,7 +690,8 @@ check_connections(THD *thd)
}
/* 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))
tmp_db, 1, 1,prepared_scramble,1,using_password,&cur_priv_version,
&cached_user))
return -1;
}
thd->password=using_password;
......@@ -1034,43 +1043,117 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
char *user= (char*) packet;
char *passwd= strend(user)+1;
char *db= strend(passwd)+1;
/* Save user and privileges */
uint save_master_access=thd->master_access;
uint save_db_access= thd->db_access;
uint save_db_length= thd->db_length;
char *save_user= thd->user;
thd->user=NULL; /* Needed for check_user to allocate new user */
char *save_priv_user= thd->priv_user;
char *save_db= thd->db;
USER_CONN *save_uc= thd->user_connect;
USER_CONN *save_uc= thd->user_connect;
bool simple_connect;
bool using_password;
ulong pkt_len=0; /* Length of reply packet */
/* Small check for incomming packet */
if ((uint) ((uchar*) db - net->read_pos) > packet_length)
{ // Check if protocol is ok
send_error(thd, ER_UNKNOWN_COM_ERROR);
break;
}
/* 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);
thd->master_access=save_master_access;
thd->db_access=save_db_access;
thd->db=save_db;
thd->db_length=save_db_length;
thd->user=save_user;
thd->priv_user=save_priv_user;
break;
goto restore_user_err;
/* Now we shall basically perform authentication again */
/* We can get only old hash at this point */
if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH)
goto restore_user_err;
char prepared_scramble[SCRAMBLE41_LENGTH+4];/* Buffer for scramble,hash */
ACL_USER* cached_user; /* Cached user */
uint cur_priv_version; /* Cached grant version */
/* Simple connect only for old clients. New clients always use sec. auth*/
simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION));
/* Store information if we used password. passwd will be dammaged */
using_password=test(passwd[0]);
/*
Check user permissions. If password failure we'll get scramble back
Do not retry if we already have sent error (result>0)
*/
if (check_user(thd,COM_CHANGE_USER, user, passwd, db, 0, simple_connect,
prepared_scramble,0,using_password,&cur_priv_version,&cached_user)<0)
{
/* If The client is old we just have to have auth failure */
if (simple_connect)
goto restore_user; /* Error is already reported */
/* Store current used and database as they are erased with next packet */
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))
goto restore_user_err;
/* Reading packet back */
if ((pkt_len=my_net_read(net)) == packet_error)
goto restore_user_err;
/* We have to get very specific packet size */
if (pkt_len!=SCRAMBLE41_LENGTH)
goto restore_user;
/* Final attempt to check the user based on reply */
if (check_user(thd,COM_CONNECT, tmp_user, (char*)net->read_pos,
tmp_db, 0, 1,prepared_scramble,1,using_password,&cur_priv_version,
&cached_user))
goto restore_user;
}
/* Finally we've authenticated new user */
if (max_connections && save_uc)
decrease_user_connections(save_uc);
x_free((gptr) save_db);
x_free((gptr) save_user);
thd->password=test(passwd[0]);
thd->password=using_password;
break;
/* Bad luck we shall restore old user */
restore_user_err:
send_error(thd, ER_UNKNOWN_COM_ERROR);
restore_user:
x_free(thd->user);
x_free(thd->db);
thd->master_access=save_master_access;
thd->db_access=save_db_access;
thd->db=save_db;
thd->db_length=save_db_length;
thd->user=save_user;
thd->priv_user=save_priv_user;
break;
}
case COM_EXECUTE:
{
mysql_stmt_execute(thd, packet);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment