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) ...@@ -768,8 +768,9 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv)
return 1; return 1;
} }
if (argv[1][0]) if (argv[1][0])
make_scrambled_password(crypted_pw,argv[1],(find_type(argv[0],&command_typelib,2) make_scrambled_password(crypted_pw,argv[1],(find_type(argv[0],
==ADMIN_OLD_PASSWORD),&rand_st); &command_typelib,2)==ADMIN_OLD_PASSWORD),
&rand_st);
else else
crypted_pw[0]=0; /* No password */ crypted_pw[0]=0; /* No password */
sprintf(buff,"set password='%s',sql_log_off=0",crypted_pw); sprintf(buff,"set password='%s',sql_log_off=0",crypted_pw);
......
...@@ -280,18 +280,22 @@ extern unsigned long net_buffer_length; ...@@ -280,18 +280,22 @@ extern unsigned long net_buffer_length;
void randominit(struct rand_struct *,unsigned long seed1, void randominit(struct rand_struct *,unsigned long seed1,
unsigned long seed2); unsigned long seed2);
double rnd(struct rand_struct *); 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); int get_password_length(my_bool force_old_scramble);
char get_password_version(const char* password); char get_password_version(const char* password);
void create_random_string(int length,struct rand_struct *rand_st,char* target); 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_stage1(char *to, const char *password);
void password_hash_stage2(char *to,const char *salt); void password_hash_stage2(char *to,const char *salt);
void password_crypt(const char* from,char* to, const char* password,int length); 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 get_salt_from_password(unsigned long *res,const char *password);
void create_key_from_old_password(const char* password,char* key); 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, char *scramble(char *to,const char *message,const char *password,
my_bool old_ver); my_bool old_ver);
my_bool check_scramble(const char *, const char *message, my_bool check_scramble(const char *, const char *message,
......
...@@ -2220,7 +2220,7 @@ Try also with PIPE or TCP/IP ...@@ -2220,7 +2220,7 @@ Try also with PIPE or TCP/IP
#include "_cust_libmysql.h"; #include "_cust_libmysql.h";
#endif #endif
DBUG_PRINT("info",("user: %s",buff+5)); DBUG_PRINT("info",("user: %s",buff+5));
/* /*
We always start with old type handshake the only difference is message sent 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 server handles secure connection type we'll not send the real scramble
*/ */
...@@ -2228,10 +2228,10 @@ Try also with PIPE or TCP/IP ...@@ -2228,10 +2228,10 @@ Try also with PIPE or TCP/IP
{ {
if (passwd[0]) if (passwd[0])
{ {
/* Use something for not empty password not to match it against empty one */ /* Use something for not empty password not to match against empty one */
end=scramble(strend(buff+5)+1, mysql->scramble_buff,"~MySQL#!", end=scramble(strend(buff+5)+1, mysql->scramble_buff,"\1~MySQL#!\2",
(my_bool) (mysql->protocol_version == 9)); (my_bool) (mysql->protocol_version == 9));
} }
else /* For empty password*/ else /* For empty password*/
{ {
end=strend(buff+5)+1; end=strend(buff+5)+1;
...@@ -2242,11 +2242,11 @@ Try also with PIPE or TCP/IP ...@@ -2242,11 +2242,11 @@ Try also with PIPE or TCP/IP
else else
end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd, end=scramble(strend(buff+5)+1, mysql->scramble_buff, passwd,
(my_bool) (mysql->protocol_version == 9)); (my_bool) (mysql->protocol_version == 9));
/* Add database if needed */ /* Add database if needed */
if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) 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)); mysql->db=my_strdup(db,MYF(MY_WME));
db=0; db=0;
} }
...@@ -2409,21 +2409,96 @@ static my_bool mysql_reconnect(MYSQL *mysql) ...@@ -2409,21 +2409,96 @@ static my_bool mysql_reconnect(MYSQL *mysql)
my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user,
const char *passwd, const char *db) 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"); DBUG_ENTER("mysql_change_user");
if (!user) if (!user)
user=""; user="";
if (!passwd) if (!passwd)
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; if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION)
pos=scramble(pos, mysql->scramble_buff, passwd, {
(my_bool) (mysql->protocol_version == 9)); /* This should basically always happen with new server unless empty password */
pos=strmov(pos+1,db ? db : ""); if (pkt_length==24) /* We have new hash back */
if (simple_command(mysql,COM_CHANGE_USER, buff,(ulong) (pos-buff),0)) {
DBUG_RETURN(1); /* 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->user,MYF(MY_ALLOW_ZERO_PTR));
my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR));
my_free(mysql->db,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, ...@@ -2432,6 +2507,10 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user,
mysql->passwd=my_strdup(passwd,MYF(MY_WME)); mysql->passwd=my_strdup(passwd,MYF(MY_WME));
mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0; mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0;
DBUG_RETURN(0); DBUG_RETURN(0);
error:
DBUG_RETURN(1);
} }
......
...@@ -48,6 +48,8 @@ ...@@ -48,6 +48,8 @@
This authentication needs 2 packet round trips instead of one but it is much 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 stronger. Now if one will steal mysql database content he will not be able
to break into MySQL. to break into MySQL.
New Password handling functions by Peter Zaitsev
*****************************************************************************/ *****************************************************************************/
......
...@@ -33,52 +33,6 @@ ...@@ -33,52 +33,6 @@
#include <stdarg.h> #include <stdarg.h>
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;
};
class acl_entry :public hash_filo_element class acl_entry :public hash_filo_element
{ {
public: public:
...@@ -105,6 +59,7 @@ static HASH acl_check_hosts, hash_tables; ...@@ -105,6 +59,7 @@ static HASH acl_check_hosts, hash_tables;
static DYNAMIC_ARRAY acl_wild_hosts; static DYNAMIC_ARRAY acl_wild_hosts;
static hash_filo *acl_cache; static hash_filo *acl_cache;
static uint grant_version=0; static uint grant_version=0;
static uint priv_version=0; /* Version of priv tables. incremented by acl_init */
static ulong get_access(TABLE *form,uint fieldnr); static ulong get_access(TABLE *form,uint fieldnr);
static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b); static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b);
static ulong get_sort(uint count,...); static ulong get_sort(uint count,...);
...@@ -148,7 +103,9 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) ...@@ -148,7 +103,9 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
{ {
DBUG_RETURN(0); /* purecov: tested */ DBUG_RETURN(0); /* purecov: tested */
} }
priv_version++; /* Priveleges updated */
/* /*
To be able to run this from boot, we allocate a temporary THD To be able to run this from boot, we allocate a temporary THD
*/ */
...@@ -515,15 +472,23 @@ void prepare_scramble(THD* thd, ACL_USER *acl_user,char* prepared_scramble) ...@@ -515,15 +472,23 @@ void prepare_scramble(THD* thd, ACL_USER *acl_user,char* prepared_scramble)
/* /*
Get master privilges for user (priviliges for all tables). Get master privilges for user (priviliges for all tables).
Required before connecting to MySQL Required before connecting to MySQL
as we have 2 stage handshake now we cache user not to lookup
it second time. At the second stage we do not lookup user in case
we already know it;
*/ */
ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user,
const char *password,const char *message,char **priv_user, const char *password,const char *message,char **priv_user,
bool old_ver, USER_RESOURCES *mqh,char* prepared_scramble,int stage) bool old_ver, USER_RESOURCES *mqh,char* prepared_scramble,
int stage,uint *cur_priv_version,ACL_USER** hint_user)
{ {
ulong user_access=NO_ACCESS; ulong user_access=NO_ACCESS;
*priv_user=(char*) user; *priv_user=(char*) user;
bool password_correct=0; bool password_correct=0;
ACL_USER *acl_user=NULL;
DBUG_ENTER("acl_getroot"); DBUG_ENTER("acl_getroot");
bzero(mqh,sizeof(USER_RESOURCES)); bzero(mqh,sizeof(USER_RESOURCES));
...@@ -533,159 +498,175 @@ ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, ...@@ -533,159 +498,175 @@ ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user,
DBUG_RETURN((ulong) ~NO_ACCESS); /* purecov: tested */ DBUG_RETURN((ulong) ~NO_ACCESS); /* purecov: tested */
} }
VOID(pthread_mutex_lock(&acl_cache->lock)); VOID(pthread_mutex_lock(&acl_cache->lock));
/* /*
Get possible access from user_list. This is or'ed to others not Get possible access from user_list. This is or'ed to others not
fully specified fully specified
If we have cached user use it, in other case look it up.
*/ */
for (uint i=0 ; i < acl_users.elements ; i++)
{ if (stage && (*cur_priv_version==priv_version))
ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); acl_user=*hint_user;
if (!acl_user->user || !strcmp(user,acl_user->user)) else
for (uint i=0 ; i < acl_users.elements ; i++)
{ {
if (compare_hostname(&acl_user->host,host,ip)) ACL_USER *acl_user_search=dynamic_element(&acl_users,i,ACL_USER*);
if (!acl_user_search->user || !strcmp(user,acl_user_search->user))
{ {
if (!acl_user->password && !*password || if (compare_hostname(&acl_user_search->host,host,ip))
(acl_user->password && *password)) {
{ /* Found mathing user */
/* Quick check and accept for empty passwords*/ acl_user=acl_user_search;
if (!acl_user->password && !*password) /* Store it as a cache */
password_correct=1; *hint_user=acl_user;
else *cur_priv_version=priv_version;
{ break;
/* 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)) /* Now we have acl_user found and may start our checks */
password_correct=1;
} if (acl_user)
else /* First stage - just prepare scramble */ {
prepare_scramble(thd,acl_user,prepared_scramble); /* Password should present for both or absend for both */
} if (!acl_user->password && !*password ||
/* Old way to check password */ (acl_user->password && *password))
else {
{ /* Quick check and accept for empty passwords*/
/* Checking the scramble at any stage. First - old clients */ if (!acl_user->password && !*password)
if (!check_scramble(password,message,acl_user->salt, password_correct=1;
(my_bool) old_ver)) else /* Normal password presents */
password_correct=1; {
else /* Password incorrect */ /* New version password is checked differently */
/* At the first stage - prepare scramble */ if (acl_user->pversion)
if (!stage) {
prepare_scramble(thd,acl_user,prepared_scramble); if (stage) /* We check password only on the second stage */
} {
if (!validate_password(password,message,acl_user->salt))
password_correct=1;
} }
/* If password correct continue with checking other limitations */ else /* First stage - just prepare scramble */
if (password_correct) prepare_scramble(thd,acl_user,prepared_scramble);
{ }
#ifdef HAVE_OPENSSL /* Old way to check password */
Vio *vio=thd->net.vio; else
/* {
In this point we know that user is allowed to connect /* Checking the scramble at any stage. First - old clients */
from given host by given username/password pair. Now if (!check_scramble(password,message,acl_user->salt,
we check if SSL is required, if user is using SSL and (my_bool) old_ver))
if X509 certificate attributes are OK password_correct=1;
*/ else /* Password incorrect */
switch (acl_user->ssl_type) { /* At the first stage - prepare scramble */
case SSL_TYPE_NOT_SPECIFIED: // Impossible if (!stage)
case SSL_TYPE_NONE: /* SSL is not required to connect */ prepare_scramble(thd,acl_user,prepared_scramble);
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; /* If user not found password_correct will also be zero */
case SSL_TYPE_X509: /* Client should have any valid certificate. */ if (!password_correct)
/* goto unlock_and_exit;
Connections with non-valid certificates are dropped already
in sslaccept() anyway, so we do not check validity here. /* OK. User found and password checked continue validation */
*/
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)
{
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))
{
user_access=NO_ACCESS;
free(ptr);
break;
}
user_access=acl_user->access;
free(ptr);
}
DBUG_PRINT("info",("checkpoint 4"));
/* X509 subject is specified, we check it .. */
if (acl_user->x509_subject)
{
char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
DBUG_PRINT("info",("comparing subjects: '%s' and '%s'",
acl_user->x509_subject, ptr));
if (strcmp(acl_user->x509_subject,ptr))
user_access=NO_ACCESS;
else
user_access=acl_user->access;
free(ptr);
}
break;
}
#else /* HAVE_OPENSSL */
user_access=acl_user->access;
#endif /* HAVE_OPENSSL */
*mqh=acl_user->user_resource;
if (!acl_user->user)
*priv_user=(char*) ""; // Change to anonymous user /* purecov: inspected */
break;
} // correct password
} // found matching user
#ifndef ALLOW_DOWNGRADE_OF_USERS #ifdef HAVE_OPENSSL
break; // Wrong password breaks loop /* purecov: inspected */ Vio *vio=thd->net.vio;
#endif /*
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.
*/
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)
{
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))
{
user_access=NO_ACCESS;
free(ptr);
break;
} }
user_access=acl_user->access;
free(ptr);
}
DBUG_PRINT("info",("checkpoint 4"));
/* X509 subject is specified, we check it .. */
if (acl_user->x509_subject)
{
char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
DBUG_PRINT("info",("comparing subjects: '%s' and '%s'",
acl_user->x509_subject, ptr));
if (strcmp(acl_user->x509_subject,ptr))
user_access=NO_ACCESS;
else
user_access=acl_user->access;
free(ptr);
} }
break;
} }
#else /* HAVE_OPENSSL */
user_access=acl_user->access;
#endif /* HAVE_OPENSSL */
*mqh=acl_user->user_resource;
if (!acl_user->user)
*priv_user=(char*) ""; // Change to anonymous user /* purecov: inspected */
unlock_and_exit:
VOID(pthread_mutex_unlock(&acl_cache->lock)); VOID(pthread_mutex_unlock(&acl_cache->lock));
DBUG_RETURN(user_access); DBUG_RETURN(user_access);
} }
/*
** Functions to add and change user and database privileges when one
** changes things with GRANT
*/
static byte* check_get_key(ACL_USER *buff,uint *length, static byte* check_get_key(ACL_USER *buff,uint *length,
my_bool not_used __attribute__((unused))) my_bool not_used __attribute__((unused)))
{ {
......
...@@ -79,6 +79,55 @@ ...@@ -79,6 +79,55 @@
#define fix_rights_for_column(A) (((A) & COL_ACLS) | ((A & ~COL_ACLS) << 7)) #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)) #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 */ /* prototypes */
my_bool acl_init(THD *thd, bool dont_read_acl_tables); 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, ...@@ -88,7 +137,8 @@ ulong acl_get(const char *host, const char *ip, const char *bin_ip,
const char *user, const char *db); const char *user, const char *db);
ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user, ulong acl_getroot(THD *thd, const char *host, const char *ip, const char *user,
const char *password,const char *scramble,char **priv_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 acl_check_host(const char *host, const char *ip);
bool check_change_password(THD *thd, const char *host, const char *user); bool check_change_password(THD *thd, const char *host, const char *user);
bool 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 @@ end: ...@@ -188,14 +188,16 @@ end:
thd->user, thd->master_access, thd->priv_user, thd->db, thd->db_access 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, const char *passwd, const char *db, bool check_count,
bool do_send_error, char* crypted_scramble,int stage, 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=0;
thd->db_length=0; thd->db_length=0;
USER_RESOURCES ur; USER_RESOURCES ur;
/* We shall avoid dupplicate user allocations here */ /* We shall avoid dupplicate user allocations here */
if (!(thd->user)) if (!(thd->user))
if (!(thd->user = my_strdup(user, MYF(0)))) 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, ...@@ -207,7 +209,9 @@ static bool check_user(THD *thd,enum_server_command command, const char *user,
passwd, thd->scramble, &thd->priv_user, passwd, thd->scramble, &thd->priv_user,
protocol_version == 9 || protocol_version == 9 ||
!(thd->client_capabilities & !(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", DBUG_PRINT("info",
("Capabilities: %d packet_length: %d Host: '%s' User: '%s' Using password: %s Access: %u db: '%s'", ("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->client_capabilities, thd->max_client_packet_length,
...@@ -233,7 +237,7 @@ static bool check_user(THD *thd,enum_server_command command, const char *user, ...@@ -233,7 +237,7 @@ static bool check_user(THD *thd,enum_server_command command, const char *user,
else else
return(-1); // do not report error in special handshake return(-1); // do not report error in special handshake
} }
if (check_count) if (check_count)
{ {
VOID(pthread_mutex_lock(&LOCK_thread_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, ...@@ -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 && if (thd->user_connect && thd->user_connect->user_resources.connections &&
check_for_max_user_connections(thd, thd->user_connect)) check_for_max_user_connections(thd, thd->user_connect))
return -1; return -1;
if (db && db[0]) if (db && db[0])
{ {
bool error=test(mysql_change_db(thd,db)); bool error=test(mysql_change_db(thd,db));
if (error && thd->user_connect) if (error && thd->user_connect)
decrease_user_connections(thd->user_connect); decrease_user_connections(thd->user_connect);
return error; return error;
} }
else else
send_ok(thd); // Ready to handle questions send_ok(thd); // Ready to handle questions
...@@ -616,7 +621,7 @@ check_connections(THD *thd) ...@@ -616,7 +621,7 @@ check_connections(THD *thd)
/* We can get only old hash at this point */ /* We can get only old hash at this point */
if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH) if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH)
return ER_HANDSHAKE_ERROR; return ER_HANDSHAKE_ERROR;
if (thd->client_capabilities & CLIENT_INTERACTIVE) 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) && if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
...@@ -625,6 +630,9 @@ check_connections(THD *thd) ...@@ -625,6 +630,9 @@ check_connections(THD *thd)
net->read_timeout=(uint) thd->variables.net_read_timeout; net->read_timeout=(uint) thd->variables.net_read_timeout;
char prepared_scramble[SCRAMBLE41_LENGTH+4]; /* Buffer for scramble and hash */ 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 */ /* Simple connect only for old clients. New clients always use secure auth */
bool simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)); bool simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION));
...@@ -634,7 +642,7 @@ check_connections(THD *thd) ...@@ -634,7 +642,7 @@ check_connections(THD *thd)
/* Check user permissions. If password failure we'll get scramble back */ /* Check user permissions. If password failure we'll get scramble back */
if (check_user(thd,COM_CONNECT, user, passwd, db, 1, simple_connect, 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 The client is old we just have to return error */
if (simple_connect) if (simple_connect)
...@@ -682,7 +690,8 @@ check_connections(THD *thd) ...@@ -682,7 +690,8 @@ check_connections(THD *thd)
} }
/* Final attempt to check the user based on reply */ /* Final attempt to check the user based on reply */
if (check_user(thd,COM_CONNECT, tmp_user, (char*)net->read_pos, 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; return -1;
} }
thd->password=using_password; thd->password=using_password;
...@@ -1034,43 +1043,117 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -1034,43 +1043,117 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
char *user= (char*) packet; char *user= (char*) packet;
char *passwd= strend(user)+1; char *passwd= strend(user)+1;
char *db= strend(passwd)+1; char *db= strend(passwd)+1;
/* Save user and privileges */ /* Save user and privileges */
uint save_master_access=thd->master_access; uint save_master_access=thd->master_access;
uint save_db_access= thd->db_access; uint save_db_access= thd->db_access;
uint save_db_length= thd->db_length; uint save_db_length= thd->db_length;
char *save_user= thd->user; 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_priv_user= thd->priv_user;
char *save_db= thd->db; 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) if ((uint) ((uchar*) db - net->read_pos) > packet_length)
{ // Check if protocol is ok goto restore_user_err;
send_error(thd, ER_UNKNOWN_COM_ERROR);
break; /* Now we shall basically perform authentication again */
}
/* WARNING THIS HAS TO BE REWRITTEN */ /* We can get only old hash at this point */
char tmp_buffer[64]; if (passwd[0] && strlen(passwd)!=SCRAMBLE_LENGTH)
printf("Change user called: %s %s %s\n",user,passwd,db); goto restore_user_err;
if (check_user(thd, COM_CHANGE_USER, user, passwd, db, 0,1,tmp_buffer,0,1))
{ // Restore old user char prepared_scramble[SCRAMBLE41_LENGTH+4];/* Buffer for scramble,hash */
x_free(thd->user); ACL_USER* cached_user; /* Cached user */
x_free(thd->db); uint cur_priv_version; /* Cached grant version */
thd->master_access=save_master_access;
thd->db_access=save_db_access; /* Simple connect only for old clients. New clients always use sec. auth*/
thd->db=save_db; simple_connect=(!(thd->client_capabilities & CLIENT_SECURE_CONNECTION));
thd->db_length=save_db_length;
thd->user=save_user; /* Store information if we used password. passwd will be dammaged */
thd->priv_user=save_priv_user; using_password=test(passwd[0]);
break;
/*
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) if (max_connections && save_uc)
decrease_user_connections(save_uc); decrease_user_connections(save_uc);
x_free((gptr) save_db); x_free((gptr) save_db);
x_free((gptr) save_user); 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; break;
} }
case COM_EXECUTE: case COM_EXECUTE:
{ {
mysql_stmt_execute(thd, packet); 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