From 6632ee979f1de8eef5e942eebd1da52dd0d6aef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Tue, 24 Mar 2015 09:55:52 +0000 Subject: [PATCH] Basic implementation of COM_CHANGE_USER client side (issue #187) --- include/MySQL_Protocol.h | 4 + include/mysql_session.h | 5 ++ lib/MySQL_Protocol.cpp | 158 +++++++++++++++++++++++++++++++++++++++ lib/MySQL_Session.cpp | 67 ++++++++++++++++- 4 files changed, 231 insertions(+), 3 deletions(-) diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h index c8ccfa332..a46ebcb58 100644 --- a/include/MySQL_Protocol.h +++ b/include/MySQL_Protocol.h @@ -58,6 +58,9 @@ class MySQL_Protocol { bool generate_COM_RESET_CONNECTION(bool send, void **ptr, unsigned int *len); bool generate_COM_CHANGE_USER(bool send, void **ptr, unsigned int *len); + bool generate_pkt_auth_switch_request(bool send, void **ptr, unsigned int *len); + bool process_pkt_auth_swich_response(unsigned char *pkt, unsigned int len); + // bool generate_pkt_column_count(MySQL_Data_Stream *myds, bool send, void **ptr, unsigned int *len, uint8_t sequence_id, uint64_t count); bool generate_pkt_column_count(bool send, void **ptr, unsigned int *len, uint8_t sequence_id, uint64_t count); // bool generate_pkt_field(MySQL_Data_Stream *myds, bool send, void **ptr, unsigned int *len, uint8_t sequence_id, char *schema, char *table, char *org_table, char *name, char *org_name, uint16_t charset, uint32_t column_length, uint8_t type, uint16_t flags, uint8_t decimals, bool field_list, uint64_t defvalue_length, char *defvalue); @@ -83,5 +86,6 @@ class MySQL_Protocol { bool process_pkt_handshake_response(unsigned char *pkt, unsigned int len); bool process_pkt_initial_handshake(unsigned char *pkt, unsigned int len); bool process_pkt_COM_QUERY(unsigned char *pkt, unsigned int len); + bool process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned int len); }; #endif /* __CLASS_MYSQL_PROTOCOL_H */ diff --git a/include/mysql_session.h b/include/mysql_session.h index b4a479abc..acf317dde 100644 --- a/include/mysql_session.h +++ b/include/mysql_session.h @@ -44,12 +44,17 @@ class MySQL_Session void handler___status_CONNECTING_SERVER___STATE_NOT_CONNECTED(PtrSize_t *); void handler___status_CONNECTING_SERVER___STATE_CLIENT_HANDSHAKE(PtrSize_t *, bool *); void handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *, bool *); + + void handler___status_CHANGING_USER_CLIENT___STATE_CLIENT_HANDSHAKE(PtrSize_t *, bool *); + void handler___status_CONNECTING_CLIENT___STATE_SSL_INIT(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_FIELD_LIST(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PING(PtrSize_t *); + void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_CHANGE_USER(PtrSize_t *, bool *); + void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_PREPARE(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_EXECUTE(PtrSize_t *); void handler___status_WAITING_SERVER_DATA___STATE_READING_COM_STMT_PREPARE_RESPONSE(PtrSize_t *); diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index a102d62fa..28b7d2c51 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -1276,6 +1276,38 @@ bool MySQL_Protocol::generate_COM_CHANGE_USER(bool send, void **ptr, unsigned in return true; } +bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, unsigned int *len) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Generating auth switch request pkt\n"); + mysql_hdr myhdr; + myhdr.pkt_id=1; + myhdr.pkt_length=1 // fe + + (strlen("mysql_native_password")+1) + + 20 // scramble + + 1; // 00 + unsigned int size=myhdr.pkt_length+sizeof(mysql_hdr); + unsigned char *_ptr=(unsigned char *)l_alloc0(size); + memcpy(_ptr, &myhdr, sizeof(mysql_hdr)); + int l; + l=sizeof(mysql_hdr); + _ptr[l]=0xfe; l++; //0xfe + + memcpy(_ptr+l,"mysql_native_password",strlen("mysql_native_password")); + l+=strlen("mysql_native_password"); + _ptr[l]=0x00; l++; + memcpy(_ptr+l, (*myds)->myconn->scramble_buff+0, 20); l+=20; + _ptr[l]=0x00; //l+=1; //0x00 + if (send==true) { + (*myds)->PSarrayOUT->add((void *)_ptr,size); + (*myds)->DSS=STATE_SERVER_HANDSHAKE; + (*myds)->sess->status=CONNECTING_CLIENT; + } + if (len) { *len=size; } + if (ptr) { *ptr=(void *)_ptr; } +#ifdef DEBUG + if (dump_pkt) { __dump_pkt(__func__,_ptr,size); } +#endif + return true; +} //bool MySQL_Protocol::generate_pkt_initial_handshake(MySQL_Data_Stream *myds, bool send, void **ptr, unsigned int *len) { bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsigned int *len) { @@ -1532,6 +1564,132 @@ exit_process_pkt_initial_handshake: } +bool MySQL_Protocol::process_pkt_auth_swich_response(unsigned char *pkt, unsigned int len) { + bool ret=false; + char *password=NULL; + + if (len!=sizeof(mysql_hdr)+20) { + return ret; + } + mysql_hdr hdr; + memcpy(&hdr,pkt,sizeof(mysql_hdr)); + int default_hostgroup=-1; + bool transaction_persistent; + bool _ret_use_ssl=false; + unsigned char pass[128]; + memset(pass,0,128); + pkt+=sizeof(mysql_hdr); + memcpy(pass, pkt, 20); + char reply[SHA_DIGEST_LENGTH+1]; + reply[SHA_DIGEST_LENGTH]='\0'; + password=GloMyAuth->lookup((char *)userinfo->username, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &transaction_persistent); + if (password==NULL) { + ret=false; + } else { +// if (pass_len==0 && strlen(password)==0) { +// ret=true; +// } else { + proxy_scramble(reply, (*myds)->myconn->scramble_buff, password); + if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) { + ret=true; + } +// } +// if (_ret_use_ssl==true) { +// ret=false; +// } + } + return ret; +} + + +bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned int len) { + // FIXME: very buggy function, it doesn't perform any real check + bool ret=false; + int cur=sizeof(mysql_hdr); + unsigned char *user=NULL; + char *password=NULL; + char *db=NULL; + mysql_hdr hdr; + memcpy(&hdr,pkt,sizeof(mysql_hdr)); + int default_hostgroup=-1; + bool transaction_persistent; + bool _ret_use_ssl=false; + cur++; + user=pkt+cur; + cur+=strlen((const char *)user); + cur++; + unsigned char pass_len=pkt[cur]; + cur++; + unsigned char pass[128]; + memset(pass,0,128); + //pkt+=sizeof(mysql_hdr); + memcpy(pass, pkt+cur, pass_len); + char reply[SHA_DIGEST_LENGTH+1]; + reply[SHA_DIGEST_LENGTH]='\0'; + cur+=pass_len; + db=(char *)pkt+cur; + password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &transaction_persistent); + (*myds)->sess->default_hostgroup=default_hostgroup; + (*myds)->sess->transaction_persistent=transaction_persistent; + if (password==NULL) { + ret=false; + } else { + if (pass_len==0 && strlen(password)==0) { + ret=true; + } else { + proxy_scramble(reply, (*myds)->myconn->scramble_buff, password); + if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) { + ret=true; + } + } + if (_ret_use_ssl==true) { + // if we reached here, use_ssl is false , but _ret_use_ssl is true + // it means that a client is required to use SSL , but it is not + ret=false; + } + } + if (userinfo->username) free(userinfo->username); + if (userinfo->password) free(userinfo->password); + if (ret==true) { + //(*myds)->myconn->options.max_allowed_pkt=max_pkt; + (*myds)->DSS=STATE_CLIENT_HANDSHAKE; + + userinfo->username=strdup((const char *)user); + userinfo->password=strdup((const char *)password); + if (db) userinfo->set_schemaname(db,strlen(db)); + } else { + // we always duplicate username and password, or crashes happen + userinfo->username=strdup((const char *)user); + /*if (pass_len) */ userinfo->password=strdup((const char *)""); + } + //if (password) free(password); + if (password) l_free_string(password); + +/* + //cur+=1; +// g_free(sess->mysql_username); + free(userinfo->username); + //unsigned char *_ptr=pkt+cur; + user=pkt+cur; + //sess->mysql_username=g_strdup(ptr); + cur+=strlen((const char *)user); + cur+=2; + //memcpy(sess->scramble_buf,mypkt->data+cur,20); + memcpy((*myds)->myconn->scramble_buff, pkt+cur, 20); + cur+=20; + //g_free(sess->mysql_schema_cur); + //ptr=mypkt->data+cur; + db=pkt+cur; + //sess->mysql_schema_cur=g_strdup(ptr); + userinfo->username=strdup((const char *)user); + // userinfo->password=strdup((const char *)password); + if (strlen((char *)db)) userinfo->set_schemaname((char *)db,strlen((char *)db)); // FIXME: buggy + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "CHANGE USER: Username %s , schema %s\n" , userinfo->username, userinfo->schemaname); +*/ +// ret=true; + return ret; +} + //bool MySQL_Protocol::process_pkt_handshake_response(MySQL_Data_Stream *myds, unsigned char *pkt, unsigned int len) { bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned int len) { bool ret=false; diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 5acbf8416..2f3912370 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -302,7 +302,17 @@ int MySQL_Session::handler() { //prot.parse_mysql_pkt(&pkt,client_myds); switch (status) { - +/* + case CHANGING_USER_CLIENT: + switch (client_myds->DSS) { + case STATE_CLIENT_HANDSHAKE: + handler___status_CHANGING_USER_CLIENT___STATE_CLIENT_HANDSHAKE(&pkt, &wrong_pass); + break; + default: + assert(0); + } + break; +*/ case CONNECTING_CLIENT: switch (client_myds->DSS) { case STATE_SERVER_HANDSHAKE: @@ -365,8 +375,8 @@ int MySQL_Session::handler() { l_free(pkt.size,pkt.ptr); } break; - case _MYSQL_COM_STMT_PREPARE: - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_PREPARE(&pkt); + case _MYSQL_COM_CHANGE_USER: + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_CHANGE_USER(&pkt, &wrong_pass); break; case _MYSQL_COM_STMT_EXECUTE: handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_EXECUTE(&pkt); @@ -1065,6 +1075,28 @@ void MySQL_Session::handler___status_CONNECTING_SERVER___STATE_CLIENT_HANDSHAKE( +void MySQL_Session::handler___status_CHANGING_USER_CLIENT___STATE_CLIENT_HANDSHAKE(PtrSize_t *pkt, bool *wrong_pass) { + // FIXME: no support for SSL yet + if ( + client_myds->myprot.process_pkt_auth_swich_response((unsigned char *)pkt->ptr,pkt->size)==true + ) { + l_free(pkt->size,pkt->ptr); + client_myds->myprot.generate_pkt_OK(true,NULL,NULL,2,0,0,0,0,NULL); + status=WAITING_CLIENT_DATA; + client_myds->DSS=STATE_SLEEP; + } else { + l_free(pkt->size,pkt->ptr); + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Wrong credentials for frontend: disconnecting\n"); + *wrong_pass=true; + // FIXME: this should become close connection + client_myds->setDSS_STATE_QUERY_SENT_NET(); + char *_s=(char *)malloc(strlen(client_myds->myconn->userinfo->username)+100); + sprintf(_s,"Access denied for user '%s' (using password: %s)", client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO")); + client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,2,1045,(char *)"#28000", _s); + free(_s); + } +} + void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *pkt, bool *wrong_pass) { if ( (client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size)==true) @@ -1314,6 +1346,35 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C client_myds->DSS=STATE_SLEEP; } +void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_CHANGE_USER(PtrSize_t *pkt, bool *wrong_pass) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_CHANGE_USER packet\n"); + if (admin==false) { + if (client_myds->myprot.process_pkt_COM_CHANGE_USER((unsigned char *)pkt->ptr, pkt->size)==true) { + l_free(pkt->size,pkt->ptr); + //client_myds->myprot.generate_pkt_auth_switch_request(true,NULL,NULL); + //client_myds->DSS=STATE_CLIENT_HANDSHAKE; + //status=CHANGING_USER_CLIENT; + client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,0,0,NULL); + client_myds->DSS=STATE_SLEEP; + status=WAITING_CLIENT_DATA; + wrong_pass=false; + } else { + l_free(pkt->size,pkt->ptr); + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Wrong credentials for frontend: disconnecting\n"); + *wrong_pass=true; + // FIXME: this should become close connection + client_myds->setDSS_STATE_QUERY_SENT_NET(); + char *_s=(char *)malloc(strlen(client_myds->myconn->userinfo->username)+100); + sprintf(_s,"Access denied for user '%s' (using password: %s)", client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO")); + client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,2,1045,(char *)"#28000", _s); + free(_s); + } + } else { + //FIXME: send an error message saying "not supported" or disconnect + l_free(pkt->size,pkt->ptr); + } +} + void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED__get_connection() { // Get a MySQL Connection