diff --git a/include/ma_common.h b/include/ma_common.h index c52af5a1..aac4adef 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -75,8 +75,15 @@ struct st_mariadb_net_extension { unsigned char *mbuff, *mbuff_end, *mbuff_pos; }; +struct st_mariadb_session_state +{ + LIST *list, + *current; +}; + struct st_mariadb_extension { MA_CONNECTION_HANDLER *conn_hdlr; + struct st_mariadb_session_state session_state[SESSION_TRACK_TYPES]; }; #define OPT_HAS_EXT_VAL(a,key) \ diff --git a/include/ma_sys.h b/include/ma_sys.h index 4f750c35..8f47f69a 100644 --- a/include/ma_sys.h +++ b/include/ma_sys.h @@ -372,7 +372,7 @@ extern gptr _mymalloc(size_t uSize,const char *sFile, uint uLine, myf MyFlag); extern gptr _myrealloc(gptr pPtr,size_t uSize,const char *sFile, uint uLine, myf MyFlag); -extern gptr my_multi_malloc _VARARGS((myf MyFlags, ...)); +extern void *ma_multi_malloc(myf MyFlags, ...); extern void _myfree(gptr pPtr,const char *sFile,uint uLine, myf MyFlag); extern int _sanity(const char *sFile,unsigned int uLine); #ifndef TERMINATE diff --git a/include/mariadb_com.h b/include/mariadb_com.h index 57546798..5bf34545 100644 --- a/include/mariadb_com.h +++ b/include/mariadb_com.h @@ -155,6 +155,7 @@ enum enum_server_command #define CLIENT_PS_MULTI_RESULTS (1UL << 18) #define CLIENT_PLUGIN_AUTH (1UL << 19) #define CLIENT_CONNECT_ATTRS (1UL << 20) +#define CLIENT_SESSION_TRACKING (1UL << 23) #define CLIENT_PROGRESS (1UL << 29) /* client supports progress indicator */ #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) #define CLIENT_REMEMBER_OPTIONS (1UL << 31) @@ -190,6 +191,7 @@ enum enum_server_command CLIENT_SSL_VERIFY_SERVER_CERT |\ CLIENT_REMEMBER_OPTIONS |\ CLIENT_PLUGIN_AUTH |\ + CLIENT_SESSION_TRACKING |\ CLIENT_CONNECT_ATTRS) #define CLIENT_CAPABILITIES (CLIENT_MYSQL | \ @@ -200,6 +202,7 @@ enum enum_server_command CLIENT_PS_MULTI_RESULTS |\ CLIENT_PROTOCOL_41 |\ CLIENT_PLUGIN_AUTH |\ + CLIENT_SESSION_TRACKING |\ CLIENT_CONNECT_ATTRS) #define CLIENT_DEFAULT_FLAGS ((CLIENT_SUPPORTED_FLAGS & ~CLIENT_COMPRESS)\ @@ -217,6 +220,7 @@ enum enum_server_command #define SERVER_STATUS_METADATA_CHANGED 1024 #define SERVER_QUERY_WAS_SLOW 2048 #define SERVER_PS_OUT_PARAMS 4096 +#define SERVER_SESSION_STATE_CHANGED (1UL << 14) #define MYSQL_ERRMSG_SIZE 512 #define NET_READ_TIMEOUT 30 /* Timeout on read */ @@ -284,6 +288,22 @@ enum enum_mysql_set_option MYSQL_OPTION_MULTI_STATEMENTS_OFF }; +enum enum_session_state_type +{ + SESSION_TRACK_SYSTEM_VARIABLES= 0, + SESSION_TRACK_SCHEMA, + SESSION_TRACK_STATE_CHANGE, + /* currently not supported by MariaDB Server */ + SESSION_TRACK_GTIDS, + SESSION_TRACK_TRANSACTION_CHARACTERISTICS, + SESSION_TRACK_TRANSACTION_TYPE /* make sure that SESSION_TRACK_END always points + to last element of enum !! */ +}; + +#define SESSION_TRACK_BEGIN 0 +#define SESSION_TRACK_END SESSION_TRACK_TRANSACTION_TYPE +#define SESSION_TRACK_TYPES SESSION_TRACK_END + 1 + enum enum_field_types { MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG, MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE, diff --git a/include/mysql.h b/include/mysql.h index c8b3670f..fc306db6 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -660,6 +660,8 @@ int STDCALL mysql_read_query_result_start(my_bool *ret, MYSQL *mysql); int STDCALL mysql_read_query_result_cont(my_bool *ret, MYSQL *mysql, int status); +int STDCALL mysql_session_track_get_next(MYSQL *mysql, enum enum_session_state_type type, const char **data, size_t *length); +int STDCALL mysql_session_track_get_first(MYSQL *mysql, enum enum_session_state_type type, const char **data, size_t *length); int STDCALL mysql_stmt_prepare_start(int *ret, MYSQL_STMT *stmt,const char *query, size_t length); int STDCALL mysql_stmt_prepare_cont(int *ret, MYSQL_STMT *stmt, int status); int STDCALL mysql_stmt_execute_start(int *ret, MYSQL_STMT *stmt); diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index 789eed51..30d3cc60 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -91,6 +91,8 @@ SET(MARIADB_LIB_SYMBOLS mysql_send_query mysql_server_end mysql_server_init + mysql_session_track_get_next + mysql_session_track_get_first mysql_set_character_set mysql_set_local_infile_default mysql_set_local_infile_handler diff --git a/libmariadb/ma_alloc.c b/libmariadb/ma_alloc.c index b516179d..552f7049 100644 --- a/libmariadb/ma_alloc.c +++ b/libmariadb/ma_alloc.c @@ -151,3 +151,33 @@ char *ma_memdup_root(MA_MEM_ROOT *root, const char *str, size_t len) memcpy(pos,str,len); return pos; } + +void *ma_multi_malloc(myf myFlags, ...) +{ + va_list args; + char **ptr,*start,*res; + uint tot_length,length; + + va_start(args,myFlags); + tot_length=0; + while ((ptr=va_arg(args, char **))) + { + length=va_arg(args,uint); + tot_length+=ALIGN_SIZE(length); + } + va_end(args); + + if (!(start=(char *)malloc(tot_length))) + return 0; + + va_start(args,myFlags); + res=start; + while ((ptr=va_arg(args, char **))) + { + *ptr=res; + length=va_arg(args,uint); + res+=ALIGN_SIZE(length); + } + va_end(args); + return start; +} diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index c7410926..f6b9c552 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -36,6 +36,7 @@ #include #include #include +#include #ifdef HAVE_PWD_H #include @@ -1835,6 +1836,21 @@ void mysql_close_slow_part(MYSQL *mysql) } } +void ma_clear_session_state(MYSQL *mysql) +{ + uint i; + + if (!mysql || !mysql->extension) + return; + + for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++) + { + /* we acquired memory via ma_multi_alloc, so we don't need to free data */ + list_free(mysql->extension->session_state[i].list, 0); + } + memset(mysql->extension->session_state, 0, sizeof(struct st_mariadb_session_state) * SESSION_TRACK_TYPES); +} + void STDCALL mysql_close(MYSQL *mysql) { @@ -1855,6 +1871,7 @@ mysql_close(MYSQL *mysql) mysql_close_memory(mysql); mysql_close_options(mysql); + ma_clear_session_state(mysql); if (mysql->net.extension) free(mysql->net.extension); @@ -1914,14 +1931,124 @@ get_info: pos=(uchar*) mysql->net.read_pos; if ((field_count= net_field_length(&pos)) == 0) { + size_t item_len; mysql->affected_rows= net_field_length_ll(&pos); mysql->insert_id= net_field_length_ll(&pos); mysql->server_status=uint2korr(pos); pos+=2; mysql->warning_count=uint2korr(pos); pos+=2; - if (pos < mysql->net.read_pos+length && net_field_length(&pos)) + if (pos < mysql->net.read_pos+length && (item_len= net_field_length(&pos))) mysql->info=(char*) pos; + + /* check if server supports session tracking */ + if (mysql->server_capabilities & CLIENT_SESSION_TRACKING) + { + ma_clear_session_state(mysql); + pos+= item_len; + + if (mysql->server_status & SERVER_SESSION_STATE_CHANGED) + { + int i; + if (pos < mysql->net.read_pos + length) + { + LIST *session_item; + MYSQL_LEX_STRING *str= NULL; + enum enum_session_state_type si_type; + uchar *old_pos= pos; + size_t item_len= net_field_length(&pos); /* length for all items */ + + /* length was already set, so make sure that info will be zero terminated */ + if (mysql->info) + *old_pos= 0; + + while (item_len > 0) + { + size_t plen; + char *data; + old_pos= pos; + si_type= (enum enum_session_state_type)net_field_length(&pos); + switch(si_type) { + case SESSION_TRACK_SCHEMA: + case SESSION_TRACK_STATE_CHANGE: + case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: + case SESSION_TRACK_SYSTEM_VARIABLES: + net_field_length(&pos); /* ignore total length, item length will follow next */ + plen= net_field_length(&pos); + if (!ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &data, plen, + 0)) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return -1; + } + str->length= plen; + str->str= data; + memcpy(str->str, (char *)pos, plen); + pos+= plen; + session_item->data= str; + mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item); + + /* in case schema has changed, we have to update mysql->db */ + if (si_type == SESSION_TRACK_SCHEMA) + { + free(mysql->db); + mysql->db= malloc(plen + 1); + memcpy(mysql->db, str->str, plen); + mysql->db[plen]= 0; + } + else if (si_type == SESSION_TRACK_SYSTEM_VARIABLES) + { + my_bool set_charset= 0; + /* make sure that we update charset in case it has changed */ + if (!strncmp(str->str, "character_set_client", str->length)) + set_charset= 1; + plen= net_field_length(&pos); + if (!ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &data, plen, + 0)) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return -1; + } + str->length= plen; + str->str= data; + memcpy(str->str, (char *)pos, plen); + pos+= plen; + session_item->data= str; + mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item); + if (set_charset && + strncmp(mysql->charset->csname, str->str, str->length) != 0) + { + char cs_name[64]; + MARIADB_CHARSET_INFO *cs_info; + memcpy(cs_name, str->str, str->length); + cs_name[str->length]= 0; + if ((cs_info = (MARIADB_CHARSET_INFO *)mysql_find_charset_name(cs_name))) + mysql->charset= cs_info; + } + } + break; + default: + /* not supported yet */ + plen= net_field_length(&pos); + pos+= plen; + break; + } + item_len-= (pos - old_pos); + } + } + for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++) + { + mysql->extension->session_state[i].list= list_reverse(mysql->extension->session_state[i].list); + mysql->extension->session_state[i].current= mysql->extension->session_state[i].list; + } + } + } return(0); } if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ @@ -1948,6 +2075,28 @@ get_info: return(0); } +int STDCALL mysql_session_track_get_next(MYSQL *mysql, enum enum_session_state_type type, + const char **data, size_t *length) +{ + MYSQL_LEX_STRING *str; + if (!mysql->extension->session_state[type].current) + return 1; + + str= (MYSQL_LEX_STRING *)mysql->extension->session_state[type].current->data; + mysql->extension->session_state[type].current= mysql->extension->session_state[type].current->next; + + *data= str->str ? str->str : NULL; + *length= str->str ? str->length : 0; + return 0; +} + +int STDCALL mysql_session_track_get_first(MYSQL *mysql, enum enum_session_state_type type, + const char **data, size_t *length) +{ + mysql->extension->session_state[type].current= mysql->extension->session_state[type].list; + return mysql_session_track_get_next(mysql, type, data, length); +} + my_bool STDCALL mysql_read_query_result(MYSQL *mysql) { diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c index 416f092e..2f29a2ef 100644 --- a/libmariadb/secure/schannel.c +++ b/libmariadb/secure/schannel.c @@ -312,9 +312,9 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) Cred.cCreds = 1; Cred.paCred = &sctx->client_cert_ctx; } + Cred.grbitEnabledProtocols= 0; if (mysql->options.extension && mysql->options.extension->tls_version) { - Cred.grbitEnabledProtocols= 0; if (strstr("TLSv1.0", mysql->options.extension->tls_version)) Cred.grbitEnabledProtocols|= SP_PROT_TLS1_0; if (strstr("TLSv1.1", mysql->options.extension->tls_version)) @@ -322,10 +322,6 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) if (strstr("TLSv1.2", mysql->options.extension->tls_version)) Cred.grbitEnabledProtocols|= SP_PROT_TLS1_2; } - else - Cred.grbitEnabledProtocols = SP_PROT_TLS1_0 | - SP_PROT_TLS1_1 | - SP_PROT_TLS1_2; if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) diff --git a/unittest/libmariadb/connection.c b/unittest/libmariadb/connection.c index 3dffdd42..0169d76c 100644 --- a/unittest/libmariadb/connection.c +++ b/unittest/libmariadb/connection.c @@ -912,7 +912,70 @@ static int test_get_options(MYSQL *my) return OK; } +static int test_sess_track_db(MYSQL *mysql) +{ + int rc; + const char *data; + size_t len; + + if (!(mysql->server_capabilities & CLIENT_SESSION_TRACKING)) + { + diag("Server doesn't support session tracking (cap=%u)", mysql->server_capabilities); + return SKIP; + } + + rc= mysql_query(mysql, "USE mysql"); + check_mysql_rc(rc, mysql); + FAIL_IF(strcmp(mysql->db, "mysql"), "Expected new schema 'mysql'"); + + FAIL_IF(mysql_session_track_get_first(mysql, SESSION_TRACK_SCHEMA, &data, &len), + "session_track_get_first failed"); + FAIL_IF(strncmp(data, "mysql", len), "Expected new schema 'mysql'"); + + rc= mysql_query(mysql, "USE testc"); + check_mysql_rc(rc, mysql); + FAIL_IF(strcmp(mysql->db, "testc"), "Expected new schema 'testc'"); + + FAIL_IF(mysql_session_track_get_first(mysql, SESSION_TRACK_SCHEMA, &data, &len), + "session_track_get_first failed"); + FAIL_IF(strncmp(data, "testc", len), "Expected new schema 'testc'"); + + rc= mysql_query(mysql, "SET NAMES utf8"); + check_mysql_rc(rc, mysql); + FAIL_IF(strcmp(mysql->charset->csname, "utf8"), "Expected charset 'utf8'"); + if (!mysql_session_track_get_first(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &len)) + do { + printf("# SESSION_TRACK_VARIABLES: %*.*s\n", len, len, data); + } while (!mysql_session_track_get_next(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &len)); + + rc= mysql_query(mysql, "SET NAMES latin1"); + check_mysql_rc(rc, mysql); + FAIL_IF(strcmp(mysql->charset->csname, "latin1"), "Expected charset 'latin1'"); + + rc= mysql_query(mysql, "DROP PROCEDURE IF EXISTS p1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE PROCEDURE p1() " + "BEGIN " + "SET @@autocommit=0; " + "SET NAMES utf8; " + "SET session auto_increment_increment=2; " + "END "); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CALL p1()"); + check_mysql_rc(rc, mysql); + + if (!mysql_session_track_get_first(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &len)) + do { + printf("# SESSION_TRACK_VARIABLES: %*.*s\n", len, len, data); + } while (!mysql_session_track_get_next(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &len)); + + return OK; +} + struct my_tests_st my_tests[] = { + {"test_sess_track_db", test_sess_track_db, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_get_options", test_get_options, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_wrong_bind_address", test_wrong_bind_address, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_bind_address", test_bind_address, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},