diff options
author | unknown <peter@mysql.com> | 2002-11-30 16:31:58 +0300 |
---|---|---|
committer | unknown <peter@mysql.com> | 2002-11-30 16:31:58 +0300 |
commit | 05ba93c2793a1ae79f560a0fc153056f3d39ce43 (patch) | |
tree | 8cb72ffc1f46d5e546f302958453ce4a83d26d5e | |
parent | 14754ce141aa6a061a94fa668094b0602edd69c5 (diff) | |
download | mariadb-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.c | 5 | ||||
-rw-r--r-- | include/mysql_com.h | 12 | ||||
-rw-r--r-- | libmysql/libmysql.c | 109 | ||||
-rw-r--r-- | sql/password.c | 2 | ||||
-rw-r--r-- | sql/sql_acl.cc | 351 | ||||
-rw-r--r-- | sql/sql_acl.h | 52 | ||||
-rw-r--r-- | sql/sql_parse.cc | 141 |
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); |