summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <peter@mysql.com>2002-11-30 16:31:58 +0300
committerunknown <peter@mysql.com>2002-11-30 16:31:58 +0300
commit05ba93c2793a1ae79f560a0fc153056f3d39ce43 (patch)
tree8cb72ffc1f46d5e546f302958453ce4a83d26d5e
parent14754ce141aa6a061a94fa668094b0602edd69c5 (diff)
downloadmariadb-git-05ba93c2793a1ae79f560a0fc153056f3d39ce43.tar.gz
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
-rw-r--r--client/mysqladmin.c5
-rw-r--r--include/mysql_com.h12
-rw-r--r--libmysql/libmysql.c109
-rw-r--r--sql/password.c2
-rw-r--r--sql/sql_acl.cc351
-rw-r--r--sql/sql_acl.h52
-rw-r--r--sql/sql_parse.cc141
7 files changed, 436 insertions, 236 deletions
diff --git a/client/mysqladmin.c b/client/mysqladmin.c
index 925e67978bf..21169f1b526 100644
--- a/client/mysqladmin.c
+++ b/client/mysqladmin.c
@@ -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);
diff --git a/include/mysql_com.h b/include/mysql_com.h
index b8e78ee8f60..09e1db5aeb6 100644
--- a/include/mysql_com.h
+++ b/include/mysql_com.h
@@ -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,
diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c
index b253a7fa6f5..3f4dc295eb2 100644
--- a/libmysql/libmysql.c
+++ b/libmysql/libmysql.c
@@ -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);
+
}
diff --git a/sql/password.c b/sql/password.c
index b9eb6012354..98cac1e07d0 100644
--- a/sql/password.c
+++ b/sql/password.c
@@ -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
*****************************************************************************/
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
index d4ca8ed1bc7..3ebd3fdbd3b 100644
--- a/sql/sql_acl.cc
+++ b/sql/sql_acl.cc
@@ -33,52 +33,6 @@
#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
{
public:
@@ -105,6 +59,7 @@ static HASH acl_check_hosts, hash_tables;
static DYNAMIC_ARRAY acl_wild_hosts;
static hash_filo *acl_cache;
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 int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b);
static ulong get_sort(uint count,...);
@@ -148,7 +103,9 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
{
DBUG_RETURN(0); /* purecov: tested */
}
-
+
+ priv_version++; /* Priveleges updated */
+
/*
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)
/*
Get master privilges for user (priviliges for all tables).
Required before connecting to MySQL
+
+ as we have 2 stage handshake now we cache user not to lookup
+ it second time. At the second stage we do not lookup user in case
+ we already know it;
+
*/
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,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;
*priv_user=(char*) user;
bool password_correct=0;
+ ACL_USER *acl_user=NULL;
+
DBUG_ENTER("acl_getroot");
bzero(mqh,sizeof(USER_RESOURCES));
@@ -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 */
}
VOID(pthread_mutex_lock(&acl_cache->lock));
-
+
+
/*
Get possible access from user_list. This is or'ed to others not
fully specified
+
+ If we have cached user use it, in other case look it up.
*/
- for (uint i=0 ; i < acl_users.elements ; i++)
- {
- ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
- if (!acl_user->user || !strcmp(user,acl_user->user))
+
+ if (stage && (*cur_priv_version==priv_version))
+ acl_user=*hint_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 ||
- (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 (compare_hostname(&acl_user_search->host,host,ip))
+ {
+ /* Found mathing user */
+ acl_user=acl_user_search;
+ /* Store it as a cache */
+ *hint_user=acl_user;
+ *cur_priv_version=priv_version;
+ break;
+ }
+ }
+ }
+
+
+ /* Now we have acl_user found and may start our checks */
+
+ if (acl_user)
+ {
+ /* Password should present for both or absend for both */
+ if (!acl_user->password && !*password ||
+ (acl_user->password && *password))
+ {
+ /* Quick check and accept for empty passwords*/
+ if (!acl_user->password && !*password)
+ password_correct=1;
+ else /* Normal password presents */
+ {
+ /* 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;
}
- /* 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.
- */
- 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
+ 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 user not found password_correct will also be zero */
+ if (!password_correct)
+ goto unlock_and_exit;
+
+ /* OK. User found and password checked continue validation */
+
-#ifndef ALLOW_DOWNGRADE_OF_USERS
- break; // Wrong password breaks loop /* purecov: inspected */
-#endif
+#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.
+ */
+ 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));
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,
my_bool not_used __attribute__((unused)))
{
diff --git a/sql/sql_acl.h b/sql/sql_acl.h
index 66e7d0dad7c..cfab9bf22cf 100644
--- a/sql/sql_acl.h
+++ b/sql/sql_acl.h
@@ -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,
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 48a02b56c0b..bdb074b304d 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -188,14 +188,16 @@ end:
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);