static int test1() { GWBUF *buffer; char *(sql[100]); int result, length, residual; /* Poll tests */ ss_dfprintf(stderr, "testmodutil : Rudimentary tests."); buffer = gwbuf_alloc(100); ss_info_dassert(0 == modutil_is_SQL(buffer), "Default buffer should be diagnosed as not SQL"); /* There would ideally be some straightforward way to create a SQL buffer? */ ss_dfprintf(stderr, "\t..done\nExtract SQL from buffer"); ss_info_dassert(0 == modutil_extract_SQL(buffer, sql, &length), "Default buffer should fail"); ss_dfprintf(stderr, "\t..done\nExtract SQL from buffer different way?"); ss_info_dassert(0 == modutil_MySQL_Query(buffer, sql, &length, &residual), "Default buffer should fail"); ss_dfprintf(stderr, "\t..done\nReplace SQL in buffer"); ss_info_dassert(0 == modutil_replace_SQL(buffer, "select * from some_table;"), "Default buffer should fail"); ss_dfprintf(stderr, "\t..done\nTidy up."); gwbuf_free(buffer); ss_dfprintf(stderr, "\t..done\n"); return 0; }
/** * Replace the contents of a GWBUF with the new SQL statement passed as a text string. * The routine takes care of the modification needed to the MySQL packet, * returning a GWBUF chain that can be used to send the data to a MySQL server * * @param orig The original request in a GWBUF * @param sql The SQL text to replace in the packet * @return A newly formed GWBUF containing the MySQL packet. */ GWBUF * modutil_replace_SQL(GWBUF *orig, char *sql) { unsigned char *ptr; int length, newlength; GWBUF *addition; if (!modutil_is_SQL(orig)) return NULL; ptr = GWBUF_DATA(orig); length = *ptr++; length += (*ptr++ << 8); length += (*ptr++ << 16); ptr += 2; // Skip sequence id and COM_QUERY byte newlength = strlen(sql); if (length - 1 == newlength) { /* New SQL is the same length as old */ memcpy(ptr, sql, newlength); } else if (length - 1 > newlength) { /* New SQL is shorter */ memcpy(ptr, sql, newlength); GWBUF_RTRIM(orig, (length - 1) - newlength); ptr = GWBUF_DATA(orig); *ptr++ = (newlength + 1) & 0xff; *ptr++ = ((newlength + 1) >> 8) & 0xff; *ptr++ = ((newlength + 1) >> 16) & 0xff; }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once applied the * query should normally be passed to the downstream component * (filter or router) in the filter chain. * * If the regular expressed configured in the match parameter of the * filter definition matches the SQL text then add the hint * "Route to named server" with the name defined in the server parameter * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { LAG_INSTANCE *my_instance = (LAG_INSTANCE *)instance; LAG_SESSION *my_session = (LAG_SESSION *)session; char *sql; time_t now = time(NULL); if (modutil_is_SQL(queue)) { if (queue->next != NULL) { queue = gwbuf_make_contiguous(queue); } if(!query_is_parsed(queue)) { parse_query(queue); } if(query_classifier_get_operation(queue) & (QUERY_OP_DELETE|QUERY_OP_INSERT|QUERY_OP_UPDATE)) { if((sql = modutil_get_SQL(queue)) != NULL) { if(my_instance->nomatch == NULL||(my_instance->nomatch && regexec(&my_instance->nore,sql,0,NULL,0) != 0)) { if(my_instance->match == NULL|| (my_instance->match && regexec(&my_instance->re,sql,0,NULL,0) == 0)) { my_session->hints_left = my_instance->count; my_session->last_modification = now; my_instance->stats.n_modified++; } } free(sql); } } else if(my_session->hints_left > 0) { queue->hint = hint_create_route(queue->hint, HINT_ROUTE_TO_MASTER, NULL); my_session->hints_left--; my_instance->stats.n_add_count++; } else if(difftime(now,my_session->last_modification) < my_instance->time) { queue->hint = hint_create_route(queue->hint, HINT_ROUTE_TO_MASTER, NULL); my_instance->stats.n_add_time++; } } return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once applied the * query shoudl normally be passed to the downstream component * (filter or router) in the filter chain. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { TEST_SESSION *my_session = (TEST_SESSION *)session; if (modutil_is_SQL(queue)) my_session->count++; return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }
/** * Extract the SQL portion of a COM_QUERY packet * * NB This sets *sql to point into the packet and does not * allocate any new storage. The string pointed to by *sql is * not NULL terminated. * * This routine is very simplistic and does not deal with SQL text * that spans multiple buffers. * * The length returned is the complete length of the SQL, which may * be larger than the amount of data in this packet. * * @param buf The packet buffer * @param sql Pointer that is set to point at the SQL data * @param length Length of the SQL query data * @return True if the packet is a COM_QUERY packet */ int modutil_extract_SQL(GWBUF *buf, char **sql, int *length) { unsigned char *ptr; if (!modutil_is_SQL(buf)) return 0; ptr = GWBUF_DATA(buf); *length = *ptr++; *length += (*ptr++ << 8); *length += (*ptr++ << 16); ptr += 2; // Skip sequence id and COM_QUERY byte *length = *length - 1; *sql = (char *)ptr; return 1; }
/** * Extract the SQL portion of a COM_QUERY packet * * NB This sets *sql to point into the packet and does not * allocate any new storage. The string pointed to by *sql is * not NULL terminated. * * The number of bytes pointed to *sql is returned in *length * * The remaining number of bytes required for the complete query string * are returned in *residual * * @param buf The packet buffer * @param sql Pointer that is set to point at the SQL data * @param length Length of the SQL query data pointed to by sql * @param residual Any remain part of the query in future packets * @return True if the packet is a COM_QUERY packet */ int modutil_MySQL_Query(GWBUF *buf, char **sql, int *length, int *residual) { unsigned char *ptr; if (!modutil_is_SQL(buf)) return 0; ptr = GWBUF_DATA(buf); *residual = *ptr++; *residual += (*ptr++ << 8); *residual += (*ptr++ << 16); ptr += 2; // Skip sequence id and COM_QUERY byte *residual = *residual - 1; *length = GWBUF_LENGTH(buf) - 5; *residual -= *length; *sql = (char *)ptr; return 1; }
/** * Calculate the length of MySQL packet and how much is missing from the GWBUF * passed as parameter. * * This routine assumes that there is only one MySQL packet in the buffer. * * @param buf buffer list including the query, may consist of * multiple buffers * @param nbytes_missing pointer to missing bytecount * * @return the length of MySQL packet and writes missing bytecount to * nbytes_missing. */ int modutil_MySQL_query_len( GWBUF* buf, int* nbytes_missing) { int len; int buflen; if (!modutil_is_SQL(buf)) { len = 0; goto retblock; } len = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf)); *nbytes_missing = len-1; buflen = gwbuf_length(buf); *nbytes_missing -= buflen-5; retblock: return len; }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the * query is passed to the downstream component * (filter or router) in the filter chain. * * The function checks whether required logging trigger conditions are met and if so, * tries to extract a SQL query out of the query buffer, canonize the query, add * a timestamp to it and publish the resulting string on the exchange. * The message is tagged with an unique identifier and the clientReply will * use the same identifier for the reply from the backend to form a query-reply pair. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char *ptr, t_buf[128], *combined,*canon_q,*sesshost,*sessusr; bool success = false, src_ok = false,schema_ok = false,obj_ok = false; int length, i, j,dbcount = 0; char** sesstbls; unsigned int plen = 0; amqp_basic_properties_t *prop; /**The user is changing databases*/ if(*((char*)(queue->start + 4)) == 0x02){ if(my_session->db){ free(my_session->db); } plen = pktlen(queue->start); my_session->db = calloc(plen,sizeof(char)); memcpy(my_session->db,queue->start + 5,plen - 1); } if(modutil_is_SQL(queue)){ /**Parse the query*/ if (!query_is_parsed(queue)){ success = parse_query(queue); } if(!success){ skygw_log_write(LOGFILE_ERROR,"Error: Parsing query failed."); goto send_downstream; } if(!my_instance->log_all){ if(!skygw_is_real_query(queue)){ goto send_downstream; } } if(my_instance->trgtype == TRG_ALL){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_ALL"); schema_ok = true; src_ok = true; obj_ok = true; goto validate_triggers; } if(my_instance->trgtype & TRG_SOURCE && my_instance->src_trg){ if(session_isvalid(my_session->session)){ sessusr = session_getUser(my_session->session); sesshost = session_get_remote(my_session->session); /**Username was configured*/ if(my_instance->src_trg->usize > 0){ for(i = 0;i<my_instance->src_trg->usize;i++){ if(strcmp(my_instance->src_trg->user[i],sessusr) == 0) { skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: user: %s = %s",my_instance->src_trg->user[i],sessusr); src_ok = true; break; } } } /**If username was not matched, try to match hostname*/ if(!src_ok && my_instance->src_trg->hsize > 0){ for(i = 0;i<my_instance->src_trg->hsize;i++){ if(strcmp(my_instance->src_trg->host[i],sesshost) == 0) { skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: host: %s = %s",my_instance->src_trg->host[i],sesshost); src_ok = true; break; } } } } if(src_ok && !my_instance->strict_logging){ schema_ok = true; obj_ok = true; goto validate_triggers; } }else{ src_ok = true; } if(my_instance->trgtype & TRG_SCHEMA && my_instance->shm_trg){ int tbsz = 0,z; char** tblnames = skygw_get_table_names(queue,&tbsz,true); char* tmp; bool all_remotes = true; for(z = 0;z<tbsz;z++){ if((tmp = strchr(tblnames[z],'.')) != NULL){ char *lasts; tmp = strtok_r(tblnames[z],".",&lasts); for(i = 0; i<my_instance->shm_trg->size; i++){ if(strcmp(tmp,my_instance->shm_trg->objects[i]) == 0){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",tmp,my_instance->shm_trg->objects[i]); schema_ok = true; break; } } }else{ all_remotes = false; } free(tblnames[z]); } free(tblnames); if(!schema_ok && !all_remotes && my_session->db && strlen(my_session->db)>0){ for(i = 0; i<my_instance->shm_trg->size; i++){ if(strcmp(my_session->db,my_instance->shm_trg->objects[i]) == 0){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",my_session->db,my_instance->shm_trg->objects[i]); schema_ok = true; break; } } } if(schema_ok && !my_instance->strict_logging){ src_ok = true; obj_ok = true; goto validate_triggers; } }else{ schema_ok = true; } if(my_instance->trgtype & TRG_OBJECT && my_instance->obj_trg){ sesstbls = skygw_get_table_names(queue,&dbcount,false); for(j = 0; j<dbcount; j++){ char* tbnm = NULL; if((strchr(sesstbls[j],'.')) != NULL){ char *lasts; tbnm = strtok_r(sesstbls[j],".",&lasts); tbnm = strtok_r(NULL,".",&lasts); }else{ tbnm = sesstbls[j]; } for(i = 0; i<my_instance->obj_trg->size; i++){ if(!strcmp(tbnm,my_instance->obj_trg->objects[i])){ obj_ok = true; skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_OBJECT: %s = %s",my_instance->obj_trg->objects[i],sesstbls[j]); break; } } } if(dbcount > 0){ for(j = 0; j<dbcount; j++){ free(sesstbls[j]); } free(sesstbls); dbcount = 0; } if(obj_ok && !my_instance->strict_logging){ src_ok = true; schema_ok = true; goto validate_triggers; } }else{ obj_ok = true; } validate_triggers: if(src_ok&&schema_ok&&obj_ok){ /** * Something matched the trigger, log the query */ skygw_log_write_flush(LOGFILE_TRACE,"Routing message to: %s:%d %s as %s/%s, exchange: %s<%s> key:%s queue:%s", my_instance->hostname,my_instance->port, my_instance->vhost,my_instance->username, my_instance->password,my_instance->exchange, my_instance->exchange_type,my_instance->key, my_instance->queue); if(my_session->uid == NULL){ my_session->uid = calloc(33,sizeof(char)); if(!my_session->uid){ skygw_log_write(LOGFILE_ERROR,"Error : Out of memory."); }else{ genkey(my_session->uid,32); } } if (queue->next != NULL) { queue = gwbuf_make_contiguous(queue); } if(modutil_extract_SQL(queue, &ptr, &length)){ my_session->was_query = true; if((prop = malloc(sizeof(amqp_basic_properties_t)))){ prop->_flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop->content_type = amqp_cstring_bytes("text/plain"); prop->delivery_mode = AMQP_DELIVERY_PERSISTENT; prop->correlation_id = amqp_cstring_bytes(my_session->uid); prop->message_id = amqp_cstring_bytes("query"); } if(success){ /**Try to convert to a canonical form and use the plain query if unsuccessful*/ if((canon_q = skygw_get_canonical(queue)) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error: Cannot form canonical query."); } } memset(t_buf,0,128); sprintf(t_buf, "%lu|",(unsigned long)time(NULL)); int qlen = strnlen(canon_q,length) + strnlen(t_buf,128); if((combined = malloc((qlen+1)*sizeof(char))) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error: Out of memory"); } strcpy(combined,t_buf); strncat(combined,canon_q,length); pushMessage(my_instance,prop,combined); free(canon_q); } } /** Pass the query downstream */ } send_downstream: return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the * query is passed to the downstream component * (filter or router) in the filter chain. * * The Luafilter calls the routeQuery functions of both the session and the global script. * The query is passed as a string parameter to the routeQuery Lua function and * the return values of the session specific function, if any were returned, * are interpreted. If the first value is bool, it is interpreted as a decision * whether to route the query or to send an error packet to the client. * If it is a string, the current query is replaced with the return value and * the query will be routed. If nil is returned, the query is routed normally. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { LUA_SESSION *my_session = (LUA_SESSION *) session; LUA_INSTANCE *my_instance = (LUA_INSTANCE *) instance; DCB* dcb = my_session->session->client_dcb; char *fullquery = NULL, *ptr; bool route = true; GWBUF* forward = queue; int rc = 0; if (modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue)) { fullquery = modutil_get_SQL(queue); if (fullquery && my_session->lua_state) { spinlock_acquire(&my_session->lock); lua_getglobal(my_session->lua_state, "routeQuery"); lua_pushlstring(my_session->lua_state, fullquery, strlen(fullquery)); if (lua_pcall(my_session->lua_state, 1, 1, 0)) { MXS_ERROR("luafilter: Session scope call to 'routeQuery' failed: '%s'.", lua_tostring(my_session->lua_state, -1)); } else if (lua_gettop(my_session->lua_state)) { if (lua_isstring(my_session->lua_state, -1)) { if (forward) { gwbuf_free(forward); } forward = modutil_create_query((char*) lua_tostring(my_session->lua_state, -1)); } else if (lua_isboolean(my_session->lua_state, -1)) { route = lua_toboolean(my_session->lua_state, -1); } } spinlock_release(&my_session->lock); } if (my_instance->global_lua_state) { spinlock_acquire(&my_instance->lock); lua_getglobal(my_instance->global_lua_state, "routeQuery"); lua_pushlstring(my_instance->global_lua_state, fullquery, strlen(fullquery)); if (lua_pcall(my_instance->global_lua_state, 1, 0, 0)) { MXS_ERROR("luafilter: Global scope call to 'routeQuery' failed: '%s'.", lua_tostring(my_session->lua_state, -1)); } else if (lua_gettop(my_instance->global_lua_state)) { if (lua_isstring(my_instance->global_lua_state, -1)) { if (forward) { gwbuf_free(forward); } forward = modutil_create_query((char*) lua_tostring(my_instance->global_lua_state, -1)); } else if (lua_isboolean(my_instance->global_lua_state, -1)) { route = lua_toboolean(my_instance->global_lua_state, -1); } } spinlock_release(&my_instance->lock); } free(fullquery); } if (!route) { gwbuf_free(queue); GWBUF* err = modutil_create_mysql_err_msg(1, 0, 1045, "28000", "Access denied."); rc = dcb->func.write(dcb, err); } else { rc = my_session->down.routeQuery(my_session->down.instance, my_session->down.session, forward); } return rc; }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the * query is passed to the downstream component * (filter or router) in the filter chain. * * The function tries to extract a SQL query out of the query buffer, * adds a timestamp to it and publishes the resulting string on the exchange. * The message is tagged with an unique identifier and the clientReply will * use the same identifier for the reply from the backend. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char *ptr, t_buf[128], *combined; int length, err_code = AMQP_STATUS_OK; amqp_basic_properties_t prop; spinlock_acquire(my_instance->rconn_lock); if(my_instance->conn_stat != AMQP_STATUS_OK){ if(difftime(time(NULL),my_instance->last_rconn) > my_instance->rconn_intv){ my_instance->last_rconn = time(NULL); if(init_conn(my_instance,my_session)){ my_instance->rconn_intv = 1.0; my_instance->conn_stat = AMQP_STATUS_OK; }else{ my_instance->rconn_intv += 5.0; skygw_log_write(LOGFILE_ERROR, "Error : Failed to reconnect to the MQRabbit server "); } err_code = my_instance->conn_stat; } } spinlock_release(my_instance->rconn_lock); if(modutil_is_SQL(queue)){ if(my_session->uid == NULL){ my_session->uid = calloc(33,sizeof(char)); if(!my_session->uid){ skygw_log_write(LOGFILE_ERROR,"Error : Out of memory."); }else{ genkey(my_session->uid,32); } } } if (err_code == AMQP_STATUS_OK){ if(modutil_extract_SQL(queue, &ptr, &length)){ my_session->was_query = 1; prop._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop.content_type = amqp_cstring_bytes("text/plain"); prop.delivery_mode = AMQP_DELIVERY_PERSISTENT; prop.correlation_id = amqp_cstring_bytes(my_session->uid); prop.message_id = amqp_cstring_bytes("query"); memset(t_buf,0,128); sprintf(t_buf, "%lu|",(unsigned long)time(NULL)); int qlen = length + strnlen(t_buf,128); if((combined = malloc((qlen+1)*sizeof(char))) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error : Out of memory"); } strcpy(combined,t_buf); strncat(combined,ptr,length); if((err_code = amqp_basic_publish(my_session->conn,my_session->channel, amqp_cstring_bytes(my_instance->exchange), amqp_cstring_bytes(my_instance->key), 0,0,&prop,amqp_cstring_bytes(combined)) ) != AMQP_STATUS_OK){ spinlock_acquire(my_instance->rconn_lock); my_instance->conn_stat = err_code; spinlock_release(my_instance->rconn_lock); skygw_log_write_flush(LOGFILE_ERROR, "Error : Failed to publish message to MQRabbit server: " "%s",amqp_error_string2(err_code)); } } } /** Pass the query downstream */ return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }