Commit 54ff0efe authored by peter@mysql.com's avatar peter@mysql.com

SCRUM: Secure auth

Implement mysql_change_user
Get rid of double user search at authentication
Some cleanups
parent 08f51eae
...@@ -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
*****************************************************************************/ *****************************************************************************/
......
This diff is collapsed.
...@@ -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