From 8a71c85dd3f522fffbc3c4e18e514019f032aacf Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 2 Oct 2014 14:57:53 +0300 Subject: [PATCH 01/31] Initial implementation of the firewall filter. --- query_classifier/query_classifier.cc | 54 ++++ query_classifier/query_classifier.h | 2 +- server/modules/filter/CMakeLists.txt | 5 + server/modules/filter/fwfilter.c | 448 +++++++++++++++++++++++++++ 4 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 server/modules/filter/fwfilter.c diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 6eea51cdb..1ee07135f 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -1186,6 +1186,60 @@ bool is_drop_table_query(GWBUF* querybuf) lex->sql_command == SQLCOM_DROP_TABLE; } +char* skygw_get_where_clause(GWBUF* buf) +{ + LEX* lex; + unsigned int buffsz = 0,bufflen = 0; + char* where = NULL; + Item* item; + if(!query_is_parsed(buf)){ + parse_query(buf); + } + if((lex = get_lex(buf)) == NULL){ + return NULL; + } + + lex->current_select = lex->all_selects_list; + + while(lex->current_select) + { + if(lex->current_select->where){ + for (item=lex->current_select->where; item != NULL; item=item->next) + { + + Item::Type tp = item->type(); + if(item->name && tp == Item::FIELD_ITEM){ + + int isize = strlen(item->name) + 1; + if(where == NULL || isize + bufflen >= buffsz) + { + char *tmp = (char*)calloc(buffsz*2 + isize,sizeof(char)); + if(tmp){ + memcpy(tmp,where,buffsz); + if(where){ + free(where); + } + where = tmp; + buffsz = buffsz*2 + isize; + }else{ + return NULL; + } + } + + if(bufflen > 0){ + strcat(where," "); + } + strcat(where,item->name); + bufflen += isize; + + } + } + } + lex->current_select = lex->current_select->next_select_in_list(); + } + return where; +} + /* * Replace user-provided literals with question marks. Return a copy of the * querystr with replacements. diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index db2011642..0a3f9bb13 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -94,7 +94,7 @@ parsing_info_t* parsing_info_init(void (*donefun)(void *)); void parsing_info_done(void* ptr); bool query_is_parsed(GWBUF* buf); char* skygw_get_qtype_str(skygw_query_type_t qtype); - +char* skygw_get_where_clause(GWBUF* buf); EXTERN_C_BLOCK_END diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index f9dea236d..58fa1bb57 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -24,4 +24,9 @@ add_library(topfilter SHARED topfilter.c) target_link_libraries(topfilter log_manager utils) install(TARGETS topfilter DESTINATION modules) +add_library(fwfilter SHARED fwfilter.c) +target_link_libraries(fwfilter log_manager utils query_classifier) +install(TARGETS fwfilter DESTINATION modules) + + add_subdirectory(hint) \ No newline at end of file diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c new file mode 100644 index 000000000..93a3589f2 --- /dev/null +++ b/server/modules/filter/fwfilter.c @@ -0,0 +1,448 @@ +/* + * This file is distributed as part of MaxScale by MariaDB Corporation. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ + +/** + * @file fwfilter.c + * Firewall Filter + * + * A filter that acts as a firewall, blocking queries that do not meet the set requirements. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "Firewall Filter" +}; + +static int sess_num; + +/** + * Utility function to check if a string contains a valid IP address. + * The string handled as a null-terminated string. + * @param str String to parse + * @return True if the string contains a valid IP address. + */ +bool valid_ip(char* str) +{ + int octval = 0; + bool valid = true; + char cmpbuff[32]; + char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); + + while(source < end && (int)(dest - cmpbuff) < 32 && valid){ + switch(*source){ + + case '.': + case '/': + case '\0': + /**End of IP, string or octet*/ + *(dest++) = '\0'; + octval = atoi(cmpbuff); + dest = cmpbuff; + valid = octval < 256 && octval > -1 ? true: false; + if(*source == '/' || *source == '\0'){ + return valid; + }else{ + source++; + } + break; + + default: + /**In the IP octet, copy to buffer*/ + *(dest++) = *(source++); + break; + } + } + + return valid; +} + +/** + * Get one octet of IP + */ +int get_octet(char* str) +{ + int octval = 0,retval = -1; + bool valid = false; + char cmpbuff[32]; + char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); + + if(end == NULL){ + return retval; + } + + while(source < end && (int)(dest - cmpbuff) < 32 && !valid){ + switch(*source){ + + /**End of IP or string or the octet is done*/ + case '.': + case '/': + case '\0': + + *(dest++) = '\0'; + source++; + octval = atoi(cmpbuff); + dest = cmpbuff; + valid = octval < 256 && octval > -1 ? true: false; + if(valid) + { + retval = octval; + } + + break; + + default: + /**In the IP octet, copy to buffer*/ + *(dest++) = *(source++); + break; + } + } + + return retval; + +} + +/** + *Convert string with IP address to an unsigned 32-bit integer + * @param str String to convert + * @return Value of the IP converted to an unsigned 32-bit integer or zero in case of an error. + */ +uint32_t strtoip(char* str) +{ + uint32_t ip = 0; + char* tok = str; + if(!valid_ip(str)){ + return 0; + } + + ip = get_octet(tok); + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 8; + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 16; + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 24; + + return ip; +} + +/** + *Convert string with a subnet mask to an unsigned 32-bit integer + */ +uint32_t strtosubmask(char* str) +{ + uint32_t mask = 0; + char *ptr; + + if(!valid_ip(str) || + (ptr = strchr(str,'/')) == NULL || + !valid_ip(++ptr)) + { + return mask; + } + + mask = strtoip(ptr+1); + return mask; +} + +static char *version_str = "V1.0.0"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static int clientReply(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostic, +}; + +/** + * The Firewall filter instance. + */ + +typedef struct { + +} FW_INSTANCE; + +/** + * The session structure for Firewall filter. + */ +typedef struct { + DOWNSTREAM down; + UPSTREAM up; + SESSION* session; + FILE* debug_file; +} FW_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ + FW_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ + return NULL; + } + sess_num = 0; + return (FILTER *)my_instance; +} + + + + +/** + * Associate a new session with this instance of the filter and opens + * a connection to the server and prepares the exchange and the queue for use. + * + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ + FW_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(FW_SESSION))) == NULL){ + return NULL; + } + my_session->session = session; + + my_session->debug_file = fopen("/tmp/fwsession.log","a"); + fprintf(my_session->debug_file,"Logfile: /tmp/fwsession.log\n"); + fflush(my_session->debug_file); + return my_session; +} + + + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + fclose(my_session->debug_file); +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + free(my_session); +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + my_session->down = *downstream; +} + +static void setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + my_session->up = *upstream; +} + +/** + * 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. + * + * @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) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + bool accept = true; + char* where; + DCB* dcb = my_session->session->client; + + if(modutil_is_SQL(queue)){ + + if(!query_is_parsed(queue)) + { + parse_query(queue); + } + + where = skygw_get_where_clause(queue); + if(where && strchr(where,'*') != NULL){ + accept = false; + skygw_log_write(LOGFILE_TRACE, "where clause has '*': %s", where); + } + + free(where); + + } + + if(!accept){ + + /** + * Fake empty COM_QUERY to block the query + */ + + *((unsigned char*)queue->start) = 0x02; + *((unsigned char*)queue->start + 1) = 0x00; + *((unsigned char*)queue->start + 2) = 0x00; + *((unsigned char*)queue->start + 3) = 0x00; + *((unsigned char*)queue->start + 4) = 0x03; + *((unsigned char*)queue->start + 5) = ';'; + } + + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * The clientReply entry point. This is passed the response buffer + * to which the filter should be applied. Once processed the + * query is passed to the upstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param reply The response data + */ +static int clientReply(FILTER* instance, void *session, GWBUF *reply) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + + return my_session->up.clientReply(my_session->up.instance, + my_session->up.session, reply); +} + +/** + * Diagnostics routine + * + * Prints the connection details and the names of the exchange, + * queue and the routing key. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + + if (my_instance) + { + dcb_printf(dcb, "\t\tFirewall Filter\n"); + } +} + From e02bed1f1414e78b92296bde57be601fc6f2a942 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 2 Oct 2014 14:57:53 +0300 Subject: [PATCH 02/31] Initial implementation of the firewall filter. --- query_classifier/query_classifier.cc | 54 +++ query_classifier/query_classifier.h | 2 +- server/modules/filter/CMakeLists.txt | 5 + server/modules/filter/fwfilter.c | 495 +++++++++++++++++++++++++++ 4 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 server/modules/filter/fwfilter.c diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 6eea51cdb..1ee07135f 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -1186,6 +1186,60 @@ bool is_drop_table_query(GWBUF* querybuf) lex->sql_command == SQLCOM_DROP_TABLE; } +char* skygw_get_where_clause(GWBUF* buf) +{ + LEX* lex; + unsigned int buffsz = 0,bufflen = 0; + char* where = NULL; + Item* item; + if(!query_is_parsed(buf)){ + parse_query(buf); + } + if((lex = get_lex(buf)) == NULL){ + return NULL; + } + + lex->current_select = lex->all_selects_list; + + while(lex->current_select) + { + if(lex->current_select->where){ + for (item=lex->current_select->where; item != NULL; item=item->next) + { + + Item::Type tp = item->type(); + if(item->name && tp == Item::FIELD_ITEM){ + + int isize = strlen(item->name) + 1; + if(where == NULL || isize + bufflen >= buffsz) + { + char *tmp = (char*)calloc(buffsz*2 + isize,sizeof(char)); + if(tmp){ + memcpy(tmp,where,buffsz); + if(where){ + free(where); + } + where = tmp; + buffsz = buffsz*2 + isize; + }else{ + return NULL; + } + } + + if(bufflen > 0){ + strcat(where," "); + } + strcat(where,item->name); + bufflen += isize; + + } + } + } + lex->current_select = lex->current_select->next_select_in_list(); + } + return where; +} + /* * Replace user-provided literals with question marks. Return a copy of the * querystr with replacements. diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index db2011642..0a3f9bb13 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -94,7 +94,7 @@ parsing_info_t* parsing_info_init(void (*donefun)(void *)); void parsing_info_done(void* ptr); bool query_is_parsed(GWBUF* buf); char* skygw_get_qtype_str(skygw_query_type_t qtype); - +char* skygw_get_where_clause(GWBUF* buf); EXTERN_C_BLOCK_END diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index f9dea236d..58fa1bb57 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -24,4 +24,9 @@ add_library(topfilter SHARED topfilter.c) target_link_libraries(topfilter log_manager utils) install(TARGETS topfilter DESTINATION modules) +add_library(fwfilter SHARED fwfilter.c) +target_link_libraries(fwfilter log_manager utils query_classifier) +install(TARGETS fwfilter DESTINATION modules) + + add_subdirectory(hint) \ No newline at end of file diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c new file mode 100644 index 000000000..40ce0bf84 --- /dev/null +++ b/server/modules/filter/fwfilter.c @@ -0,0 +1,495 @@ +/* + * This file is distributed as part of MaxScale by MariaDB Corporation. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ + +/** + * @file fwfilter.c + * Firewall Filter + * + * A filter that acts as a firewall, blocking queries that do not meet the set requirements. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "Firewall Filter" +}; + +static int sess_num; + +/** + * Utility function to check if a string contains a valid IP address. + * The string handled as a null-terminated string. + * @param str String to parse + * @return True if the string contains a valid IP address. + */ +bool valid_ip(char* str) +{ + int octval = 0; + bool valid = true; + char cmpbuff[32]; + char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); + + while(source < end && (int)(dest - cmpbuff) < 32 && valid){ + switch(*source){ + + case '.': + case '/': + case '\0': + /**End of IP, string or octet*/ + *(dest++) = '\0'; + octval = atoi(cmpbuff); + dest = cmpbuff; + valid = octval < 256 && octval > -1 ? true: false; + if(*source == '/' || *source == '\0'){ + return valid; + }else{ + source++; + } + break; + + default: + /**In the IP octet, copy to buffer*/ + *(dest++) = *(source++); + break; + } + } + + return valid; +} + +/** + * Get one octet of IP + */ +int get_octet(char* str) +{ + int octval = 0,retval = -1; + bool valid = false; + char cmpbuff[32]; + char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); + + if(end == NULL){ + return retval; + } + + while(source < end && (int)(dest - cmpbuff) < 32 && !valid){ + switch(*source){ + + /**End of IP or string or the octet is done*/ + case '.': + case '/': + case '\0': + + *(dest++) = '\0'; + source++; + octval = atoi(cmpbuff); + dest = cmpbuff; + valid = octval < 256 && octval > -1 ? true: false; + if(valid) + { + retval = octval; + } + + break; + + default: + /**In the IP octet, copy to buffer*/ + *(dest++) = *(source++); + break; + } + } + + return retval; + +} + +/** + *Convert string with IP address to an unsigned 32-bit integer + * @param str String to convert + * @return Value of the IP converted to an unsigned 32-bit integer or zero in case of an error. + */ +uint32_t strtoip(char* str) +{ + uint32_t ip = 0; + char* tok = str; + if(!valid_ip(str)){ + return 0; + } + + ip = get_octet(tok); + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 8; + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 16; + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok))<< 24; + + return ip; +} + +/** + *Convert string with a subnet mask to an unsigned 32-bit integer + */ +uint32_t strtosubmask(char* str) +{ + uint32_t mask = 0; + char *ptr; + + if(!valid_ip(str) || + (ptr = strchr(str,'/')) == NULL || + !valid_ip(++ptr)) + { + return mask; + } + + mask = strtoip(ptr+1); + return mask; +} + +static char *version_str = "V1.0.0"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static int clientReply(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostic, +}; + +/** + * The Firewall filter instance. + */ + +typedef struct { + +} FW_INSTANCE; + +/** + * The session structure for Firewall filter. + */ +typedef struct { + DOWNSTREAM down; + UPSTREAM up; + SESSION* session; + bool blocked; +} FW_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ + FW_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ + return NULL; + } + sess_num = 0; + return (FILTER *)my_instance; +} + + + + +/** + * Associate a new session with this instance of the filter and opens + * a connection to the server and prepares the exchange and the queue for use. + * + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ + FW_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(FW_SESSION))) == NULL){ + return NULL; + } + my_session->session = session; + my_session->blocked = false; + return my_session; +} + + + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ + //FW_SESSION *my_session = (FW_SESSION *)session; +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + free(my_session); +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + my_session->down = *downstream; +} + +static void setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + my_session->up = *upstream; +} + +/** + * 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. + * + * @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) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + bool accept = true; + char *where,*query; + int len; + DCB* dcb = my_session->session->client; + + if(modutil_is_SQL(queue)){ + + modutil_extract_SQL(queue, &query, &len); + + where = skygw_get_where_clause(queue); + if((where && strchr(where,'*') != NULL) || + (skygw_is_real_query(queue) && memchr(query,'*',len) != NULL)){ + accept = false; + skygw_log_write(LOGFILE_TRACE, "where clause with '*': %s", where); + } + + free(where); + + } + + if(!accept){ + + /** + * Convert the query to a fake COM_QUERY with no content + * to block the query and trigger an error package from the backend. + */ + + my_session->blocked = true; + + *((unsigned char*)queue->start) = 0x02; + *((unsigned char*)queue->start + 1) = 0x00; + *((unsigned char*)queue->start + 2) = 0x00; + *((unsigned char*)queue->start + 3) = 0x00; + *((unsigned char*)queue->start + 4) = 0x03; + *((unsigned char*)queue->start + 5) = ';'; + + } + + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Checks if the packet contains an empty query error + * and if the session blocked the last query + * @param buf Buffer to inspect + * @param session Filter session object + * @return true if the error is the right one and the previous query was blocked + */ +bool is_dummy(GWBUF* buf,FW_SESSION* session) +{ + return(*((unsigned char*)buf->start + 4) == 0xff && + *((unsigned char*)buf->start + 5) == 0x29 && + *((unsigned char*)buf->start + 6) == 0x04 && + session->blocked); +} + +/** + * Generates a dummy error packet for the client. + * @return The dummy packet or NULL if an error occurred + */ +GWBUF* gen_dummy_error() +{ + GWBUF* buf; + const char* errmsg = "Access denied."; + unsigned int errlen = strlen(errmsg), + pktlen = errlen + 9; + buf = gwbuf_alloc(13 + errlen); + if(buf){ + strcpy(buf->start + 7,"#HY000"); + memcpy(buf->start + 13,errmsg,errlen); + *((unsigned char*)buf->start + 0) = pktlen; + *((unsigned char*)buf->start + 1) = pktlen >> 8; + *((unsigned char*)buf->start + 2) = pktlen >> 16; + *((unsigned char*)buf->start + 3) = 0x01; + *((unsigned char*)buf->start + 4) = 0xff; + *((unsigned char*)buf->start + 5) = (unsigned char)1141; + *((unsigned char*)buf->start + 6) = (unsigned char)(1141 >> 8); + } + return buf; +} + +/** + * The clientReply entry point. This is passed the response buffer + * to which the filter should be applied. Once processed the + * query is passed to the upstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param reply The response data + */ +static int clientReply(FILTER* instance, void *session, GWBUF *reply) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + GWBUF* forward = reply; + + if(is_dummy(reply,my_session)){ + gwbuf_free(reply); + forward = gen_dummy_error(); + my_session->blocked = false; + } + + return my_session->up.clientReply(my_session->up.instance, + my_session->up.session, forward); +} + +/** + * Diagnostics routine + * + * Prints the connection details and the names of the exchange, + * queue and the routing key. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + + if (my_instance) + { + dcb_printf(dcb, "\t\tFirewall Filter\n"); + } +} + From 8d5b985e2bb418717060eb7ea1e0e7aa51ccf134 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 9 Oct 2014 15:31:44 +0300 Subject: [PATCH 03/31] Added blocking of IP ranges. --- server/modules/filter/fwfilter.c | 242 +++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 80 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 40ce0bf84..820773055 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -35,7 +35,7 @@ #include #include #include - +#include MODULE_INFO info = { MODULE_API_FILTER, @@ -44,8 +44,6 @@ MODULE_INFO info = { "Firewall Filter" }; -static int sess_num; - /** * Utility function to check if a string contains a valid IP address. * The string handled as a null-terminated string. @@ -64,13 +62,14 @@ bool valid_ip(char* str) case '.': case '/': + case ' ': case '\0': /**End of IP, string or octet*/ *(dest++) = '\0'; octval = atoi(cmpbuff); dest = cmpbuff; valid = octval < 256 && octval > -1 ? true: false; - if(*source == '/' || *source == '\0'){ + if(*source == '/' || *source == '\0' || *source == ' '){ return valid; }else{ source++; @@ -86,6 +85,41 @@ bool valid_ip(char* str) return valid; } +/** + * Replace all non-essential characters with whitespace from a null-terminated string. + * This function modifies the passed string. + * @param str String to purify + */ +char* strip_tags(char* str) +{ + char *ptr = str, *lead = str, *tail = NULL; + int len = 0; + while(*ptr != '\0'){ + if(isalnum(*ptr) || *ptr == '.' || *ptr == '/'){ + ptr++; + continue; + } + *ptr++ = ' '; + } + + /**Strip leading and trailing whitespace*/ + + while(*lead != '\0'){ + if(isspace(*lead)){ + lead++; + }else{ + tail = strchr(str,'\0') - 1; + while(tail > lead && isspace(*tail)){ + tail--; + } + len = (int)(tail - lead) + 1; + memmove(str,lead,len); + memset(str+len, 0, 1); + break; + } + } + return str; +} /** * Get one octet of IP @@ -95,7 +129,7 @@ int get_octet(char* str) int octval = 0,retval = -1; bool valid = false; char cmpbuff[32]; - char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); + char *source = str,*dest = cmpbuff,*end = strchr(str,'\0') + 1; if(end == NULL){ return retval; @@ -107,6 +141,7 @@ int get_octet(char* str) /**End of IP or string or the octet is done*/ case '.': case '/': + case ' ': case '\0': *(dest++) = '\0'; @@ -145,13 +180,13 @@ uint32_t strtoip(char* str) return 0; } - ip = get_octet(tok); - tok = strchr(tok,'.') + 1; - ip |= (get_octet(tok))<< 8; + ip |= get_octet(tok) << 24; tok = strchr(tok,'.') + 1; ip |= (get_octet(tok))<< 16; tok = strchr(tok,'.') + 1; - ip |= (get_octet(tok))<< 24; + ip |= (get_octet(tok))<< 8; + tok = strchr(tok,'.') + 1; + ip |= (get_octet(tok)); return ip; } @@ -171,8 +206,8 @@ uint32_t strtosubmask(char* str) return mask; } - mask = strtoip(ptr+1); - return mask; + mask = strtoip(ptr); + return ~mask; } static char *version_str = "V1.0.0"; @@ -206,9 +241,21 @@ static FILTER_OBJECT MyObject = { /** * The Firewall filter instance. */ +typedef struct iprange_t{ + struct iprange_t* next; + uint32_t ip; + uint32_t mask; +}IPRANGE; typedef struct { - + char** forbid_column; + char** users; + IPRANGE* networks; + int forbid_column_count; + int user_count; + bool block_select_all; + bool whitelist; + } FW_INSTANCE; /** @@ -218,7 +265,6 @@ typedef struct { DOWNSTREAM down; UPSTREAM up; SESSION* session; - bool blocked; } FW_SESSION; /** @@ -255,6 +301,40 @@ GetModuleObject() return &MyObject; } +void parse_rule(char* rule, FW_INSTANCE* instance) +{ + char* ptr = rule; + bool allow,block; + + /**IP range rules*/ + if((allow = (strstr(rule,"allow") != NULL)) || + (block = (strstr(rule,"block") != NULL))){ + if(allow){ + instance->whitelist = true; + }else if(block){ + instance->whitelist = false; + } + + ptr = strchr(rule,' '); + ptr++; + if(valid_ip(ptr)){ + IPRANGE* rng = calloc(1,sizeof(IPRANGE)); + if(rng){ + rng->ip = strtoip(ptr); + rng->mask = strtosubmask(ptr); + rng->next = instance->networks; + instance->networks = rng; + } + } + + } + + /**Column rules*/ + + /**Sanity rules*/ + +} + /** * Create an instance of the filter for a particular service * within MaxScale. @@ -271,7 +351,12 @@ createInstance(char **options, FILTER_PARAMETER **params) if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ return NULL; } - sess_num = 0; + int i; + for(i = 0;params[i];i++){ + if(strcmp(params[i]->name,"rule") == 0){ + parse_rule(strip_tags(params[i]->value),my_instance); + } + } return (FILTER *)my_instance; } @@ -296,7 +381,6 @@ newSession(FILTER *instance, SESSION *session) return NULL; } my_session->session = session; - my_session->blocked = false; return my_session; } @@ -349,63 +433,6 @@ static void setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) my_session->up = *upstream; } -/** - * 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. - * - * @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) -{ - FW_SESSION *my_session = (FW_SESSION *)session; - FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; - bool accept = true; - char *where,*query; - int len; - DCB* dcb = my_session->session->client; - - if(modutil_is_SQL(queue)){ - - modutil_extract_SQL(queue, &query, &len); - - where = skygw_get_where_clause(queue); - if((where && strchr(where,'*') != NULL) || - (skygw_is_real_query(queue) && memchr(query,'*',len) != NULL)){ - accept = false; - skygw_log_write(LOGFILE_TRACE, "where clause with '*': %s", where); - } - - free(where); - - } - - if(!accept){ - - /** - * Convert the query to a fake COM_QUERY with no content - * to block the query and trigger an error package from the backend. - */ - - my_session->blocked = true; - - *((unsigned char*)queue->start) = 0x02; - *((unsigned char*)queue->start + 1) = 0x00; - *((unsigned char*)queue->start + 2) = 0x00; - *((unsigned char*)queue->start + 3) = 0x00; - *((unsigned char*)queue->start + 4) = 0x03; - *((unsigned char*)queue->start + 5) = ';'; - - } - - return my_session->down.routeQuery(my_session->down.instance, - my_session->down.session, queue); -} - /** * Checks if the packet contains an empty query error * and if the session blocked the last query @@ -417,8 +444,7 @@ bool is_dummy(GWBUF* buf,FW_SESSION* session) { return(*((unsigned char*)buf->start + 4) == 0xff && *((unsigned char*)buf->start + 5) == 0x29 && - *((unsigned char*)buf->start + 6) == 0x04 && - session->blocked); + *((unsigned char*)buf->start + 6) == 0x04); } /** @@ -446,6 +472,68 @@ GWBUF* gen_dummy_error() return buf; } +/** + * 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. + * + * @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) +{ + FW_SESSION *my_session = (FW_SESSION *)session; + FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; + IPRANGE* ipranges = my_instance->networks; + bool accept = true; + char *where,*query; + uint32_t ip; + int len; + DCB* dcb = my_session->session->client; + ip = strtoip(dcb->remote); + + while(ipranges){ + if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ + accept = my_instance->whitelist; + break; + } + ipranges = ipranges->next; + } + if(accept){ + + if(modutil_is_SQL(queue)){ + + modutil_extract_SQL(queue, &query, &len); + + where = skygw_get_where_clause(queue); + if((where && strchr(where,'*') != NULL) || + (skygw_is_real_query(queue) && memchr(query,'*',len) != NULL)){ + accept = false; + skygw_log_write(LOGFILE_TRACE, "where clause with '*': %s", where); + } + + free(where); + + } + } + + if(!accept){ + + gwbuf_free(queue); + GWBUF* forward = gen_dummy_error(); + dcb->func.write(dcb,forward); + //gwbuf_free(forward); + return 0; + + } + + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + /** * The clientReply entry point. This is passed the response buffer * to which the filter should be applied. Once processed the @@ -462,12 +550,6 @@ static int clientReply(FILTER* instance, void *session, GWBUF *reply) FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; GWBUF* forward = reply; - if(is_dummy(reply,my_session)){ - gwbuf_free(reply); - forward = gen_dummy_error(); - my_session->blocked = false; - } - return my_session->up.clientReply(my_session->up.instance, my_session->up.session, forward); } From afc081d992287800315d620f69378ce5ce7321df Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 10 Oct 2014 08:50:41 +0300 Subject: [PATCH 04/31] Added column, username and wildcard rules. --- server/modules/filter/fwfilter.c | 237 ++++++++++++++++++++----------- 1 file changed, 151 insertions(+), 86 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 820773055..92e978a4e 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -78,7 +78,11 @@ bool valid_ip(char* str) default: /**In the IP octet, copy to buffer*/ - *(dest++) = *(source++); + if(isdigit(*source)){ + *(dest++) = *(source++); + }else{ + return false; + } break; } } @@ -158,7 +162,11 @@ int get_octet(char* str) default: /**In the IP octet, copy to buffer*/ - *(dest++) = *(source++); + if(isdigit(*source)){ + *(dest++) = *(source++); + }else{ + return -1; + } break; } } @@ -174,19 +182,22 @@ int get_octet(char* str) */ uint32_t strtoip(char* str) { - uint32_t ip = 0; + uint32_t ip = 0,octet = 0; char* tok = str; if(!valid_ip(str)){ return 0; } - - ip |= get_octet(tok) << 24; + octet = get_octet(tok) << 24; + ip |= octet; tok = strchr(tok,'.') + 1; - ip |= (get_octet(tok))<< 16; + octet = get_octet(tok) << 16; + ip |= octet; tok = strchr(tok,'.') + 1; - ip |= (get_octet(tok))<< 8; + octet = get_octet(tok) << 8; + ip |= octet; tok = strchr(tok,'.') + 1; - ip |= (get_octet(tok)); + octet = get_octet(tok); + ip |= octet; return ip; } @@ -220,9 +231,7 @@ static void *newSession(FILTER *instance, SESSION *session); static void closeSession(FILTER *instance, void *session); static void freeSession(FILTER *instance, void *session); static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); -static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream); static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); -static int clientReply(FILTER *instance, void *fsession, GWBUF *queue); static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); @@ -232,12 +241,20 @@ static FILTER_OBJECT MyObject = { closeSession, freeSession, setDownstream, - setUpstream, + NULL, routeQuery, - clientReply, + NULL, diagnostic, }; +/** + * Generic linked list of string values + */ +typedef struct item_t{ + struct item_t* next; + char* value; +}ITEM; + /** * The Firewall filter instance. */ @@ -248,13 +265,11 @@ typedef struct iprange_t{ }IPRANGE; typedef struct { - char** forbid_column; - char** users; + ITEM* columns; + ITEM* users; IPRANGE* networks; - int forbid_column_count; - int user_count; - bool block_select_all; - bool whitelist; + int column_count, column_size, user_count, user_size; + bool block_wildcard, whitelist_users,whitelist_networks; } FW_INSTANCE; @@ -288,13 +303,13 @@ ModuleInit() } /** - * The module entry point routine. It is this routine that - * must populate the structure that is referred to as the - * "module object", this is a structure with the set of - * external entry points for this module. - * - * @return The module object - */ +* The module entry point routine. It is this routine that +* must populate the structure that is referred to as the +* "module object", this is a structure with the set of +* external entry points for this module. +* +* @return The module object +*/ FILTER_OBJECT * GetModuleObject() { @@ -304,20 +319,19 @@ GetModuleObject() void parse_rule(char* rule, FW_INSTANCE* instance) { char* ptr = rule; - bool allow,block; + bool allow,block,mode; /**IP range rules*/ if((allow = (strstr(rule,"allow") != NULL)) || (block = (strstr(rule,"block") != NULL))){ - if(allow){ - instance->whitelist = true; - }else if(block){ - instance->whitelist = false; - } + mode = allow ? true:false; ptr = strchr(rule,' '); ptr++; - if(valid_ip(ptr)){ + + if(valid_ip(ptr)){ /**Add IP address range*/ + + instance->whitelist_networks = mode; IPRANGE* rng = calloc(1,sizeof(IPRANGE)); if(rng){ rng->ip = strtoip(ptr); @@ -325,14 +339,51 @@ void parse_rule(char* rule, FW_INSTANCE* instance) rng->next = instance->networks; instance->networks = rng; } + + }else{ /**Add usernames or columns*/ + + char *tok = strtok(ptr," ,\0"); + ITEM* prev = NULL; + bool is_user = false, is_column = false; + + if(strcmp(tok,"wildcard") == 0){ + instance->block_wildcard = block ? true : false; + return; + } + + if(strcmp(tok,"users") == 0){/**Adding users*/ + prev = instance->users; + instance->whitelist_users = mode; + is_user = true; + }else if(strcmp(tok,"columns") == 0){/**Adding Columns*/ + prev = instance->columns; + is_column = true; + } + + tok = strtok(NULL," ,\0"); + + if(is_user || is_column){ + while(tok){ + + ITEM* item = calloc(1,sizeof(ITEM)); + if(item){ + item->next = prev; + item->value = strdup(tok); + prev = item; + } + tok = strtok(NULL," ,\0"); + + } + if(is_user){ + instance->users = prev; + }else if(is_column){ + instance->columns = prev; + } + } } } - /**Column rules*/ - - /**Sanity rules*/ - } /** @@ -353,7 +404,7 @@ createInstance(char **options, FILTER_PARAMETER **params) } int i; for(i = 0;params[i];i++){ - if(strcmp(params[i]->name,"rule") == 0){ + if(strstr(params[i]->name,"rule")){ parse_rule(strip_tags(params[i]->value),my_instance); } } @@ -427,12 +478,6 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) my_session->down = *downstream; } -static void setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) -{ - FW_SESSION *my_session = (FW_SESSION *)session; - my_session->up = *upstream; -} - /** * Checks if the packet contains an empty query error * and if the session blocked the last query @@ -488,70 +533,90 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; IPRANGE* ipranges = my_instance->networks; - bool accept = true; + ITEM *users = my_instance->users, *columns = my_instance->columns; + bool accept = false, match = false; char *where,*query; uint32_t ip; int len; DCB* dcb = my_session->session->client; ip = strtoip(dcb->remote); - while(ipranges){ - if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ - accept = my_instance->whitelist; + while(users){ + if(strcmp(dcb->user,users->value)==0){ + match = true; + accept = my_instance->whitelist_users; + skygw_log_write(LOGFILE_TRACE, "%s@%s was %s.", + dcb->user,dcb->remote,(my_instance->whitelist_users ? "allowed":"blocked")); break; } - ipranges = ipranges->next; + users = users->next; } - if(accept){ - if(modutil_is_SQL(queue)){ - - modutil_extract_SQL(queue, &query, &len); - - where = skygw_get_where_clause(queue); - if((where && strchr(where,'*') != NULL) || - (skygw_is_real_query(queue) && memchr(query,'*',len) != NULL)){ - accept = false; - skygw_log_write(LOGFILE_TRACE, "where clause with '*': %s", where); + if(!match){ + while(ipranges){ + if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ + match = true; + accept = my_instance->whitelist_networks; + skygw_log_write(LOGFILE_TRACE, "%s@%s was %s.", + dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"blocked")); + break; } - - free(where); - + ipranges = ipranges->next; } } - if(!accept){ + + if(modutil_is_SQL(queue)){ + + if(!query_is_parsed(queue)){ + parse_query(queue); + } + + if(skygw_is_real_query(queue)){ + + match = false; + modutil_extract_SQL(queue, &query, &len); + where = skygw_get_where_clause(queue); + + if(my_instance->block_wildcard && + ((where && strchr(where,'*') != NULL) || + (memchr(query,'*',len) != NULL))){ + match = true; + accept = false; + skygw_log_write(LOGFILE_TRACE, "query contains wildcard, blocking it: %.*s",len,query); + } + if(!match){ + if(where == NULL){ + where = malloc(sizeof(char)*len+1); + memcpy(where,query,len); + memset(where+len,0,1); + } + while(columns){ + if(strstr(where,columns->value)){ + match = true; + accept = false; + skygw_log_write(LOGFILE_TRACE, "query contains a forbidden column %s, blocking it: %.*s",columns->value,len,query); + break; + } + columns = columns->next; + } + } + free(where); + } + } + + if(accept){ + + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); + }else{ gwbuf_free(queue); GWBUF* forward = gen_dummy_error(); dcb->func.write(dcb,forward); //gwbuf_free(forward); return 0; - } - - return my_session->down.routeQuery(my_session->down.instance, - my_session->down.session, queue); -} - -/** - * The clientReply entry point. This is passed the response buffer - * to which the filter should be applied. Once processed the - * query is passed to the upstream component - * (filter or router) in the filter chain. - * - * @param instance The filter instance data - * @param session The filter session - * @param reply The response data - */ -static int clientReply(FILTER* instance, void *session, GWBUF *reply) -{ - FW_SESSION *my_session = (FW_SESSION *)session; - FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; - GWBUF* forward = reply; - - return my_session->up.clientReply(my_session->up.instance, - my_session->up.session, forward); } /** From c99b7c884ff4f6ebe6ae1e2a84931ecf6577e2a0 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 12 Oct 2014 10:12:35 +0300 Subject: [PATCH 05/31] Added rule for requirement of where clause on queries --- server/modules/filter/fwfilter.c | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 92e978a4e..bb9b4526b 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -247,16 +247,31 @@ static FILTER_OBJECT MyObject = { diagnostic, }; + + +/** + * Query types + */ + +enum querytype_t{ + ALL, + SELECT, + INSERT, + UPDATE, + DELETE +}; + /** * Generic linked list of string values */ + typedef struct item_t{ struct item_t* next; char* value; }ITEM; /** - * The Firewall filter instance. + * A link in a list of IP adresses and subnet masks */ typedef struct iprange_t{ struct iprange_t* next; @@ -264,11 +279,15 @@ typedef struct iprange_t{ uint32_t mask; }IPRANGE; +/** + * The Firewall filter instance. + */ typedef struct { ITEM* columns; ITEM* users; IPRANGE* networks; int column_count, column_size, user_count, user_size; + bool require_where[QUERY_TYPES]; bool block_wildcard, whitelist_users,whitelist_networks; } FW_INSTANCE; @@ -326,7 +345,10 @@ void parse_rule(char* rule, FW_INSTANCE* instance) (block = (strstr(rule,"block") != NULL))){ mode = allow ? true:false; - ptr = strchr(rule,' '); + + if((ptr = strchr(rule,' ')) == NULL){ + return; + } ptr++; if(valid_ip(ptr)){ /**Add IP address range*/ @@ -382,6 +404,32 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } } + }else if((ptr = strstr(rule,"require")) != NULL){ + + if((ptr = strstr(ptr,"where")) != NULL && + (ptr = strchr(ptr,' ')) != NULL){ + char* tok; + + ptr++; + tok = strtok(ptr," ,\0"); + while(tok){ + if(strcmp(tok, "all") == 0){ + instance->require_where[ALL] = true; + break; + }else if(strcmp(tok, "select") == 0){ + instance->require_where[SELECT] = true; + }else if(strcmp(tok, "insert") == 0){ + instance->require_where[INSERT] = true; + }else if(strcmp(tok, "update") == 0){ + instance->require_where[UPDATE] = true; + }else if(strcmp(tok, "delete") == 0){ + instance->require_where[DELETE] = true; + } + tok = strtok(NULL," ,\0"); + } + + } + } } From 9abe270da88c04bfdab1ac295f1d2d089a54c569 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 13 Oct 2014 13:48:07 +0300 Subject: [PATCH 06/31] added checking for where clause on queries --- query_classifier/query_classifier.cc | 86 +++++++--- query_classifier/query_classifier.h | 8 +- server/modules/filter/fwfilter.c | 230 +++++++++++++++------------ 3 files changed, 200 insertions(+), 124 deletions(-) diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 1ee07135f..5eedc31c0 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -1449,7 +1449,7 @@ static void parsing_info_set_plain_str( * @return string representing the query type value */ char* skygw_get_qtype_str( - skygw_query_type_t qtype) + skygw_query_type_t qtype) { int t1 = (int)qtype; int t2 = 1; @@ -1461,26 +1461,72 @@ char* skygw_get_qtype_str( * t1 is completely cleared. */ while (t1 != 0) - { - if (t1&t2) - { - t = (skygw_query_type_t)t2; + { + if (t1&t2) + { + t = (skygw_query_type_t)t2; - if (qtype_str == NULL) - { - qtype_str = strdup(STRQTYPE(t)); - } - else - { - size_t len = strlen(STRQTYPE(t)); - /** reallocate space for delimiter, new string and termination */ - qtype_str = (char *)realloc(qtype_str, strlen(qtype_str)+1+len+1); - snprintf(qtype_str+strlen(qtype_str), 1+len+1, "|%s", STRQTYPE(t)); - } - /** Remove found value from t1 */ - t1 &= ~t2; + if (qtype_str == NULL) + { + qtype_str = strdup(STRQTYPE(t)); + } + else + { + size_t len = strlen(STRQTYPE(t)); + /** reallocate space for delimiter, new string and termination */ + qtype_str = (char *)realloc(qtype_str, strlen(qtype_str)+1+len+1); + snprintf(qtype_str+strlen(qtype_str), 1+len+1, "|%s", STRQTYPE(t)); + } + /** Remove found value from t1 */ + t1 &= ~t2; + } + t2 <<= 1; } - t2 <<= 1; - } return qtype_str; } +skygw_query_op_t query_classifier_get_operation(GWBUF* querybuf) +{ + LEX* lex = get_lex(querybuf); + skygw_query_op_t operation; + if(lex){ + switch(lex->sql_command){ + case SQLCOM_SELECT: + operation = QUERY_OP_SELECT; + break; + case SQLCOM_CREATE_TABLE: + operation = QUERY_OP_CREATE_TABLE; + break; + case SQLCOM_CREATE_INDEX: + operation = QUERY_OP_CREATE_INDEX; + break; + case SQLCOM_ALTER_TABLE: + operation = QUERY_OP_ALTER_TABLE; + break; + case SQLCOM_UPDATE: + operation = QUERY_OP_UPDATE; + break; + case SQLCOM_INSERT: + operation = QUERY_OP_INSERT; + break; + case SQLCOM_INSERT_SELECT: + operation = QUERY_OP_INSERT_SELECT; + break; + case SQLCOM_DELETE: + operation = QUERY_OP_DELETE; + break; + case SQLCOM_TRUNCATE: + operation = QUERY_OP_TRUNCATE; + break; + case SQLCOM_DROP_TABLE: + operation = QUERY_OP_DROP_TABLE; + break; + case SQLCOM_DROP_INDEX: + operation = QUERY_OP_DROP_INDEX; + break; + + default: + operation = QUERY_OP_UNDEFINED; + } + } + return operation; +} diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 3e6bdf59b..eb0e6d4b9 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -60,6 +60,12 @@ typedef enum { QUERY_TYPE_SHOW_TABLES = 0x400000 /*< Show list of tables */ } skygw_query_type_t; +typedef enum { + QUERY_OP_UNDEFINED, QUERY_OP_SELECT, QUERY_OP_CREATE_TABLE, QUERY_OP_CREATE_INDEX, + QUERY_OP_ALTER_TABLE, QUERY_OP_UPDATE, QUERY_OP_INSERT, QUERY_OP_INSERT_SELECT, + QUERY_OP_DELETE, QUERY_OP_TRUNCATE, QUERY_OP_DROP_TABLE, QUERY_OP_DROP_INDEX, + +}skygw_query_op_t; typedef struct parsing_info_st { #if defined(SS_DEBUG) @@ -81,7 +87,7 @@ typedef struct parsing_info_st { * classify the query. */ skygw_query_type_t query_classifier_get_type(GWBUF* querybuf); - +skygw_query_op_t query_classifier_get_operation(GWBUF* querybuf); /** Free THD context and close MYSQL */ char* skygw_query_classifier_get_stmtname(MYSQL* mysql); char* skygw_get_created_table_name(GWBUF* querybuf); diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index bb9b4526b..7893b0724 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -22,6 +22,9 @@ * * A filter that acts as a firewall, blocking queries that do not meet the set requirements. */ +#include +#include +#include #include #include #include @@ -30,12 +33,12 @@ #include #include #include +#include #include #include #include -#include #include -#include + MODULE_INFO info = { MODULE_API_FILTER, @@ -44,6 +47,89 @@ MODULE_INFO info = { "Firewall Filter" }; +static char *version_str = "V1.0.0"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + NULL, + routeQuery, + NULL, + diagnostic, +}; + + + +/** + * Query types + */ + +#define QUERY_TYPES 5 + +enum querytype_t{ + ALL, + SELECT, + INSERT, + UPDATE, + DELETE +}; + +/** + * Generic linked list of string values + */ + +typedef struct item_t{ + struct item_t* next; + char* value; +}ITEM; + +/** + * A link in a list of IP adresses and subnet masks + */ +typedef struct iprange_t{ + struct iprange_t* next; + uint32_t ip; + uint32_t mask; +}IPRANGE; + +/** + * The Firewall filter instance. + */ +typedef struct { + ITEM* columns; + ITEM* users; + IPRANGE* networks; + int column_count, column_size, user_count, user_size; + bool require_where[QUERY_TYPES]; + bool block_wildcard, whitelist_users,whitelist_networks,def_op; + +} FW_INSTANCE; + +/** + * The session structure for Firewall filter. + */ +typedef struct { + DOWNSTREAM down; + UPSTREAM up; + SESSION* session; +} FW_SESSION; + + /** * Utility function to check if a string contains a valid IP address. * The string handled as a null-terminated string. @@ -221,85 +307,6 @@ uint32_t strtosubmask(char* str) return ~mask; } -static char *version_str = "V1.0.0"; - -/* - * The filter entry points - */ -static FILTER *createInstance(char **options, FILTER_PARAMETER **); -static void *newSession(FILTER *instance, SESSION *session); -static void closeSession(FILTER *instance, void *session); -static void freeSession(FILTER *instance, void *session); -static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); -static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); -static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); - - -static FILTER_OBJECT MyObject = { - createInstance, - newSession, - closeSession, - freeSession, - setDownstream, - NULL, - routeQuery, - NULL, - diagnostic, -}; - - - -/** - * Query types - */ - -enum querytype_t{ - ALL, - SELECT, - INSERT, - UPDATE, - DELETE -}; - -/** - * Generic linked list of string values - */ - -typedef struct item_t{ - struct item_t* next; - char* value; -}ITEM; - -/** - * A link in a list of IP adresses and subnet masks - */ -typedef struct iprange_t{ - struct iprange_t* next; - uint32_t ip; - uint32_t mask; -}IPRANGE; - -/** - * The Firewall filter instance. - */ -typedef struct { - ITEM* columns; - ITEM* users; - IPRANGE* networks; - int column_count, column_size, user_count, user_size; - bool require_where[QUERY_TYPES]; - bool block_wildcard, whitelist_users,whitelist_networks; - -} FW_INSTANCE; - -/** - * The session structure for Firewall filter. - */ -typedef struct { - DOWNSTREAM down; - UPSTREAM up; - SESSION* session; -} FW_SESSION; /** * Implementation of the mandatory version entry point @@ -451,6 +458,7 @@ createInstance(char **options, FILTER_PARAMETER **params) return NULL; } int i; + my_instance->def_op = true; for(i = 0;params[i];i++){ if(strstr(params[i]->name,"rule")){ parse_rule(strip_tags(params[i]->value),my_instance); @@ -526,31 +534,26 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) my_session->down = *downstream; } -/** - * Checks if the packet contains an empty query error - * and if the session blocked the last query - * @param buf Buffer to inspect - * @param session Filter session object - * @return true if the error is the right one and the previous query was blocked - */ -bool is_dummy(GWBUF* buf,FW_SESSION* session) -{ - return(*((unsigned char*)buf->start + 4) == 0xff && - *((unsigned char*)buf->start + 5) == 0x29 && - *((unsigned char*)buf->start + 6) == 0x04); -} - /** * Generates a dummy error packet for the client. * @return The dummy packet or NULL if an error occurred */ -GWBUF* gen_dummy_error() +GWBUF* gen_dummy_error(FW_SESSION* session) { GWBUF* buf; - const char* errmsg = "Access denied."; - unsigned int errlen = strlen(errmsg), - pktlen = errlen + 9; + char errmsg[512]; + DCB* dcb = session->session->client; + MYSQL_session* mysql_session = (MYSQL_session*)session->session->data; + unsigned int errlen, pktlen; + + sprintf(errmsg,"Access denied for user '%s'@'%s' to database '%s' ", + dcb->user, + dcb->remote, + mysql_session->db); + errlen = strlen(errmsg); + pktlen = errlen + 9; buf = gwbuf_alloc(13 + errlen); + if(buf){ strcpy(buf->start + 7,"#HY000"); memcpy(buf->start + 13,errmsg,errlen); @@ -625,14 +628,30 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) match = false; modutil_extract_SQL(queue, &query, &len); where = skygw_get_where_clause(queue); - - if(my_instance->block_wildcard && + skygw_query_op_t queryop = query_classifier_get_operation(queue); + + if(where == NULL){ + if(my_instance->require_where[ALL] || + (my_instance->require_where[SELECT] && queryop == QUERY_OP_SELECT) || + (my_instance->require_where[UPADTE] && queryop == QUERY_OP_UPDATE) || + (my_instance->require_where[INSERT] && queryop == QUERY_OP_INSERT) || + (my_instance->require_where[DELETE] && queryop == QUERY_OP_DELETE)){ + match = true; + accept = false; + skygw_log_write(LOGFILE_TRACE, "query does not have a where clause, blocking it: %.*s",len,query); + + } + } + + if(!match && + my_instance->block_wildcard && ((where && strchr(where,'*') != NULL) || (memchr(query,'*',len) != NULL))){ match = true; accept = false; skygw_log_write(LOGFILE_TRACE, "query contains wildcard, blocking it: %.*s",len,query); - } + } + if(!match){ if(where == NULL){ where = malloc(sizeof(char)*len+1); @@ -653,6 +672,11 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } } + /**If no rules matched, do the default operation. (allow by default)*/ + if(!match){ + accept = my_instance->def_op; + } + if(accept){ return my_session->down.routeQuery(my_session->down.instance, @@ -660,7 +684,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) }else{ gwbuf_free(queue); - GWBUF* forward = gen_dummy_error(); + GWBUF* forward = gen_dummy_error(my_session); dcb->func.write(dcb,forward); //gwbuf_free(forward); return 0; From effe8f32971025eea9e5bca25492575e5a1c28b8 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 15 Oct 2014 15:23:14 +0300 Subject: [PATCH 07/31] Switched over to hashtables for users and columns. --- query_classifier/query_classifier.cc | 124 +++++++++++++----- query_classifier/query_classifier.h | 3 +- server/modules/filter/fwfilter.c | 182 +++++++++++++++++---------- 3 files changed, 211 insertions(+), 98 deletions(-) diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 5eedc31c0..40d5e7f91 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -1186,60 +1186,122 @@ bool is_drop_table_query(GWBUF* querybuf) lex->sql_command == SQLCOM_DROP_TABLE; } -char* skygw_get_where_clause(GWBUF* buf) +inline void add_str(char** buf, int* buflen, int* bufsize, char* str) +{ + int isize = strlen(str) + 1; + if(*buf == NULL || isize + *buflen >= *bufsize) + { + char *tmp = (char*)calloc((*bufsize) * 2 + isize, sizeof(char)); + if(tmp){ + memcpy(tmp,*buf,*bufsize); + if(*buf){ + free(*buf); + } + *buf = tmp; + *bufsize = (*bufsize) * 2 + isize; + } + } + + if(*buflen > 0){ + strcat(*buf," "); + } + strcat(*buf,str); + *buflen += isize; + +} + + +/** + * Returns all the fields that the query affects. + * @param buf Buffer to parse + * @return Pointer to newly allocated string or NULL if nothing was found + */ +char* skygw_get_affected_fields(GWBUF* buf) { LEX* lex; - unsigned int buffsz = 0,bufflen = 0; + int buffsz = 0,bufflen = 0; char* where = NULL; - Item* item; + Item* item; + Item::Type itype; + if(!query_is_parsed(buf)){ parse_query(buf); } + if((lex = get_lex(buf)) == NULL){ return NULL; } - - lex->current_select = lex->all_selects_list; - + + lex->current_select = lex->all_selects_list; + while(lex->current_select) { + + List_iterator ilist(lex->current_select->item_list); + item = (Item*)ilist.next(); + for (item; item != NULL; item=(Item*)ilist.next()) + { + + itype = item->type(); + if(item->name && itype == Item::FIELD_ITEM){ + add_str(&where,&buffsz,&bufflen,item->name); + } + } + + if(lex->current_select->where){ for (item=lex->current_select->where; item != NULL; item=item->next) { - Item::Type tp = item->type(); - if(item->name && tp == Item::FIELD_ITEM){ - - int isize = strlen(item->name) + 1; - if(where == NULL || isize + bufflen >= buffsz) - { - char *tmp = (char*)calloc(buffsz*2 + isize,sizeof(char)); - if(tmp){ - memcpy(tmp,where,buffsz); - if(where){ - free(where); - } - where = tmp; - buffsz = buffsz*2 + isize; - }else{ - return NULL; - } - } - - if(bufflen > 0){ - strcat(where," "); - } - strcat(where,item->name); - bufflen += isize; - + itype = item->type(); + if(item->name && itype == Item::FIELD_ITEM){ + add_str(&where,&buffsz,&bufflen,item->name); } } } + + if(lex->current_select->having){ + for (item=lex->current_select->having; item != NULL; item=item->next) + { + + itype = item->type(); + if(item->name && itype == Item::FIELD_ITEM){ + add_str(&where,&buffsz,&bufflen,item->name); + } + } + } + lex->current_select = lex->current_select->next_select_in_list(); } return where; } +bool skygw_query_has_clause(GWBUF* buf) +{ + LEX* lex; + bool clause = false; + + if(!query_is_parsed(buf)){ + parse_query(buf); + } + + if((lex = get_lex(buf)) == NULL){ + return false; + } + + lex->current_select = lex->all_selects_list; + + while(lex->current_select) + { + if(lex->current_select->where || lex->current_select->having){ + clause = true; + } + + lex->current_select = lex->current_select->next_select_in_list(); + } + return clause; +} + /* * Replace user-provided literals with question marks. Return a copy of the * querystr with replacements. diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index eb0e6d4b9..77230d4b6 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -100,8 +100,9 @@ bool parse_query (GWBUF* querybuf); parsing_info_t* parsing_info_init(void (*donefun)(void *)); void parsing_info_done(void* ptr); bool query_is_parsed(GWBUF* buf); +bool skygw_query_has_clause(GWBUF* buf); char* skygw_get_qtype_str(skygw_query_type_t qtype); -char* skygw_get_where_clause(GWBUF* buf); +char* skygw_get_affected_fields(GWBUF* buf); EXTERN_C_BLOCK_END diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 6807d863a..0ea1892b7 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -73,21 +73,27 @@ static FILTER_OBJECT MyObject = { diagnostic, }; - +#define QUERY_TYPES 5 /** * Query types */ - -#define QUERY_TYPES 5 - -enum querytype_t{ +typedef enum{ ALL, SELECT, INSERT, UPDATE, DELETE -}; +}querytype_t; + +/** + * Rule types + */ +typedef enum { + RT_UNDEFINED, + RT_USER, + RT_COLUMN +}ruletype_t; /** * Generic linked list of string values @@ -111,8 +117,7 @@ typedef struct iprange_t{ * The Firewall filter instance. */ typedef struct { - ITEM* columns; - ITEM* users; + HASHTABLE* htable; IPRANGE* networks; int column_count, column_size, user_count, user_size; bool require_where[QUERY_TYPES]; @@ -129,6 +134,45 @@ typedef struct { SESSION* session; } FW_SESSION; +static int hashkeyfun(void* key); +static int hashcmpfun (void *, void *); + +static int hashkeyfun( + void* key) +{ + if(key == NULL){ + return 0; + } + unsigned int hash = 0,c = 0; + char* ptr = (char*)key; + while((c = *ptr++)){ + hash = c + (hash << 6) + (hash << 16) - hash; + } + return (int)hash > 0 ? hash : -hash; +} + +static int hashcmpfun( + void* v1, + void* v2) +{ + char* i1 = (char*) v1; + char* i2 = (char*) v2; + + return strcmp(i1,i2); +} + +static void* hstrdup(void* fval) +{ + char* str = (char*)fval; + return strdup(str); +} + + +static void* hfree(void* fval) +{ + free (fval); + return NULL; +} /** * Utility function to check if a string contains a valid IP address. @@ -308,6 +352,7 @@ uint32_t strtosubmask(char* str) } + /** * Implementation of the mandatory version entry point * @@ -369,11 +414,10 @@ void parse_rule(char* rule, FW_INSTANCE* instance) instance->networks = rng; } - }else{ /**Add usernames or columns*/ + }else{ /**Add rules on usernames or columns*/ char *tok = strtok(ptr," ,\0"); - ITEM* prev = NULL; - bool is_user = false, is_column = false; + bool is_user = false, is_column = false, is_time = false; if(strcmp(tok,"wildcard") == 0){ instance->block_wildcard = block ? true : false; @@ -381,11 +425,9 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } if(strcmp(tok,"users") == 0){/**Adding users*/ - prev = instance->users; instance->whitelist_users = mode; is_user = true; }else if(strcmp(tok,"columns") == 0){/**Adding Columns*/ - prev = instance->columns; is_column = true; } @@ -393,21 +435,17 @@ void parse_rule(char* rule, FW_INSTANCE* instance) if(is_user || is_column){ while(tok){ - - ITEM* item = calloc(1,sizeof(ITEM)); - if(item){ - item->next = prev; - item->value = strdup(tok); - prev = item; - } + + /**Add value to hashtable*/ + + ruletype_t rtype = is_user ? RT_USER : is_column ? RT_COLUMN: RT_UNDEFINED; + hashtable_add(instance->htable, + (void *)tok, + (void *)rtype); + tok = strtok(NULL," ,\0"); } - if(is_user){ - instance->users = prev; - }else if(is_column){ - instance->columns = prev; - } } } @@ -458,7 +496,18 @@ createInstance(char **options, FILTER_PARAMETER **params) return NULL; } int i; + HASHTABLE* ht; + + if((ht = hashtable_alloc(7, hashkeyfun, hashcmpfun)) == NULL){ + skygw_log_write(LOGFILE_ERROR, "Unable to allocate hashtable."); + return NULL; + } + + hashtable_memory_fns(ht,hstrdup,NULL,hfree,NULL); + + my_instance->htable = ht; my_instance->def_op = true; + for(i = 0;params[i];i++){ if(strstr(params[i]->name,"rule")){ parse_rule(strip_tags(params[i]->value),my_instance); @@ -584,23 +633,22 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; IPRANGE* ipranges = my_instance->networks; - ITEM *users = my_instance->users, *columns = my_instance->columns; bool accept = false, match = false; - char *where,*query; + char *where; uint32_t ip; - int len; + ruletype_t rtype = RT_UNDEFINED; + skygw_query_op_t queryop; DCB* dcb = my_session->session->client; ip = strtoip(dcb->remote); - - while(users){ - if(strcmp(dcb->user,users->value)==0){ - match = true; - accept = my_instance->whitelist_users; - skygw_log_write(LOGFILE_TRACE, "%s@%s was %s.", - dcb->user,dcb->remote,(my_instance->whitelist_users ? "allowed":"blocked")); - break; - } - users = users->next; + + rtype = (ruletype_t)hashtable_fetch(my_instance->htable, dcb->user); + if(rtype == RT_USER){ + match = true; + accept = my_instance->whitelist_users; + skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", + dcb->user, dcb->remote, + (my_instance->whitelist_users ? + "allowed":"blocked")); } if(!match){ @@ -608,7 +656,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ match = true; accept = my_instance->whitelist_networks; - skygw_log_write(LOGFILE_TRACE, "%s@%s was %s.", + skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"blocked")); break; } @@ -626,11 +674,11 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(skygw_is_real_query(queue)){ match = false; - modutil_extract_SQL(queue, &query, &len); - where = skygw_get_where_clause(queue); - skygw_query_op_t queryop = query_classifier_get_operation(queue); + + if(!skygw_query_has_clause(queue)){ + + queryop = query_classifier_get_operation(queue); - if(where == NULL){ if(my_instance->require_where[ALL] || (my_instance->require_where[SELECT] && queryop == QUERY_OP_SELECT) || (my_instance->require_where[UPDATE] && queryop == QUERY_OP_UPDATE) || @@ -638,37 +686,39 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) (my_instance->require_where[DELETE] && queryop == QUERY_OP_DELETE)){ match = true; accept = false; - skygw_log_write(LOGFILE_TRACE, "query does not have a where clause, blocking it: %.*s",len,query); - + skygw_log_write(LOGFILE_TRACE, "Firewall: query does not have a where clause or a having clause, blocking it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); } } - if(!match && - my_instance->block_wildcard && - ((where && strchr(where,'*') != NULL) || - (memchr(query,'*',len) != NULL))){ - match = true; - accept = false; - skygw_log_write(LOGFILE_TRACE, "query contains wildcard, blocking it: %.*s",len,query); - } - if(!match){ - if(where == NULL){ - where = malloc(sizeof(char)*len+1); - memcpy(where,query,len); - memset(where+len,0,1); - } - while(columns){ - if(strstr(where,columns->value)){ + + where = skygw_get_affected_fields(queue); + + if(my_instance->block_wildcard && + where && strchr(where,'*') != NULL) + { match = true; accept = false; - skygw_log_write(LOGFILE_TRACE, "query contains a forbidden column %s, blocking it: %.*s",columns->value,len,query); - break; + skygw_log_write(LOGFILE_TRACE, "Firewall: query contains wildcard, blocking it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); } - columns = columns->next; - } + else if(where) + { + char* tok = strtok(where," "); + + while(tok){ + rtype = (ruletype_t)hashtable_fetch(my_instance->htable, tok); + if(rtype == RT_COLUMN){ + match = true; + accept = false; + skygw_log_write(LOGFILE_TRACE, "Firewall: query contains a forbidden column %s, blocking it: %.*s",tok,GWBUF_LENGTH(queue),(char*)(queue->start + 5)); + } + tok = strtok(NULL," "); + } + + } + free(where); } - free(where); + } } From 11c2d4dfe451dbe19e23e3fbb26af067ffc08ce8 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 16 Oct 2014 13:47:06 +0300 Subject: [PATCH 08/31] Added a rule to restrict queries during certain hours of the day --- server/modules/filter/fwfilter.c | 270 +++++++++++++++++++++++++------ 1 file changed, 218 insertions(+), 52 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 0ea1892b7..aa373b8e1 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -38,7 +38,8 @@ #include #include #include - +#include +#include MODULE_INFO info = { MODULE_API_FILTER, @@ -92,20 +93,18 @@ typedef enum{ typedef enum { RT_UNDEFINED, RT_USER, - RT_COLUMN + RT_COLUMN, + RT_TIME }ruletype_t; -/** - * Generic linked list of string values - */ - -typedef struct item_t{ - struct item_t* next; - char* value; -}ITEM; +typedef struct timerange_t{ + struct timerange_t* next; + struct tm start; + struct tm end; +}TIMERANGE; /** - * A link in a list of IP adresses and subnet masks + * Linked list of IP adresses and subnet masks */ typedef struct iprange_t{ struct iprange_t* next; @@ -117,9 +116,9 @@ typedef struct iprange_t{ * The Firewall filter instance. */ typedef struct { - HASHTABLE* htable; + HASHTABLE* htable; /**Usernames and forbidden columns*/ IPRANGE* networks; - int column_count, column_size, user_count, user_size; + TIMERANGE* times; bool require_where[QUERY_TYPES]; bool block_wildcard, whitelist_users,whitelist_networks,def_op; @@ -138,40 +137,40 @@ static int hashkeyfun(void* key); static int hashcmpfun (void *, void *); static int hashkeyfun( - void* key) + void* key) { - if(key == NULL){ - return 0; - } - unsigned int hash = 0,c = 0; - char* ptr = (char*)key; - while((c = *ptr++)){ - hash = c + (hash << 6) + (hash << 16) - hash; - } + if(key == NULL){ + return 0; + } + unsigned int hash = 0,c = 0; + char* ptr = (char*)key; + while((c = *ptr++)){ + hash = c + (hash << 6) + (hash << 16) - hash; + } return (int)hash > 0 ? hash : -hash; } static int hashcmpfun( - void* v1, - void* v2) + void* v1, + void* v2) { - char* i1 = (char*) v1; - char* i2 = (char*) v2; + char* i1 = (char*) v1; + char* i2 = (char*) v2; - return strcmp(i1,i2); + return strcmp(i1,i2); } static void* hstrdup(void* fval) { - char* str = (char*)fval; - return strdup(str); + char* str = (char*)fval; + return strdup(str); } static void* hfree(void* fval) { - free (fval); - return NULL; + free (fval); + return NULL; } /** @@ -182,6 +181,8 @@ static void* hfree(void* fval) */ bool valid_ip(char* str) { + assert(str != NULL); + int octval = 0; bool valid = true; char cmpbuff[32]; @@ -226,10 +227,16 @@ bool valid_ip(char* str) */ char* strip_tags(char* str) { + assert(str != NULL); + char *ptr = str, *lead = str, *tail = NULL; int len = 0; while(*ptr != '\0'){ - if(isalnum(*ptr) || *ptr == '.' || *ptr == '/'){ + if(isalnum(*ptr) || + *ptr == '.' || + *ptr == '/' || + *ptr == ':' || + *ptr == '-' ){ ptr++; continue; } @@ -260,6 +267,8 @@ char* strip_tags(char* str) */ int get_octet(char* str) { + assert(str != NULL); + int octval = 0,retval = -1; bool valid = false; char cmpbuff[32]; @@ -312,6 +321,8 @@ int get_octet(char* str) */ uint32_t strtoip(char* str) { + assert(str != NULL); + uint32_t ip = 0,octet = 0; char* tok = str; if(!valid_ip(str)){ @@ -337,6 +348,8 @@ uint32_t strtoip(char* str) */ uint32_t strtosubmask(char* str) { + assert(str != NULL); + uint32_t mask = 0; char *ptr; @@ -351,7 +364,98 @@ uint32_t strtosubmask(char* str) return ~mask; } +/** + * Checks whether a null-terminated string contains two ISO-8601 compliant times separated + * by a single dash. + * @param str String to check + * @return True if the string is valid + */ +bool check_time(char* str) +{ + assert(str != NULL); + char* ptr = str; + int colons = 0,numbers = 0,dashes = 0; + while(*ptr){ + if(isdigit(*ptr)){numbers++;} + else if(*ptr == ':'){colons++;} + else if(*ptr == '-'){dashes++;} + ptr++; + } + return numbers == 12 && colons == 4 && dashes == 1; +} + +#define CHK_TIMES(t)(assert(t->tm_sec > -1 && t->tm_sec < 62 \ + && t->tm_min > -1 && t->tm_min < 60 \ + && t->tm_hour > -1 && t->tm_hour < 24)) + +/** + * Parses a null-terminated string into two time_t structs and adds the + * TIMERANGE into the FW_FILTER instance. + * @param str String to parse + * @param instance FW_FILTER instance + */ +void parse_time(char* str, FW_INSTANCE* instance) +{ + + TIMERANGE* tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + int intbuffer[3]; + int* idest = intbuffer; + char strbuffer[3]; + char *ptr,*sdest; + struct tm* tmptr; + + assert(str != NULL && tr != NULL && instance != NULL); + + memset(&tr->start,0,sizeof(struct tm)); + memset(&tr->end,0,sizeof(struct tm)); + ptr = str; + sdest = strbuffer; + tmptr = &tr->start; + tr->next = instance->times; + instance->times = tr; + + while(ptr - str < 19){ + if(isdigit(*ptr)){ + *sdest = *ptr; + }else if(*ptr == ':' ||*ptr == '-' || *ptr == '\0'){ + *sdest = '\0'; + *idest++ = atoi(strbuffer); + sdest = strbuffer; + + if(*ptr == '-' || *ptr == '\0'){ + + tmptr->tm_hour = intbuffer[0]; + tmptr->tm_min = intbuffer[1]; + tmptr->tm_sec = intbuffer[2]; + + CHK_TIMES(tmptr); + + idest = intbuffer; + tmptr = &tr->end; + } + ptr++; + continue; + } + ptr++; + sdest++; + } + + /**The timerange is reversed*/ + if(mktime(&tr->end) < mktime(&tr->start)){ + tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + tr->next = instance->times; + tr->start.tm_hour = 0; + tr->start.tm_min = 0; + tr->start.tm_sec = 0; + tr->end = instance->times->end; + instance->times->end.tm_hour = 23; + instance->times->end.tm_min = 59; + instance->times->end.tm_sec = 59; + instance->times = tr; + } + +} /** * Implementation of the mandatory version entry point @@ -374,13 +478,13 @@ ModuleInit() } /** -* The module entry point routine. It is this routine that -* must populate the structure that is referred to as the -* "module object", this is a structure with the set of -* external entry points for this module. -* -* @return The module object -*/ + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ FILTER_OBJECT * GetModuleObject() { @@ -403,7 +507,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } ptr++; - if(valid_ip(ptr)){ /**Add IP address range*/ + if(valid_ip(ptr)){ /**Add IP address range*/ instance->whitelist_networks = mode; IPRANGE* rng = calloc(1,sizeof(IPRANGE)); @@ -429,20 +533,33 @@ void parse_rule(char* rule, FW_INSTANCE* instance) is_user = true; }else if(strcmp(tok,"columns") == 0){/**Adding Columns*/ is_column = true; + }else if(strcmp(tok,"times") == 0){ + is_time = true; } tok = strtok(NULL," ,\0"); - if(is_user || is_column){ + if(is_user || is_column || is_time){ while(tok){ /**Add value to hashtable*/ - ruletype_t rtype = is_user ? RT_USER : is_column ? RT_COLUMN: RT_UNDEFINED; - hashtable_add(instance->htable, - (void *)tok, - (void *)rtype); + ruletype_t rtype = + is_user ? RT_USER : + is_column ? RT_COLUMN: + is_time ? RT_TIME : + RT_UNDEFINED; + if(rtype == RT_USER || rtype == RT_COLUMN) + { + hashtable_add(instance->htable, + (void *)tok, + (void *)rtype); + } + else if(rtype == RT_TIME && check_time(tok)) + { + parse_time(tok,instance); + } tok = strtok(NULL," ,\0"); } @@ -595,14 +712,24 @@ GWBUF* gen_dummy_error(FW_SESSION* session) MYSQL_session* mysql_session = (MYSQL_session*)session->session->data; unsigned int errlen, pktlen; - sprintf(errmsg,"Access denied for user '%s'@'%s' to database '%s' ", - dcb->user, - dcb->remote, - mysql_session->db); + if(mysql_session->db[0] == '\0') + { + sprintf(errmsg, + "Access denied for user '%s'@'%s'", + dcb->user, + dcb->remote); + }else + { + sprintf(errmsg, + "Access denied for user '%s'@'%s' to database '%s' ", + dcb->user, + dcb->remote, + mysql_session->db); + } errlen = strlen(errmsg); pktlen = errlen + 9; buf = gwbuf_alloc(13 + errlen); - + if(buf){ strcpy(buf->start + 7,"#HY000"); memcpy(buf->start + 13,errmsg,errlen); @@ -633,15 +760,25 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; IPRANGE* ipranges = my_instance->networks; + TIMERANGE* times = my_instance->times; + time_t time_now; + struct tm* tm_now; + struct tm tm_before,tm_after; bool accept = false, match = false; char *where; uint32_t ip; ruletype_t rtype = RT_UNDEFINED; skygw_query_op_t queryop; DCB* dcb = my_session->session->client; - ip = strtoip(dcb->remote); - + + + time(&time_now); + tm_now = localtime(&time_now); + memcpy(&tm_before,tm_now,sizeof(struct tm)); + memcpy(&tm_after,tm_now,sizeof(struct tm)); + rtype = (ruletype_t)hashtable_fetch(my_instance->htable, dcb->user); + if(rtype == RT_USER){ match = true; accept = my_instance->whitelist_users; @@ -652,6 +789,9 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } if(!match){ + + ip = strtoip(dcb->remote); + while(ipranges){ if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ match = true; @@ -663,6 +803,32 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) ipranges = ipranges->next; } } + + while(times){ + + tm_before.tm_sec = times->start.tm_sec; + tm_before.tm_min = times->start.tm_min; + tm_before.tm_hour = times->start.tm_hour; + tm_after.tm_sec = times->end.tm_sec; + tm_after.tm_min = times->end.tm_min; + tm_after.tm_hour = times->end.tm_hour; + + + time_t before = mktime(&tm_before); + time_t after = mktime(&tm_after); + time_t now = mktime(tm_now); + double to_before = difftime(now,before); + double to_after = difftime(now,after); + /**Restricted time*/ + if(to_before > 0.0 && to_after < 0.0){ + match = true; + accept = false; + skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered during restricted time: %s.",asctime(tm_now)); + break; + } + + times = times->next; + } if(modutil_is_SQL(queue)){ From 8c55d0b0cb84cacd7462519069e562e85e5a5cf7 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 19 Oct 2014 07:05:45 +0300 Subject: [PATCH 09/31] Added documentation about rule syntax. --- server/modules/filter/fwfilter.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index aa373b8e1..b45ee97de 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -21,6 +21,18 @@ * Firewall Filter * * A filter that acts as a firewall, blocking queries that do not meet the set requirements. + * + * This filter uses "rules" to define the blcking parameters. To configure rules into the configuration file, + * give each rule a unique name and assing the rule contents by passing a string enclosed in quotes. + * + * For example, to block users John and Jane and all users from address range 192.168.0.0/24, the following is needed in the configuration file: + * rule1="block users John Jane" + * rule2="block 192.168.0.0/255.255.255.0" + * + * Rule syntax + * + * [block|allow] [users|columns|times] value ... Blocks or allows the given list of values interpreted as certain type + * require where [all|select|update|insert|delete] ... Requires a where clause for the list of query types */ #include #include From 67101278ac950738cec66440419890d67b741e5f Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 24 Oct 2014 08:43:51 +0300 Subject: [PATCH 10/31] Redid the rules to make it easier to apply different rules to different users --- server/modules/filter/fwfilter.c | 537 ++++++++++++++++++++++++------- 1 file changed, 421 insertions(+), 116 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index b45ee97de..36e10dc67 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -20,19 +20,25 @@ * @file fwfilter.c * Firewall Filter * - * A filter that acts as a firewall, blocking queries that do not meet the set requirements. + * A filter that acts as a firewall, denying queries that do not meet the set requirements. * * This filter uses "rules" to define the blcking parameters. To configure rules into the configuration file, * give each rule a unique name and assing the rule contents by passing a string enclosed in quotes. * - * For example, to block users John and Jane and all users from address range 192.168.0.0/24, the following is needed in the configuration file: - * rule1="block users John Jane" - * rule2="block 192.168.0.0/255.255.255.0" + * For example, to define a rule denying users from accessing the column 'salary', the following is needed in the configuration file: * - * Rule syntax + * rule1="rule block_salary deny columns salary" * - * [block|allow] [users|columns|times] value ... Blocks or allows the given list of values interpreted as certain type - * require where [all|select|update|insert|delete] ... Requires a where clause for the list of query types + * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: + * + * rule2="users John@% Jane@192.168.0.1 rules block_salary" + * + * Rule syntax TODO: implement timeranges, query type restrictions + * + * rule NAME deny|allow|require + * [wildcard|columns VALUE ...] + * [times VALUE...] + * [on_queries [all|select|update|delete|insert]...] */ #include #include @@ -73,7 +79,6 @@ static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstre static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); - static FILTER_OBJECT MyObject = { createInstance, newSession, @@ -106,15 +111,45 @@ typedef enum { RT_UNDEFINED, RT_USER, RT_COLUMN, - RT_TIME + RT_TIME, + RT_WILDCARD }ruletype_t; +/** + * Linked list of strings. + */ +typedef struct strlink_t{ + struct strlink_t *next; + char* value; +}STRLINK; + + typedef struct timerange_t{ struct timerange_t* next; struct tm start; struct tm end; }TIMERANGE; +/** + * A structure used to identify individual rules and to store their contents + * + * Each type of rule has different requirements that are expressed as void pointers. + * This allows to match an arbitrary set of rules against a user. + */ +typedef struct rule_t{ + void* data; + char* name; + ruletype_t type; +}RULE; + +/** + * Linked list of pointers to a global pool of RULE structs + */ +typedef struct rulelist_t{ + RULE* rule; + struct rulelist_t* next; +}RULELIST; + /** * Linked list of IP adresses and subnet masks */ @@ -129,10 +164,12 @@ typedef struct iprange_t{ */ typedef struct { HASHTABLE* htable; /**Usernames and forbidden columns*/ + RULELIST* rules; IPRANGE* networks; TIMERANGE* times; + STRLINK* userstrings; bool require_where[QUERY_TYPES]; - bool block_wildcard, whitelist_users,whitelist_networks,def_op; + bool deny_wildcard, whitelist_users,whitelist_networks,whitelist_times,def_op; } FW_INSTANCE; @@ -179,12 +216,46 @@ static void* hstrdup(void* fval) } -static void* hfree(void* fval) +static void* hstrfree(void* fval) { free (fval); return NULL; } +static void* hruledup(void* fval) +{ + + if(fval == NULL){ + return NULL; + } + + RULELIST *rule = NULL, + *ptr = (RULELIST*)fval; + + while(ptr){ + RULELIST* tmp = (RULELIST*)malloc(sizeof(RULELIST)); + tmp->next = rule; + tmp->rule = ptr->rule; + rule = tmp; + ptr = ptr->next; + } + + return rule; +} + + +static void* hrulefree(void* fval) +{ + RULELIST *ptr = (RULELIST*)fval,*tmp; + while(ptr){ + tmp = ptr; + ptr = ptr->next; + free(tmp); + } + return NULL; +} + + /** * Utility function to check if a string contains a valid IP address. * The string handled as a null-terminated string. @@ -244,15 +315,13 @@ char* strip_tags(char* str) char *ptr = str, *lead = str, *tail = NULL; int len = 0; while(*ptr != '\0'){ - if(isalnum(*ptr) || - *ptr == '.' || - *ptr == '/' || - *ptr == ':' || - *ptr == '-' ){ - ptr++; - continue; + + if(*ptr == '"' || + *ptr == '\''){ + *ptr = ' '; } - *ptr++ = ' '; + ptr++; + } /**Strip leading and trailing whitespace*/ @@ -401,31 +470,39 @@ bool check_time(char* str) && t->tm_min > -1 && t->tm_min < 60 \ && t->tm_hour > -1 && t->tm_hour < 24)) +#define IS_RVRS_TIME(tr) (mktime(&tr->end) < mktime(&tr->start)) /** - * Parses a null-terminated string into two time_t structs and adds the - * TIMERANGE into the FW_FILTER instance. + * Parses a null-terminated string into two tm_t structs that mark a timerange * @param str String to parse * @param instance FW_FILTER instance + * @return If successful returns a pointer to the new TIMERANGE instance. If errors occurred or + * the timerange was invalid, a NULL pointer is returned. */ -void parse_time(char* str, FW_INSTANCE* instance) +TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) { - TIMERANGE* tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + TIMERANGE* tr = NULL; int intbuffer[3]; int* idest = intbuffer; char strbuffer[3]; char *ptr,*sdest; struct tm* tmptr; - assert(str != NULL && tr != NULL && instance != NULL); + assert(str != NULL && instance != NULL); + + tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + + if(tr == NULL){ + skygw_log_write(LOGFILE_ERROR, "fwfilter error: malloc returned NULL."); + return NULL; + } + memset(&tr->start,0,sizeof(struct tm)); memset(&tr->end,0,sizeof(struct tm)); ptr = str; sdest = strbuffer; tmptr = &tr->start; - tr->next = instance->times; - instance->times = tr; while(ptr - str < 19){ if(isdigit(*ptr)){ @@ -453,20 +530,33 @@ void parse_time(char* str, FW_INSTANCE* instance) sdest++; } - /**The timerange is reversed*/ - if(mktime(&tr->end) < mktime(&tr->start)){ - tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); - tr->next = instance->times; - tr->start.tm_hour = 0; - tr->start.tm_min = 0; - tr->start.tm_sec = 0; - tr->end = instance->times->end; - instance->times->end.tm_hour = 23; - instance->times->end.tm_min = 59; - instance->times->end.tm_sec = 59; - instance->times = tr; - } + + return tr; +} + +/** + * Splits the reversed timerange into two. + *@param tr A reversed timerange + *@return If the timerange is reversed, returns a pointer to the new TIMERANGE otherwise returns a NULL pointer + */ +TIMERANGE* split_reverse_time(TIMERANGE* tr) +{ + TIMERANGE* tmp = NULL; + + if(IS_RVRS_TIME(tr)){ + tmp = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + tmp->next = tr; + tmp->start.tm_hour = 0; + tmp->start.tm_min = 0; + tmp->start.tm_sec = 0; + tmp->end = tr->end; + tr->end.tm_hour = 23; + tr->end.tm_min = 59; + tr->end.tm_sec = 59; + } + + return tmp; } /** @@ -503,80 +593,210 @@ GetModuleObject() return &MyObject; } -void parse_rule(char* rule, FW_INSTANCE* instance) +/** + * Finds the rule with a name matching the passed string. + * + * @param tok Name to search for + * @param instance A valid FW_FILTER instance + * @return A pointer to the matching RULE if found, else returns NULL + */ +RULE* find_rule(char* tok, FW_INSTANCE* instance) { - char* ptr = rule; - bool allow,block,mode; + RULELIST* rlist = instance->rules; - /**IP range rules*/ - if((allow = (strstr(rule,"allow") != NULL)) || - (block = (strstr(rule,"block") != NULL))){ - - mode = allow ? true:false; - - if((ptr = strchr(rule,' ')) == NULL){ - return; + while(rlist){ + if(strcmp(rlist->rule->name,tok) == 0){ + return rlist->rule; } - ptr++; + rlist = rlist->next; + } + return NULL; +} - if(valid_ip(ptr)){ /**Add IP address range*/ +void add_users(char* rule, FW_INSTANCE* instance) +{ + assert(rule != NULL && instance != NULL); - instance->whitelist_networks = mode; - IPRANGE* rng = calloc(1,sizeof(IPRANGE)); - if(rng){ - rng->ip = strtoip(ptr); - rng->mask = strtosubmask(ptr); - rng->next = instance->networks; - instance->networks = rng; - } + STRLINK* link = malloc(sizeof(STRLINK)); + link->next = instance->userstrings; + link->value = strdup(rule); + instance->userstrings = link; +} - }else{ /**Add rules on usernames or columns*/ - - char *tok = strtok(ptr," ,\0"); - bool is_user = false, is_column = false, is_time = false; - - if(strcmp(tok,"wildcard") == 0){ - instance->block_wildcard = block ? true : false; - return; - } +void link_rules(char* rule, FW_INSTANCE* instance) +{ + assert(rule != NULL && instance != NULL); + + /**Apply rules to users*/ + + char *tok, *ruleptr, *userptr; + RULELIST* rulelist = NULL; - if(strcmp(tok,"users") == 0){/**Adding users*/ - instance->whitelist_users = mode; - is_user = true; - }else if(strcmp(tok,"columns") == 0){/**Adding Columns*/ - is_column = true; - }else if(strcmp(tok,"times") == 0){ - is_time = true; - } + userptr = strstr(rule,"users"); + ruleptr = strstr(rule,"rules"); + + if(userptr == NULL || ruleptr == NULL) { + /**No rules to apply were found*/ + skygw_log_write(LOGFILE_TRACE, "Rule syntax was not proper, 'user' or 'rules' was found but not the other: %s",rule); + return; + } - tok = strtok(NULL," ,\0"); - - if(is_user || is_column || is_time){ - while(tok){ - - /**Add value to hashtable*/ - - ruletype_t rtype = - is_user ? RT_USER : - is_column ? RT_COLUMN: - is_time ? RT_TIME : - RT_UNDEFINED; - - if(rtype == RT_USER || rtype == RT_COLUMN) - { - hashtable_add(instance->htable, - (void *)tok, - (void *)rtype); - } - else if(rtype == RT_TIME && check_time(tok)) - { - parse_time(tok,instance); - } - tok = strtok(NULL," ,\0"); + tok = strtok(ruleptr," ,"); + tok = strtok(NULL," ,"); + + while(tok) + { + RULE* rule_found = NULL; + + if((rule_found = find_rule(tok,instance)) != NULL) + { + RULELIST* tmp_rl = (RULELIST*)calloc(1,sizeof(RULELIST)); + tmp_rl->rule = rule_found; + tmp_rl->next = rulelist; + rulelist = tmp_rl; } + tok = strtok(NULL," ,"); + } + + /** + * Apply this list of rules to all the listed users + */ + + *(ruleptr) = '\0'; + userptr = strtok(rule," ,"); + userptr = strtok(NULL," ,"); + + while(userptr) + { + if(hashtable_add(instance->htable, + (void *)userptr, + (void *)rulelist) == 0) + { + skygw_log_write(LOGFILE_TRACE, "Name conflict in fwfilter: %s was found twice.",tok); + } + + userptr = strtok(NULL," ,"); + + } + +} + +void parse_rule(char* rule, FW_INSTANCE* instance) +{ + assert(rule != NULL && instance != NULL); + + char *rulecpy = strdup(rule); + char *ptr = rule; + char *tok = strtok(rulecpy," ,"); + bool allow,deny,mode; + RULE* ruledef = NULL; + + if(tok == NULL) goto retblock; + + if(strcmp("rule",tok) == 0){ /**Define a new rule*/ + + tok = strtok(NULL," ,"); + + if(tok == NULL) goto retblock; + + RULELIST* rlist = NULL; + + ruledef = (RULE*)malloc(sizeof(RULE)); + rlist = (RULELIST*)malloc(sizeof(RULELIST)); + ruledef->name = strdup(tok); + ruledef->type = RT_UNDEFINED; + ruledef->data = NULL; + rlist->rule = ruledef; + rlist->next = instance->rules; + instance->rules = rlist; + + }else if(strcmp("users",tok) == 0){ + + /**Apply rules to users*/ + add_users(rule, instance); + goto retblock; + } + + tok = strtok(NULL, " ,"); + + /**IP range rules*/ + if((allow = (strcmp(tok,"allow") == 0)) || + (deny = (strcmp(tok,"deny") == 0))){ + + mode = allow ? true:false; + tok = strtok(NULL, " ,"); + + + bool is_user = false, is_column = false, is_time = false; + + if(strcmp(tok,"wildcard") == 0) + { + ruledef->type = RT_WILDCARD; + ruledef->data = (void*)mode; + } + else if(strcmp(tok,"columns") == 0) + { + STRLINK *tail = NULL,*current; + ruledef->type = RT_COLUMN; + tok = strtok(NULL, " ,"); + while(tok){ + current = malloc(sizeof(STRLINK)); + current->value = strdup(tok); + current->next = tail; + tail = current; + tok = strtok(NULL, " ,"); + } + + ruledef->data = (void*)tail; + + } + else if(strcmp(tok,"times") == 0) + { + + tok = strtok(NULL, " ,"); + ruledef->type = RT_TIME; + + TIMERANGE *tr = parse_time(tok,instance); + + if(IS_RVRS_TIME(tr)){ + TIMERANGE *tmptr = split_reverse_time(tr); + tr = tmptr; + } + ruledef->data = (void*)tr; + + } + + goto retblock; + + tok = strtok(NULL," ,\0"); + + if(is_user || is_column || is_time){ + while(tok){ + + /**Add value to hashtable*/ + + ruletype_t rtype = + is_user ? RT_USER : + is_column ? RT_COLUMN: + is_time ? RT_TIME : + RT_UNDEFINED; + + if(rtype == RT_USER || rtype == RT_COLUMN) + { + hashtable_add(instance->htable, + (void *)tok, + (void *)rtype); + } + else if(rtype == RT_TIME && check_time(tok)) + { + parse_time(tok,instance); + } + tok = strtok(NULL," ,\0"); + } } + }else if((ptr = strstr(rule,"require")) != NULL){ @@ -605,7 +825,8 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } } - + retblock: + free(rulecpy); } /** @@ -626,13 +847,14 @@ createInstance(char **options, FILTER_PARAMETER **params) } int i; HASHTABLE* ht; + STRLINK *ptr,*tmp; if((ht = hashtable_alloc(7, hashkeyfun, hashcmpfun)) == NULL){ skygw_log_write(LOGFILE_ERROR, "Unable to allocate hashtable."); return NULL; } - hashtable_memory_fns(ht,hstrdup,NULL,hfree,NULL); + hashtable_memory_fns(ht,hstrdup,hruledup,hstrfree,hrulefree); my_instance->htable = ht; my_instance->def_op = true; @@ -642,6 +864,17 @@ createInstance(char **options, FILTER_PARAMETER **params) parse_rule(strip_tags(params[i]->value),my_instance); } } + + /**Apply the rules to users*/ + ptr = my_instance->userstrings; + while(ptr){ + link_rules(ptr->value,my_instance); + tmp = ptr; + ptr = ptr->next; + free(tmp->value); + free(tmp); + } + return (FILTER *)my_instance; } @@ -778,11 +1011,79 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) struct tm tm_before,tm_after; bool accept = false, match = false; char *where; + char username[128]; uint32_t ip; ruletype_t rtype = RT_UNDEFINED; skygw_query_op_t queryop; DCB* dcb = my_session->session->client; + RULELIST* rulelist = NULL; + STRLINK* strln = NULL; + sprintf(username,"%s@%s",dcb->user,dcb->remote); + + rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); + if(rulelist == NULL){ + sprintf(username,"%s@%%",dcb->user); + rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); + } + if(rulelist == NULL){ + sprintf(username,"%%@%s",dcb->remote); + rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); + } + + if(rulelist == NULL){ + sprintf(username,"%%@%%"); + rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); + } + + if(rulelist == NULL){ + accept = true; + goto queryresolved; + } + + while(rulelist){ + switch(rulelist->rule->type){ + + case RT_UNDEFINED: + accept = true; + goto queryresolved; + + case RT_COLUMN: + + if(modutil_is_SQL(queue)){ + + if(!query_is_parsed(queue)){ + parse_query(queue); + } + + if(skygw_is_real_query(queue)){ + + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + + while(strln){ + if(strstr(where,strln->value)){ + accept = false; + goto queryresolved; + } + strln = strln->next; + } + } + } + } + break; + + } + + rulelist = rulelist->next; + } + + if(rulelist == NULL){ + accept = true; + goto queryresolved; + } time(&time_now); tm_now = localtime(&time_now); @@ -797,7 +1098,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", dcb->user, dcb->remote, (my_instance->whitelist_users ? - "allowed":"blocked")); + "allowed":"denied")); } if(!match){ @@ -809,7 +1110,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) match = true; accept = my_instance->whitelist_networks; skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", - dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"blocked")); + dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"denied")); break; } ipranges = ipranges->next; @@ -831,10 +1132,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) time_t now = mktime(tm_now); double to_before = difftime(now,before); double to_after = difftime(now,after); - /**Restricted time*/ + /**Inside time range*/ if(to_before > 0.0 && to_after < 0.0){ match = true; - accept = false; + accept = my_instance->whitelist_times; skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered during restricted time: %s.",asctime(tm_now)); break; } @@ -857,14 +1158,18 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) queryop = query_classifier_get_operation(queue); - if(my_instance->require_where[ALL] || + /** + *TODO: Add column name requirement in addition to query type, match rules against users + */ + + if(my_instance->require_where[ALL] || (my_instance->require_where[SELECT] && queryop == QUERY_OP_SELECT) || (my_instance->require_where[UPDATE] && queryop == QUERY_OP_UPDATE) || (my_instance->require_where[INSERT] && queryop == QUERY_OP_INSERT) || (my_instance->require_where[DELETE] && queryop == QUERY_OP_DELETE)){ match = true; accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query does not have a where clause or a having clause, blocking it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); + skygw_log_write(LOGFILE_TRACE, "Firewall: query does not have a where clause or a having clause, denying it: %.*s",GWBUF_LENGTH(queue) - 5,(char*)(queue->start + 5)); } } @@ -872,23 +1177,23 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) where = skygw_get_affected_fields(queue); - if(my_instance->block_wildcard && + if(my_instance->deny_wildcard && where && strchr(where,'*') != NULL) { match = true; accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query contains wildcard, blocking it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); + skygw_log_write(LOGFILE_TRACE, "Firewall: query contains wildcard, denying it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); } else if(where) { char* tok = strtok(where," "); - + while(tok){ rtype = (ruletype_t)hashtable_fetch(my_instance->htable, tok); if(rtype == RT_COLUMN){ match = true; accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query contains a forbidden column %s, blocking it: %.*s",tok,GWBUF_LENGTH(queue),(char*)(queue->start + 5)); + skygw_log_write(LOGFILE_TRACE, "Firewall: query contains a forbidden column %s, denying it: %.*s",tok,GWBUF_LENGTH(queue),(char*)(queue->start + 5)); } tok = strtok(NULL," "); } @@ -904,6 +1209,8 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!match){ accept = my_instance->def_op; } + + queryresolved: if(accept){ @@ -913,9 +1220,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) gwbuf_free(queue); GWBUF* forward = gen_dummy_error(my_session); - dcb->func.write(dcb,forward); - //gwbuf_free(forward); - return 0; + return dcb->func.write(dcb,forward); } } From 38de0909c3bcc1addd7a8193c49564df256aa041 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 26 Oct 2014 10:34:26 +0200 Subject: [PATCH 11/31] Redid some of the code to make it easier to add more rule types. Added a timerange for the rules when they are active, defaults to always on. Added custom error messages. --- server/modules/filter/fwfilter.c | 438 ++++++++++++++----------------- 1 file changed, 198 insertions(+), 240 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 36e10dc67..acd150795 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -109,9 +109,9 @@ typedef enum{ */ typedef enum { RT_UNDEFINED, - RT_USER, RT_COLUMN, RT_TIME, + RT_PERMISSION, RT_WILDCARD }ruletype_t; @@ -140,6 +140,8 @@ typedef struct rule_t{ void* data; char* name; ruletype_t type; + bool allow; + TIMERANGE* active; }RULE; /** @@ -490,7 +492,6 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) assert(str != NULL && instance != NULL); - tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); if(tr == NULL){ @@ -520,6 +521,10 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) CHK_TIMES(tmptr); + if(*ptr == '\0'){ + return tr; + } + idest = intbuffer; tmptr = &tr->end; } @@ -617,7 +622,7 @@ void add_users(char* rule, FW_INSTANCE* instance) { assert(rule != NULL && instance != NULL); - STRLINK* link = malloc(sizeof(STRLINK)); + STRLINK* link = calloc(1,sizeof(STRLINK)); link->next = instance->userstrings; link->value = strdup(rule); instance->userstrings = link; @@ -687,7 +692,6 @@ void parse_rule(char* rule, FW_INSTANCE* instance) assert(rule != NULL && instance != NULL); char *rulecpy = strdup(rule); - char *ptr = rule; char *tok = strtok(rulecpy," ,"); bool allow,deny,mode; RULE* ruledef = NULL; @@ -702,11 +706,10 @@ void parse_rule(char* rule, FW_INSTANCE* instance) RULELIST* rlist = NULL; - ruledef = (RULE*)malloc(sizeof(RULE)); - rlist = (RULELIST*)malloc(sizeof(RULELIST)); + ruledef = (RULE*)calloc(1,sizeof(RULE)); + rlist = (RULELIST*)calloc(1,sizeof(RULELIST)); ruledef->name = strdup(tok); ruledef->type = RT_UNDEFINED; - ruledef->data = NULL; rlist->rule = ruledef; rlist->next = instance->rules; instance->rules = rlist; @@ -720,27 +723,25 @@ void parse_rule(char* rule, FW_INSTANCE* instance) tok = strtok(NULL, " ,"); - /**IP range rules*/ + if((allow = (strcmp(tok,"allow") == 0)) || (deny = (strcmp(tok,"deny") == 0))){ mode = allow ? true:false; + ruledef->allow = mode; + ruledef->type = RT_PERMISSION; tok = strtok(NULL, " ,"); - - - bool is_user = false, is_column = false, is_time = false; if(strcmp(tok,"wildcard") == 0) { ruledef->type = RT_WILDCARD; - ruledef->data = (void*)mode; } else if(strcmp(tok,"columns") == 0) { STRLINK *tail = NULL,*current; ruledef->type = RT_COLUMN; tok = strtok(NULL, " ,"); - while(tok){ + while(tok && strcmp(tok,"times") != 0){ current = malloc(sizeof(STRLINK)); current->value = strdup(tok); current->next = tail; @@ -755,78 +756,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance) { tok = strtok(NULL, " ,"); - ruledef->type = RT_TIME; TIMERANGE *tr = parse_time(tok,instance); if(IS_RVRS_TIME(tr)){ - TIMERANGE *tmptr = split_reverse_time(tr); - tr = tmptr; + tr = split_reverse_time(tr); } - ruledef->data = (void*)tr; - + ruledef->active = tr; } goto retblock; - - tok = strtok(NULL," ,\0"); - - if(is_user || is_column || is_time){ - while(tok){ - - /**Add value to hashtable*/ - - ruletype_t rtype = - is_user ? RT_USER : - is_column ? RT_COLUMN: - is_time ? RT_TIME : - RT_UNDEFINED; - - if(rtype == RT_USER || rtype == RT_COLUMN) - { - hashtable_add(instance->htable, - (void *)tok, - (void *)rtype); - } - else if(rtype == RT_TIME && check_time(tok)) - { - parse_time(tok,instance); - } - tok = strtok(NULL," ,\0"); - - } - } - - - }else if((ptr = strstr(rule,"require")) != NULL){ - - if((ptr = strstr(ptr,"where")) != NULL && - (ptr = strchr(ptr,' ')) != NULL){ - char* tok; - - ptr++; - tok = strtok(ptr," ,\0"); - while(tok){ - if(strcmp(tok, "all") == 0){ - instance->require_where[ALL] = true; - break; - }else if(strcmp(tok, "select") == 0){ - instance->require_where[SELECT] = true; - }else if(strcmp(tok, "insert") == 0){ - instance->require_where[INSERT] = true; - }else if(strcmp(tok, "update") == 0){ - instance->require_where[UPDATE] = true; - }else if(strcmp(tok, "delete") == 0){ - instance->require_where[DELETE] = true; - } - tok = strtok(NULL," ,\0"); - } - - } - } + retblock: free(rulecpy); + } /** @@ -862,6 +806,9 @@ createInstance(char **options, FILTER_PARAMETER **params) for(i = 0;params[i];i++){ if(strstr(params[i]->name,"rule")){ parse_rule(strip_tags(params[i]->value),my_instance); + }else if(strcmp(params[i]->name,"mode") == 0 && + strcmp(params[i]->value,"whitelist") == 0){ + my_instance->def_op = false; } } @@ -949,13 +896,22 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) * Generates a dummy error packet for the client. * @return The dummy packet or NULL if an error occurred */ -GWBUF* gen_dummy_error(FW_SESSION* session) +GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) { GWBUF* buf; - char errmsg[512]; + char* errmsg; DCB* dcb = session->session->client; MYSQL_session* mysql_session = (MYSQL_session*)session->session->data; unsigned int errlen, pktlen; + + errlen = msg != NULL ? strlen(msg) : 0; + errmsg = malloc((512 + errlen)*sizeof(char)); + + if(errmsg == NULL){ + skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL."); + return NULL; + } + if(mysql_session->db[0] == '\0') { @@ -966,11 +922,18 @@ GWBUF* gen_dummy_error(FW_SESSION* session) }else { sprintf(errmsg, - "Access denied for user '%s'@'%s' to database '%s' ", + "Access denied for user '%s'@'%s' to database '%s'", dcb->user, dcb->remote, mysql_session->db); } + + if(msg != NULL){ + char* ptr = strchr(errmsg,'\0'); + sprintf(ptr,": %s",msg); + + } + errlen = strlen(errmsg); pktlen = errlen + 9; buf = gwbuf_alloc(13 + errlen); @@ -989,6 +952,40 @@ GWBUF* gen_dummy_error(FW_SESSION* session) return buf; } +bool inside_timerange(TIMERANGE* comp) +{ + + struct tm* tm_now; + struct tm tm_before,tm_after; + time_t before,after,now, time_now; + double to_before,to_after; + + time(&time_now); + tm_now = localtime(&time_now); + memcpy(&tm_before,tm_now,sizeof(struct tm)); + memcpy(&tm_after,tm_now,sizeof(struct tm)); + + + tm_before.tm_sec = comp->start.tm_sec; + tm_before.tm_min = comp->start.tm_min; + tm_before.tm_hour = comp->start.tm_hour; + tm_after.tm_sec = comp->end.tm_sec; + tm_after.tm_min = comp->end.tm_min; + tm_after.tm_hour = comp->end.tm_hour; + + + before = mktime(&tm_before); + after = mktime(&tm_after); + now = mktime(tm_now); + to_before = difftime(now,before); + to_after = difftime(now,after); + + if(to_before > 0.0 && to_after < 0.0){ + return true; + } + return false; +} + /** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the @@ -1004,49 +1001,79 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) { FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; - IPRANGE* ipranges = my_instance->networks; - TIMERANGE* times = my_instance->times; time_t time_now; struct tm* tm_now; - struct tm tm_before,tm_after; - bool accept = false, match = false; - char *where; - char username[128]; - uint32_t ip; - ruletype_t rtype = RT_UNDEFINED; - skygw_query_op_t queryop; + bool accept = my_instance->def_op; + char *where, *msg = NULL; + char uname[128]; + char uname_addr[128]; + char addr[128]; + char emsg[1024]; DCB* dcb = my_session->session->client; - RULELIST* rulelist = NULL; + RULELIST *rulelist = NULL; STRLINK* strln = NULL; + TIMERANGE *times; - sprintf(username,"%s@%s",dcb->user,dcb->remote); + sprintf(uname_addr,"%s@%s",dcb->user,dcb->remote); + sprintf(uname,"%s@%%",dcb->user); + sprintf(addr,"%%@%s",dcb->remote); - rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); - if(rulelist == NULL){ - sprintf(username,"%s@%%",dcb->user); - rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); - } - if(rulelist == NULL){ - sprintf(username,"%%@%s",dcb->remote); - rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); - } + time(&time_now); + tm_now = localtime(&time_now); - if(rulelist == NULL){ - sprintf(username,"%%@%%"); - rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); - } + + - if(rulelist == NULL){ - accept = true; - goto queryresolved; - } + if((rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL && + (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname)) == NULL && + (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, addr)) == NULL && + (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, "%@%")) == NULL) + { + /** + *No rules matched, do default operation. + * TODO: add incremental wildcard search of network addresses + * i.e. iteration from 127.0.0.1 all the way up to 127.% + */ + + goto queryresolved; + } + while(rulelist){ + + if(rulelist->rule->active != NULL){ + bool rule_active = false; + times = (TIMERANGE*)rulelist->rule->active; + + while(times){ + + if(inside_timerange(times)){ + rule_active = true; + break; + } + + times = times->next; + } + if(!rule_active){ + rulelist = rulelist->next; + continue; + } + } + switch(rulelist->rule->type){ case RT_UNDEFINED: - accept = true; - goto queryresolved; + break; + + case RT_PERMISSION: + if(!rulelist->rule->allow){ + accept = false; + msg = strdup("Permission denied."); + goto queryresolved; + }else{ + break; + } + break; case RT_COLUMN: @@ -1065,8 +1092,16 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) while(strln){ if(strstr(where,strln->value)){ - accept = false; - goto queryresolved; + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + sprintf(emsg,"Permission denied to column '%s'.",strln->value); + msg = strdup(emsg); + goto queryresolved; + }else{ + break; + } } strln = strln->next; } @@ -1074,142 +1109,62 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } } break; + + case RT_TIME: /**Obsolete*/ + + times = (TIMERANGE*)rulelist->rule->data; + + while(times){ + + if(inside_timerange(times)){ + accept = rulelist->rule->allow; + skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered at %s, rule %s activated.",asctime(tm_now),rulelist->rule->name); + if(!rulelist->rule->allow){ + goto queryresolved; + }else{ + break; + } + break; + } + + times = times->next; + } + + break; + + case RT_WILDCARD: + + + if(modutil_is_SQL(queue)){ + + if(!query_is_parsed(queue)){ + parse_query(queue); + } + + if(skygw_is_real_query(queue)){ + + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + if(strchr(where,'*')){ + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + msg = strdup("Usage of wildcard denied."); + goto queryresolved; + } + } + } + } + } + break; } rulelist = rulelist->next; } - if(rulelist == NULL){ - accept = true; - goto queryresolved; - } - - time(&time_now); - tm_now = localtime(&time_now); - memcpy(&tm_before,tm_now,sizeof(struct tm)); - memcpy(&tm_after,tm_now,sizeof(struct tm)); - - rtype = (ruletype_t)hashtable_fetch(my_instance->htable, dcb->user); - - if(rtype == RT_USER){ - match = true; - accept = my_instance->whitelist_users; - skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", - dcb->user, dcb->remote, - (my_instance->whitelist_users ? - "allowed":"denied")); - } - - if(!match){ - - ip = strtoip(dcb->remote); - - while(ipranges){ - if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){ - match = true; - accept = my_instance->whitelist_networks; - skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.", - dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"denied")); - break; - } - ipranges = ipranges->next; - } - } - - while(times){ - - tm_before.tm_sec = times->start.tm_sec; - tm_before.tm_min = times->start.tm_min; - tm_before.tm_hour = times->start.tm_hour; - tm_after.tm_sec = times->end.tm_sec; - tm_after.tm_min = times->end.tm_min; - tm_after.tm_hour = times->end.tm_hour; - - - time_t before = mktime(&tm_before); - time_t after = mktime(&tm_after); - time_t now = mktime(tm_now); - double to_before = difftime(now,before); - double to_after = difftime(now,after); - /**Inside time range*/ - if(to_before > 0.0 && to_after < 0.0){ - match = true; - accept = my_instance->whitelist_times; - skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered during restricted time: %s.",asctime(tm_now)); - break; - } - - times = times->next; - } - - - if(modutil_is_SQL(queue)){ - - if(!query_is_parsed(queue)){ - parse_query(queue); - } - - if(skygw_is_real_query(queue)){ - - match = false; - - if(!skygw_query_has_clause(queue)){ - - queryop = query_classifier_get_operation(queue); - - /** - *TODO: Add column name requirement in addition to query type, match rules against users - */ - - if(my_instance->require_where[ALL] || - (my_instance->require_where[SELECT] && queryop == QUERY_OP_SELECT) || - (my_instance->require_where[UPDATE] && queryop == QUERY_OP_UPDATE) || - (my_instance->require_where[INSERT] && queryop == QUERY_OP_INSERT) || - (my_instance->require_where[DELETE] && queryop == QUERY_OP_DELETE)){ - match = true; - accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query does not have a where clause or a having clause, denying it: %.*s",GWBUF_LENGTH(queue) - 5,(char*)(queue->start + 5)); - } - } - - if(!match){ - - where = skygw_get_affected_fields(queue); - - if(my_instance->deny_wildcard && - where && strchr(where,'*') != NULL) - { - match = true; - accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query contains wildcard, denying it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5)); - } - else if(where) - { - char* tok = strtok(where," "); - - while(tok){ - rtype = (ruletype_t)hashtable_fetch(my_instance->htable, tok); - if(rtype == RT_COLUMN){ - match = true; - accept = false; - skygw_log_write(LOGFILE_TRACE, "Firewall: query contains a forbidden column %s, denying it: %.*s",tok,GWBUF_LENGTH(queue),(char*)(queue->start + 5)); - } - tok = strtok(NULL," "); - } - - } - free(where); - } - - } - } - - /**If no rules matched, do the default operation. (allow by default)*/ - if(!match){ - accept = my_instance->def_op; - } - queryresolved: if(accept){ @@ -1219,7 +1174,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) }else{ gwbuf_free(queue); - GWBUF* forward = gen_dummy_error(my_session); + GWBUF* forward = gen_dummy_error(my_session,msg); + if(msg){ + free(msg); + } return dcb->func.write(dcb,forward); } } From 339974ae84bb1ff86a10dacedf2d65a8c27ac63d Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 27 Oct 2014 13:38:31 +0200 Subject: [PATCH 12/31] Added a regex rule. --- server/modules/filter/fwfilter.c | 173 +++++++++++++++++++------------ 1 file changed, 104 insertions(+), 69 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index acd150795..68d8bb14c 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -58,7 +58,7 @@ #include #include #include - +#include MODULE_INFO info = { MODULE_API_FILTER, MODULE_ALPHA_RELEASE, @@ -110,9 +110,9 @@ typedef enum{ typedef enum { RT_UNDEFINED, RT_COLUMN, - RT_TIME, RT_PERMISSION, - RT_WILDCARD + RT_WILDCARD, + RT_REGEX }ruletype_t; /** @@ -495,7 +495,7 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); if(tr == NULL){ - skygw_log_write(LOGFILE_ERROR, "fwfilter error: malloc returned NULL."); + skygw_log_write(LOGFILE_ERROR, "fwfilter: malloc returned NULL."); return NULL; } @@ -678,7 +678,7 @@ void link_rules(char* rule, FW_INSTANCE* instance) (void *)userptr, (void *)rulelist) == 0) { - skygw_log_write(LOGFILE_TRACE, "Name conflict in fwfilter: %s was found twice.",tok); + skygw_log_write(LOGFILE_TRACE, "fwfilter: Name conflict (%s was found twice)",tok); } userptr = strtok(NULL," ,"); @@ -732,6 +732,8 @@ void parse_rule(char* rule, FW_INSTANCE* instance) ruledef->type = RT_PERMISSION; tok = strtok(NULL, " ,"); + + while(tok){ if(strcmp(tok,"wildcard") == 0) { ruledef->type = RT_WILDCARD; @@ -750,6 +752,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } ruledef->data = (void*)tail; + continue; } else if(strcmp(tok,"times") == 0) @@ -764,6 +767,42 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } ruledef->active = tr; } + else if(strcmp(tok,"regex") == 0) + { + bool escaped = false; + tok += 6; + + while(isspace(*tok) || *tok == '\'' || *tok == '"'){ + tok++; + } + char* start = tok, *str; + while(true){ + + if((*tok == '\'' || *tok == '"') && !escaped){ + break; + } + escaped = (*tok == '\\'); + tok++; + } + + str = malloc(((tok - start) + 1)*sizeof(char)); + *tok = '\0'; + memcpy(str, start, (tok-start) + 1); + + regex_t *re = malloc(sizeof(regex_t)); + + if(regcomp(re, str,REG_NOSUB)){ + skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); + free(re); + } + + ruledef->type = RT_REGEX; + ruledef->data = (void*) re; + + + } + tok = strtok(NULL," ,"); + } goto retblock; } @@ -805,7 +844,7 @@ createInstance(char **options, FILTER_PARAMETER **params) for(i = 0;params[i];i++){ if(strstr(params[i]->name,"rule")){ - parse_rule(strip_tags(params[i]->value),my_instance); + parse_rule(params[i]->value,my_instance); }else if(strcmp(params[i]->name,"mode") == 0 && strcmp(params[i]->value,"whitelist") == 0){ my_instance->def_op = false; @@ -1002,9 +1041,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; time_t time_now; - struct tm* tm_now; - bool accept = my_instance->def_op; - char *where, *msg = NULL; + bool accept = my_instance->def_op, + is_sql = false, + is_real = false; + char *where, *msg = NULL, *fullquery,*ptr; char uname[128]; char uname_addr[128]; char addr[128]; @@ -1013,13 +1053,13 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) RULELIST *rulelist = NULL; STRLINK* strln = NULL; TIMERANGE *times; + int qlen; sprintf(uname_addr,"%s@%s",dcb->user,dcb->remote); sprintf(uname,"%s@%%",dcb->user); sprintf(addr,"%%@%s",dcb->remote); time(&time_now); - tm_now = localtime(&time_now); @@ -1038,7 +1078,20 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) goto queryresolved; } - + + is_sql = modutil_is_SQL(queue); + + if(is_sql){ + if(!query_is_parsed(queue)){ + parse_query(queue); + } + modutil_extract_SQL(queue, &ptr, &qlen); + fullquery = malloc((qlen + 1) * sizeof(char)); + memcpy(fullquery,ptr,qlen); + memset(fullquery + qlen,0,1); + is_real = skygw_is_real_query(queue); + } + while(rulelist){ if(rulelist->rule->active != NULL){ @@ -1065,10 +1118,26 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) case RT_UNDEFINED: break; + case RT_REGEX: + + if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + msg = strdup("Permission denied, query matched regular expression."); + goto queryresolved; + }else{ + break; + } + } + + break; + case RT_PERMISSION: if(!rulelist->rule->allow){ accept = false; - msg = strdup("Permission denied."); + msg = strdup("Permission denied at this time."); goto queryresolved; }else{ break; @@ -1077,87 +1146,53 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) case RT_COLUMN: - if(modutil_is_SQL(queue)){ - - if(!query_is_parsed(queue)){ - parse_query(queue); - } + if(is_sql && is_real){ - if(skygw_is_real_query(queue)){ + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); - strln = (STRLINK*)rulelist->rule->data; - where = skygw_get_affected_fields(queue); + if(where != NULL){ - if(where != NULL){ + while(strln){ + if(strstr(where,strln->value)){ - while(strln){ - if(strstr(where,strln->value)){ + accept = rulelist->rule->allow; - accept = rulelist->rule->allow; - - if(!rulelist->rule->allow){ - sprintf(emsg,"Permission denied to column '%s'.",strln->value); - msg = strdup(emsg); - goto queryresolved; - }else{ - break; - } + if(!rulelist->rule->allow){ + sprintf(emsg,"Permission denied to column '%s'.",strln->value); + msg = strdup(emsg); + goto queryresolved; + }else{ + break; } - strln = strln->next; } + strln = strln->next; } } } - break; - - case RT_TIME: /**Obsolete*/ - times = (TIMERANGE*)rulelist->rule->data; - - while(times){ - - if(inside_timerange(times)){ - accept = rulelist->rule->allow; - skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered at %s, rule %s activated.",asctime(tm_now),rulelist->rule->name); - if(!rulelist->rule->allow){ - goto queryresolved; - }else{ - break; - } - break; - } - - times = times->next; - } - break; case RT_WILDCARD: - if(modutil_is_SQL(queue)){ - - if(!query_is_parsed(queue)){ - parse_query(queue); - } - - if(skygw_is_real_query(queue)){ + if(is_sql && is_real){ - where = skygw_get_affected_fields(queue); + where = skygw_get_affected_fields(queue); - if(where != NULL){ - if(strchr(where,'*')){ + if(where != NULL){ + if(strchr(where,'*')){ - accept = rulelist->rule->allow; + accept = rulelist->rule->allow; - if(!rulelist->rule->allow){ - msg = strdup("Usage of wildcard denied."); - goto queryresolved; - } + if(!rulelist->rule->allow){ + msg = strdup("Usage of wildcard denied."); + goto queryresolved; } } } } + break; } From f3c627cfed9a05cd6ad31093db89e60af7ee2e5a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 29 Oct 2014 13:19:00 +0200 Subject: [PATCH 13/31] Queries can be matched against any matching rule or only when all rules match. --- server/modules/filter/fwfilter.c | 337 +++++++++++++++++++++++-------- 1 file changed, 258 insertions(+), 79 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 68d8bb14c..3eb2b10b8 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -20,7 +20,7 @@ * @file fwfilter.c * Firewall Filter * - * A filter that acts as a firewall, denying queries that do not meet the set requirements. + * A filter that acts as a firewall, denying queries that do not meet a set requirements. * * This filter uses "rules" to define the blcking parameters. To configure rules into the configuration file, * give each rule a unique name and assing the rule contents by passing a string enclosed in quotes. @@ -97,11 +97,12 @@ static FILTER_OBJECT MyObject = { * Query types */ typedef enum{ - ALL, - SELECT, - INSERT, - UPDATE, - DELETE + NONE = 0, + ALL = (1), + SELECT = (1<<1), + INSERT = (1<<2), + UPDATE = (1<<3), + DELETE = (1<<4) }querytype_t; /** @@ -123,7 +124,6 @@ typedef struct strlink_t{ char* value; }STRLINK; - typedef struct timerange_t{ struct timerange_t* next; struct tm start; @@ -140,6 +140,7 @@ typedef struct rule_t{ void* data; char* name; ruletype_t type; + querytype_t on_queries; bool allow; TIMERANGE* active; }RULE; @@ -152,6 +153,12 @@ typedef struct rulelist_t{ struct rulelist_t* next; }RULELIST; +typedef struct user_t{ + char* name; + RULELIST* rules_or; + RULELIST* rules_and; +}USER; + /** * Linked list of IP adresses and subnet masks */ @@ -165,13 +172,10 @@ typedef struct iprange_t{ * The Firewall filter instance. */ typedef struct { - HASHTABLE* htable; /**Usernames and forbidden columns*/ + HASHTABLE* htable; RULELIST* rules; - IPRANGE* networks; - TIMERANGE* times; STRLINK* userstrings; - bool require_where[QUERY_TYPES]; - bool deny_wildcard, whitelist_users,whitelist_networks,whitelist_times,def_op; + bool def_op; } FW_INSTANCE; @@ -224,16 +228,14 @@ static void* hstrfree(void* fval) return NULL; } -static void* hruledup(void* fval) + +void* rlistdup(void* fval) { - if(fval == NULL){ - return NULL; - } - RULELIST *rule = NULL, *ptr = (RULELIST*)fval; + while(ptr){ RULELIST* tmp = (RULELIST*)malloc(sizeof(RULELIST)); tmp->next = rule; @@ -241,19 +243,34 @@ static void* hruledup(void* fval) rule = tmp; ptr = ptr->next; } + + return (void*)rule; - return rule; } +/* +static void* hruledup(void* fval) +{ + return fval; +}*/ static void* hrulefree(void* fval) { - RULELIST *ptr = (RULELIST*)fval,*tmp; + USER* user = (USER*)fval; + RULELIST *ptr = user->rules_or,*tmp; while(ptr){ tmp = ptr; ptr = ptr->next; free(tmp); } + ptr = user->rules_and; + while(ptr){ + tmp = ptr; + ptr = ptr->next; + free(tmp); + } + free(user->name); + free(user); return NULL; } @@ -397,6 +414,33 @@ int get_octet(char* str) } +char* next_ip_class(char* str) +{ + assert(str != NULL); + + char* ptr = strchr(str,'\0'); + + if(ptr == NULL){ + return NULL; + } + + while(ptr > str){ + ptr--; + if(*ptr == '.' && *(ptr+1) != '%'){ + break; + } + } + + if(ptr == str){ + return NULL; + } + + *++ptr = '%'; + *++ptr = '\0'; + + return str; +} + /** *Convert string with IP address to an unsigned 32-bit integer * @param str String to convert @@ -492,7 +536,7 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) assert(str != NULL && instance != NULL); - tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + tr = (TIMERANGE*)calloc(1,sizeof(TIMERANGE)); if(tr == NULL){ skygw_log_write(LOGFILE_ERROR, "fwfilter: malloc returned NULL."); @@ -550,7 +594,7 @@ TIMERANGE* split_reverse_time(TIMERANGE* tr) TIMERANGE* tmp = NULL; if(IS_RVRS_TIME(tr)){ - tmp = (TIMERANGE*)malloc(sizeof(TIMERANGE)); + tmp = (TIMERANGE*)calloc(1,sizeof(TIMERANGE)); tmp->next = tr; tmp->start.tm_hour = 0; tmp->start.tm_min = 0; @@ -633,21 +677,39 @@ void link_rules(char* rule, FW_INSTANCE* instance) assert(rule != NULL && instance != NULL); /**Apply rules to users*/ - - char *tok, *ruleptr, *userptr; + + bool match_any; + char *tok, *ruleptr, *userptr, *modeptr; RULELIST* rulelist = NULL; - userptr = strstr(rule,"users"); - ruleptr = strstr(rule,"rules"); - - if(userptr == NULL || ruleptr == NULL) { - /**No rules to apply were found*/ - skygw_log_write(LOGFILE_TRACE, "Rule syntax was not proper, 'user' or 'rules' was found but not the other: %s",rule); + userptr = strstr(rule,"users "); + modeptr = strstr(rule," match "); + ruleptr = strstr(rule," rules "); + + if((userptr == NULL || ruleptr == NULL || modeptr == NULL)|| + (userptr > modeptr || userptr > ruleptr || modeptr > ruleptr)) { + skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule syntax incorrect, right keywords not found in the correct order: %s",rule); return; } + + *modeptr++ = '\0'; + *ruleptr++ = '\0'; - tok = strtok(ruleptr," ,"); - tok = strtok(NULL," ,"); + tok = strtok(modeptr," "); + if(strcmp(tok,"match") == 0){ + tok = strtok(NULL," "); + if(strcmp(tok,"any") == 0){ + match_any = true; + }else if(strcmp(tok,"all") == 0){ + match_any = false; + }else{ + skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule syntax incorrect, 'match' was not followed by 'any' or 'all': %s",rule); + return; + } + } + + tok = strtok(ruleptr," "); + tok = strtok(NULL," "); while(tok) { @@ -661,7 +723,7 @@ void link_rules(char* rule, FW_INSTANCE* instance) rulelist = tmp_rl; } - tok = strtok(NULL," ,"); + tok = strtok(NULL," "); } /** @@ -669,19 +731,44 @@ void link_rules(char* rule, FW_INSTANCE* instance) */ *(ruleptr) = '\0'; - userptr = strtok(rule," ,"); - userptr = strtok(NULL," ,"); + userptr = strtok(rule," "); + userptr = strtok(NULL," "); while(userptr) { - if(hashtable_add(instance->htable, - (void *)userptr, - (void *)rulelist) == 0) - { - skygw_log_write(LOGFILE_TRACE, "fwfilter: Name conflict (%s was found twice)",tok); + USER* user; + RULELIST *tl = NULL,*tail = NULL; + + if((user = hashtable_fetch(instance->htable,userptr)) == NULL){ + + /**New user*/ + user = calloc(1,sizeof(USER)); + if(user == NULL){ + return; } - - userptr = strtok(NULL," ,"); + } + + user->name = strdup(userptr); + tl = rlistdup(rulelist); + tail = tl; + while(tail->next){ + tail = tail->next; + } + + + if(match_any){ + tail->next = user->rules_or; + user->rules_or = tl; + }else{ + tail->next = user->rules_and; + user->rules_and = tl; + } + + hashtable_add(instance->htable, + (void *)userptr, + (void *)user); + + userptr = strtok(NULL," "); } @@ -798,7 +885,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) ruledef->type = RT_REGEX; ruledef->data = (void*) re; - + free(str); } tok = strtok(NULL," ,"); @@ -837,7 +924,7 @@ createInstance(char **options, FILTER_PARAMETER **params) return NULL; } - hashtable_memory_fns(ht,hstrdup,hruledup,hstrfree,hrulefree); + hashtable_memory_fns(ht,hstrdup,NULL,hstrfree,hrulefree); my_instance->htable = ht; my_instance->def_op = true; @@ -1025,6 +1112,26 @@ bool inside_timerange(TIMERANGE* comp) return false; } +bool rule_is_active(RULE* rule) +{ + TIMERANGE* times; + if(rule->active != NULL){ + + times = (TIMERANGE*)rule->active; + + while(times){ + + if(inside_timerange(times)){ + return true; + } + + times = times->next; + } + return false; + } + return true; +} + /** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the @@ -1043,42 +1150,47 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) time_t time_now; bool accept = my_instance->def_op, is_sql = false, - is_real = false; - char *where, *msg = NULL, *fullquery,*ptr; - char uname[128]; + is_real = false,rule_match; + char *where, *msg = NULL, *fullquery = NULL,*ptr,*ipaddr; char uname_addr[128]; - char addr[128]; char emsg[1024]; DCB* dcb = my_session->session->client; RULELIST *rulelist = NULL; + USER* user = NULL; STRLINK* strln = NULL; - TIMERANGE *times; int qlen; - sprintf(uname_addr,"%s@%s",dcb->user,dcb->remote); - sprintf(uname,"%s@%%",dcb->user); - sprintf(addr,"%%@%s",dcb->remote); + ipaddr = strdup(dcb->remote); + sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); time(&time_now); - - + if((user = (USER*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL){ + while(user == NULL && next_ip_class(ipaddr)){ + sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); + user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); + } + } - if((rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL && - (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname)) == NULL && - (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, addr)) == NULL && - (rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, "%@%")) == NULL) - { + if(user == NULL){ + strcpy(ipaddr,dcb->remote); + + do{ + sprintf(uname_addr,"%%@%s",ipaddr); + user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); + }while(user == NULL && next_ip_class(ipaddr)); + } + + if(user == NULL){ /** *No rules matched, do default operation. - * TODO: add incremental wildcard search of network addresses - * i.e. iteration from 127.0.0.1 all the way up to 127.% */ goto queryresolved; } + is_sql = modutil_is_SQL(queue); if(is_sql){ @@ -1092,27 +1204,15 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) is_real = skygw_is_real_query(queue); } + rulelist = user->rules_or; + while(rulelist){ - if(rulelist->rule->active != NULL){ - bool rule_active = false; - times = (TIMERANGE*)rulelist->rule->active; - - while(times){ - - if(inside_timerange(times)){ - rule_active = true; - break; - } - - times = times->next; - } - if(!rule_active){ - rulelist = rulelist->next; - continue; - } + if(!rule_is_active(rulelist->rule)){ + rulelist = rulelist->next; + continue; } - + switch(rulelist->rule->type){ case RT_UNDEFINED: @@ -1200,8 +1300,87 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) rulelist = rulelist->next; } - queryresolved: + + rulelist = user->rules_and; + rule_match = (rulelist != NULL); + while(rulelist && rule_match){ + + if(!rule_is_active(rulelist->rule)){ + rule_match = false; + break; + } + + switch(rulelist->rule->type){ + + case RT_UNDEFINED: + break; + + case RT_REGEX: + + if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) != 0){ + rule_match = false; + } + + break; + + case RT_PERMISSION: + if(!rulelist->rule->allow){ + rule_match = false; + } + break; + + case RT_COLUMN: + + if(is_sql && is_real){ + + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + + while(strln){ + if(strstr(where,strln->value)){ + rule_match = false; + break; + } + strln = strln->next; + } + } + } + + break; + + case RT_WILDCARD: + + + if(is_sql && is_real){ + + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + if(strchr(where,'*')){ + rule_match = false; + } + } + } + + break; + + } + + rulelist = rulelist->next; + } + + if(rule_match == true){ + /**AND rules match TODO: add a way to control what happens if AND matches*/ + accept = false; + } + + queryresolved: + + free(ipaddr); + free(fullquery); if(accept){ return my_session->down.routeQuery(my_session->down.instance, From 39cee913cc0757da03da1d6aa5372f974e2a5c63 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 29 Oct 2014 16:10:04 +0200 Subject: [PATCH 14/31] Refined the rule syntax,moved over to separate rule files and fixed some bugs. --- server/modules/filter/fwfilter.c | 96 +++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 3eb2b10b8..4d492a9e0 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -25,20 +25,17 @@ * This filter uses "rules" to define the blcking parameters. To configure rules into the configuration file, * give each rule a unique name and assing the rule contents by passing a string enclosed in quotes. * - * For example, to define a rule denying users from accessing the column 'salary', the following is needed in the configuration file: + * For example, to define a rule denying users from accessing the column 'salary' between 15:00 and 17:00, the following is needed in the configuration file: * - * rule1="rule block_salary deny columns salary" + * rule1="rule block_salary deny columns salary at_times 15:00:00-17:00:00" * * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: * - * rule2="users John@% Jane@192.168.0.1 rules block_salary" + * rule2="users John@% Jane@192.168.0.1 match any rules block_salary" * - * Rule syntax TODO: implement timeranges, query type restrictions + * Rule syntax TODO: query type restrictions * - * rule NAME deny|allow|require - * [wildcard|columns VALUE ...] - * [times VALUE...] - * [on_queries [all|select|update|delete|insert]...] + * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX] [at_times VALUE...] */ #include #include @@ -432,7 +429,9 @@ char* next_ip_class(char* str) } if(ptr == str){ - return NULL; + *ptr++ = '%'; + *ptr = '\0'; + return str; } *++ptr = '%'; @@ -751,7 +750,7 @@ void link_rules(char* rule, FW_INSTANCE* instance) user->name = strdup(userptr); tl = rlistdup(rulelist); tail = tl; - while(tail->next){ + while(tail && tail->next){ tail = tail->next; } @@ -797,6 +796,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) rlist = (RULELIST*)calloc(1,sizeof(RULELIST)); ruledef->name = strdup(tok); ruledef->type = RT_UNDEFINED; + ruledef->on_queries = ALL; rlist->rule = ruledef; rlist->next = instance->rules; instance->rules = rlist; @@ -830,7 +830,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) STRLINK *tail = NULL,*current; ruledef->type = RT_COLUMN; tok = strtok(NULL, " ,"); - while(tok && strcmp(tok,"times") != 0){ + while(tok && strcmp(tok,"at_times") != 0){ current = malloc(sizeof(STRLINK)); current->value = strdup(tok); current->next = tail; @@ -842,15 +842,20 @@ void parse_rule(char* rule, FW_INSTANCE* instance) continue; } - else if(strcmp(tok,"times") == 0) + else if(strcmp(tok,"at_times") == 0) { tok = strtok(NULL, " ,"); - - TIMERANGE *tr = parse_time(tok,instance); + TIMERANGE *tr = NULL; + while(tok){ + TIMERANGE *tmp = parse_time(tok,instance); - if(IS_RVRS_TIME(tr)){ - tr = split_reverse_time(tr); + if(IS_RVRS_TIME(tmp)){ + tmp = split_reverse_time(tmp); + } + tmp->next = tr; + tr = tmp; + tok = strtok(NULL, " ,"); } ruledef->active = tr; } @@ -873,8 +878,9 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } str = malloc(((tok - start) + 1)*sizeof(char)); - *tok = '\0'; - memcpy(str, start, (tok-start) + 1); + + memcpy(str, start, (tok-start)); + memset((str + (tok-start) +1),0,1); regex_t *re = malloc(sizeof(regex_t)); @@ -911,16 +917,19 @@ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { FW_INSTANCE *my_instance; - + int i,paramc; + HASHTABLE* ht; + STRLINK *ptr,*tmp; + char *filename, *nl; + char buffer[2048]; + FILE* file; if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ return NULL; } - int i; - HASHTABLE* ht; - STRLINK *ptr,*tmp; if((ht = hashtable_alloc(7, hashkeyfun, hashcmpfun)) == NULL){ skygw_log_write(LOGFILE_ERROR, "Unable to allocate hashtable."); + free(my_instance); return NULL; } @@ -930,14 +939,37 @@ createInstance(char **options, FILTER_PARAMETER **params) my_instance->def_op = true; for(i = 0;params[i];i++){ - if(strstr(params[i]->name,"rule")){ - parse_rule(params[i]->value,my_instance); - }else if(strcmp(params[i]->name,"mode") == 0 && - strcmp(params[i]->value,"whitelist") == 0){ - my_instance->def_op = false; + if(strcmp(params[i]->name, "rulelist") == 0){ + filename = strdup(params[i]->value); } } + if((file = fopen(filename,"rb")) == NULL ){ + free(my_instance); + free(filename); + return NULL; + } + + free(filename); + + while(!feof(file)) + { + + if(fgets(buffer,2048,file) == NULL){ + free(my_instance); + return NULL; + } + + if((nl = strchr(buffer,'\n')) != NULL && ((char*)nl - (char*)buffer) < 2048){ + *nl = '\0'; + } + + parse_rule(buffer,my_instance); + + } + + fclose(file); + /**Apply the rules to users*/ ptr = my_instance->userstrings; while(ptr){ @@ -1148,6 +1180,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; time_t time_now; + struct tm* tm_now; bool accept = my_instance->def_op, is_sql = false, is_real = false,rule_match; @@ -1164,6 +1197,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); time(&time_now); + tm_now = localtime(&time_now); if((user = (USER*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL){ while(user == NULL && next_ip_class(ipaddr)){ @@ -1216,6 +1250,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) switch(rulelist->rule->type){ case RT_UNDEFINED: + skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); break; case RT_REGEX: @@ -1226,6 +1261,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!rulelist->rule->allow){ msg = strdup("Permission denied, query matched regular expression."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); goto queryresolved; }else{ break; @@ -1238,6 +1274,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!rulelist->rule->allow){ accept = false; msg = strdup("Permission denied at this time."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); goto queryresolved; }else{ break; @@ -1260,6 +1297,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!rulelist->rule->allow){ sprintf(emsg,"Permission denied to column '%s'.",strln->value); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); msg = strdup(emsg); goto queryresolved; }else{ @@ -1287,6 +1325,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!rulelist->rule->allow){ msg = strdup("Usage of wildcard denied."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); goto queryresolved; } } @@ -1295,6 +1334,9 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) break; + default: + break; + } rulelist = rulelist->next; From f8f6006314b96a8105e5ec263f7459142df2eccf Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 31 Oct 2014 14:37:06 +0200 Subject: [PATCH 15/31] Fixed a bug with regex matching --- server/modules/filter/fwfilter.c | 212 ++++++++++++++++--------------- 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 4d492a9e0..194a6f314 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -33,7 +33,7 @@ * * rule2="users John@% Jane@192.168.0.1 match any rules block_salary" * - * Rule syntax TODO: query type restrictions + * Rule syntax TODO: query type restrictions, update the documentation * * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX] [at_times VALUE...] */ @@ -245,10 +245,10 @@ void* rlistdup(void* fval) } /* -static void* hruledup(void* fval) -{ - return fval; -}*/ + static void* hruledup(void* fval) + { + return fval; + }*/ static void* hrulefree(void* fval) @@ -821,80 +821,79 @@ void parse_rule(char* rule, FW_INSTANCE* instance) while(tok){ - if(strcmp(tok,"wildcard") == 0) - { - ruledef->type = RT_WILDCARD; - } - else if(strcmp(tok,"columns") == 0) - { - STRLINK *tail = NULL,*current; - ruledef->type = RT_COLUMN; - tok = strtok(NULL, " ,"); - while(tok && strcmp(tok,"at_times") != 0){ - current = malloc(sizeof(STRLINK)); - current->value = strdup(tok); - current->next = tail; - tail = current; + if(strcmp(tok,"wildcard") == 0) + { + ruledef->type = RT_WILDCARD; + } + else if(strcmp(tok,"columns") == 0) + { + STRLINK *tail = NULL,*current; + ruledef->type = RT_COLUMN; tok = strtok(NULL, " ,"); - } - - ruledef->data = (void*)tail; - continue; - - } - else if(strcmp(tok,"at_times") == 0) - { - - tok = strtok(NULL, " ,"); - TIMERANGE *tr = NULL; - while(tok){ - TIMERANGE *tmp = parse_time(tok,instance); - - if(IS_RVRS_TIME(tmp)){ - tmp = split_reverse_time(tmp); + while(tok && strcmp(tok,"at_times") != 0){ + current = malloc(sizeof(STRLINK)); + current->value = strdup(tok); + current->next = tail; + tail = current; + tok = strtok(NULL, " ,"); } - tmp->next = tr; - tr = tmp; + + ruledef->data = (void*)tail; + continue; + + } + else if(strcmp(tok,"at_times") == 0) + { + tok = strtok(NULL, " ,"); - } - ruledef->active = tr; - } - else if(strcmp(tok,"regex") == 0) - { - bool escaped = false; - tok += 6; - - while(isspace(*tok) || *tok == '\'' || *tok == '"'){ - tok++; - } - char* start = tok, *str; - while(true){ - - if((*tok == '\'' || *tok == '"') && !escaped){ - break; + TIMERANGE *tr = NULL; + while(tok){ + TIMERANGE *tmp = parse_time(tok,instance); + + if(IS_RVRS_TIME(tmp)){ + tmp = split_reverse_time(tmp); + } + tmp->next = tr; + tr = tmp; + tok = strtok(NULL, " ,"); } - escaped = (*tok == '\\'); - tok++; + ruledef->active = tr; } + else if(strcmp(tok,"regex") == 0) + { + bool escaped = false; + tok += 6; - str = malloc(((tok - start) + 1)*sizeof(char)); + while(isspace(*tok) || *tok == '\'' || *tok == '"'){ + tok++; + } + char* start = tok, *str; + while(true){ - memcpy(str, start, (tok-start)); - memset((str + (tok-start) +1),0,1); + if((*tok == '\'' || *tok == '"') && !escaped){ + break; + } + escaped = (*tok == '\\'); + tok++; + } + + str = calloc(((tok - start) + 1),sizeof(char)); + + memcpy(str, start, (tok-start)); - regex_t *re = malloc(sizeof(regex_t)); + regex_t *re = malloc(sizeof(regex_t)); + + if(regcomp(re, str,REG_NOSUB)){ + skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); + free(re); + } + + ruledef->type = RT_REGEX; + ruledef->data = (void*) re; + free(str); - if(regcomp(re, str,REG_NOSUB)){ - skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); - free(re); } - - ruledef->type = RT_REGEX; - ruledef->data = (void*) re; - free(str); - - } - tok = strtok(NULL," ,"); + tok = strtok(NULL," ,"); } goto retblock; @@ -920,7 +919,7 @@ createInstance(char **options, FILTER_PARAMETER **params) int i,paramc; HASHTABLE* ht; STRLINK *ptr,*tmp; - char *filename, *nl; + char *filename = NULL, *nl; char buffer[2048]; FILE* file; if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ @@ -939,7 +938,7 @@ createInstance(char **options, FILTER_PARAMETER **params) my_instance->def_op = true; for(i = 0;params[i];i++){ - if(strcmp(params[i]->name, "rulelist") == 0){ + if(strcmp(params[i]->name, "rules") == 0){ filename = strdup(params[i]->value); } } @@ -1192,41 +1191,41 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) USER* user = NULL; STRLINK* strln = NULL; int qlen; - + ipaddr = strdup(dcb->remote); sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); time(&time_now); tm_now = localtime(&time_now); - + if((user = (USER*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL){ - while(user == NULL && next_ip_class(ipaddr)){ - sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); - user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); - } + while(user == NULL && next_ip_class(ipaddr)){ + sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); + user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); } - - if(user == NULL){ - strcpy(ipaddr,dcb->remote); - - do{ - sprintf(uname_addr,"%%@%s",ipaddr); - user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); - }while(user == NULL && next_ip_class(ipaddr)); - } - - if(user == NULL){ - - /** - *No rules matched, do default operation. - */ - - goto queryresolved; - } - + } + + if(user == NULL){ + strcpy(ipaddr,dcb->remote); + + do{ + sprintf(uname_addr,"%%@%s",ipaddr); + user = (USER*)hashtable_fetch(my_instance->htable, uname_addr); + }while(user == NULL && next_ip_class(ipaddr)); + } + + if(user == NULL){ + + /** + *No rules matched, do default operation. + */ + + goto queryresolved; + } + is_sql = modutil_is_SQL(queue); - + if(is_sql){ if(!query_is_parsed(queue)){ parse_query(queue); @@ -1378,14 +1377,19 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) strln = (STRLINK*)rulelist->rule->data; where = skygw_get_affected_fields(queue); + rule_match = false; if(where != NULL){ while(strln){ - if(strstr(where,strln->value)){ - rule_match = false; + + /**At least one value matched*/ + + if(strstr(where,strln->value)){ + rule_match = true; break; } + strln = strln->next; } } @@ -1395,13 +1399,12 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) case RT_WILDCARD: - if(is_sql && is_real){ where = skygw_get_affected_fields(queue); if(where != NULL){ - if(strchr(where,'*')){ + if(strchr(where,'*') == NULL){ rule_match = false; } } @@ -1414,9 +1417,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) rulelist = rulelist->next; } - if(rule_match == true){ - /**AND rules match TODO: add a way to control what happens if AND matches*/ - accept = false; + if(rule_match){ + /**AND rules match*/ + skygw_log_write(LOGFILE_TRACE, "fwfilter: all rules match, query is %s.",accept ? "allowed":"denied"); + accept = !my_instance->def_op; } queryresolved: From e275b5e533f7deb0da0197ff6e0e770d1c033589 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 3 Nov 2014 11:36:13 +0200 Subject: [PATCH 16/31] Added query throttling for network ranges --- server/modules/filter/fwfilter.c | 151 ++++++++++++++++++++++++++++--- 1 file changed, 138 insertions(+), 13 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 194a6f314..243acdc76 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -22,20 +22,23 @@ * * A filter that acts as a firewall, denying queries that do not meet a set requirements. * - * This filter uses "rules" to define the blcking parameters. To configure rules into the configuration file, - * give each rule a unique name and assing the rule contents by passing a string enclosed in quotes. + * This filter uses "rules" to define the blcking parameters. Write the rules to a separate file and + * set the path to the file: + * + * rules= + * * * For example, to define a rule denying users from accessing the column 'salary' between 15:00 and 17:00, the following is needed in the configuration file: * - * rule1="rule block_salary deny columns salary at_times 15:00:00-17:00:00" + * rule block_salary deny columns salary at_times 15:00:00-17:00:00 * * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: * - * rule2="users John@% Jane@192.168.0.1 match any rules block_salary" + * users John@% Jane@192.168.0.1 match any rules block_salary * * Rule syntax TODO: query type restrictions, update the documentation * - * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX] [at_times VALUE...] + * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF] [at_times VALUE...] */ #include #include @@ -108,6 +111,7 @@ typedef enum{ typedef enum { RT_UNDEFINED, RT_COLUMN, + RT_THROTTLE, RT_PERMISSION, RT_WILDCARD, RT_REGEX @@ -127,6 +131,16 @@ typedef struct timerange_t{ struct tm end; }TIMERANGE; +typedef struct queryspeed_t{ + time_t first_query; + time_t triggered; + double period; + double cooldown; + int count; + int limit; +}QUERYSPEED; + + /** * A structure used to identify individual rules and to store their contents * @@ -411,6 +425,11 @@ int get_octet(char* str) } +/** + * Parses a string that contains an IP address and converts the last octet to '%' + * @param str String to parse + * @return Pointer to modified string + */ char* next_ip_class(char* str) { assert(str != NULL); @@ -738,17 +757,17 @@ void link_rules(char* rule, FW_INSTANCE* instance) USER* user; RULELIST *tl = NULL,*tail = NULL; - if((user = hashtable_fetch(instance->htable,userptr)) == NULL){ + if((user = (USER*)hashtable_fetch(instance->htable,userptr)) == NULL){ /**New user*/ - user = calloc(1,sizeof(USER)); + user = (USER*)calloc(1,sizeof(USER)); if(user == NULL){ return; } } - user->name = strdup(userptr); - tl = rlistdup(rulelist); + user->name = (char*)strdup(userptr); + tl = (RULELIST*)rlistdup(rulelist); tail = tl; while(tail && tail->next){ tail = tail->next; @@ -862,12 +881,15 @@ void parse_rule(char* rule, FW_INSTANCE* instance) else if(strcmp(tok,"regex") == 0) { bool escaped = false; + regex_t *re; + char* start = tok, *str; + tok += 6; while(isspace(*tok) || *tok == '\'' || *tok == '"'){ tok++; } - char* start = tok, *str; + while(true){ if((*tok == '\'' || *tok == '"') && !escaped){ @@ -878,10 +900,15 @@ void parse_rule(char* rule, FW_INSTANCE* instance) } str = calloc(((tok - start) + 1),sizeof(char)); + re = (regex_t*)malloc(sizeof(regex_t)); + + if(re == NULL || str == NULL){ + skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL."); + + return; + } memcpy(str, start, (tok-start)); - - regex_t *re = malloc(sizeof(regex_t)); if(regcomp(re, str,REG_NOSUB)){ skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); @@ -893,6 +920,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance) free(str); } + else if(strcmp(tok,"limit_queries") == 0) + { + + QUERYSPEED* qs = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); + + tok = strtok(NULL," "); + qs->limit = atoi(tok); + + tok = strtok(NULL," "); + qs->period = atof(tok); + tok = strtok(NULL," "); + qs->cooldown = atof(tok); + ruledef->type = RT_THROTTLE; + ruledef->data = (void*)qs; + } tok = strtok(NULL," ,"); } @@ -916,7 +958,7 @@ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { FW_INSTANCE *my_instance; - int i,paramc; + int i; HASHTABLE* ht; STRLINK *ptr,*tmp; char *filename = NULL, *nl; @@ -1190,6 +1232,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) RULELIST *rulelist = NULL; USER* user = NULL; STRLINK* strln = NULL; + QUERYSPEED* queryspeed; int qlen; ipaddr = strdup(dcb->remote); @@ -1332,6 +1375,45 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } break; + + case RT_THROTTLE: + queryspeed = (QUERYSPEED*)rulelist->rule->data; + if(queryspeed->count > queryspeed->limit) + { + queryspeed->triggered = time_now; + queryspeed->count = 0; + accept = false; + + + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", + rulelist->rule->name, + queryspeed->limit, + queryspeed->period, + uname_addr, + queryspeed->cooldown); + } + else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) + { + + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + + sprintf(emsg,"Queries denied for %f seconds",uname_addr,blocked_for); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); + msg = strdup(emsg); + + accept = false; + } + else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) + { + queryspeed->count++; + } + else + { + queryspeed->first_query = time_now; + } + + break; default: break; @@ -1411,6 +1493,49 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } break; + case RT_THROTTLE: + queryspeed = (QUERYSPEED*)rulelist->rule->data; + if(queryspeed->count > queryspeed->limit) + { + + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': query limit triggered (%d queries during the last %f seconds), denying queries from user %s for %f seconds.", + rulelist->rule->name, + queryspeed->count, + queryspeed->period, + uname_addr, + queryspeed->cooldown); + queryspeed->triggered = time_now; + queryspeed->count = 0; + accept = false; + } + else if(difftime(queryspeed->triggered,time_now) < queryspeed->cooldown) + { + + double blocked_for = queryspeed->cooldown - difftime(queryspeed->triggered,time_now); + + sprintf(emsg,"Access denied for user %s for %f seconds.",uname_addr,blocked_for); + msg = strdup(emsg); + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': user %s blocked for %f seconds.", + rulelist->rule->name, + uname_addr, + blocked_for); + accept = false; + } + else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) + { + queryspeed->count++; + } + else + { + queryspeed->first_query = time_now; + } + + break; + + default: + break; } From 349557c2646be264ad225d1cfbdc440c487bcea2 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 4 Nov 2014 18:44:07 +0200 Subject: [PATCH 17/31] Added more documentation to fwfilter.c --- server/modules/filter/fwfilter.c | 51 ++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 243acdc76..19bfb89b1 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -294,6 +294,7 @@ static void* hrulefree(void* fval) */ bool valid_ip(char* str) { + /**TODO: Obsolete code*/ assert(str != NULL); int octval = 0; @@ -340,6 +341,9 @@ bool valid_ip(char* str) */ char* strip_tags(char* str) { + + /**TODO: repurpose for regex unquoting*/ + assert(str != NULL); char *ptr = str, *lead = str, *tail = NULL; @@ -378,6 +382,9 @@ char* strip_tags(char* str) */ int get_octet(char* str) { + + /**TODO: Obsolete code*/ + assert(str != NULL); int octval = 0,retval = -1; @@ -426,9 +433,10 @@ int get_octet(char* str) } /** - * Parses a string that contains an IP address and converts the last octet to '%' + * Parses a string that contains an IP address and converts the last octet to '%'. + * This modifies the string passed as the parameter. * @param str String to parse - * @return Pointer to modified string + * @return Pointer to modified string or NULL if an error occurred */ char* next_ip_class(char* str) { @@ -466,6 +474,8 @@ char* next_ip_class(char* str) */ uint32_t strtoip(char* str) { + + /**TODO: Obsolete code*/ assert(str != NULL); uint32_t ip = 0,octet = 0; @@ -493,6 +503,9 @@ uint32_t strtoip(char* str) */ uint32_t strtosubmask(char* str) { + + /**TODO: Obsolete code*/ + assert(str != NULL); uint32_t mask = 0; @@ -677,9 +690,14 @@ RULE* find_rule(char* tok, FW_INSTANCE* instance) } rlist = rlist->next; } + skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule not found: %s",tok); return NULL; } - +/** + * Adds the given rule string to the list of strings to be parsed for users. + * @param rule The rule string, assumed to be null-terminated + * @param instance The FW_FILTER instance + */ void add_users(char* rule, FW_INSTANCE* instance) { assert(rule != NULL && instance != NULL); @@ -690,6 +708,12 @@ void add_users(char* rule, FW_INSTANCE* instance) instance->userstrings = link; } +/** + * Parses the list of rule strings for users and links them against the listed rules. + * Only adds those rules that are found. If the rule isn't found a message is written to the error log. + * @param rule Rule string to parse + * @param instance The FW_FILTER instance + */ void link_rules(char* rule, FW_INSTANCE* instance) { assert(rule != NULL && instance != NULL); @@ -792,6 +816,12 @@ void link_rules(char* rule, FW_INSTANCE* instance) } + +/** + * Parse the configuration value either as a new rule or a list of users. + * @param rule The string to parse + * @param instance The FW_FILTER instance + */ void parse_rule(char* rule, FW_INSTANCE* instance) { assert(rule != NULL && instance != NULL); @@ -1060,7 +1090,6 @@ newSession(FILTER *instance, SESSION *session) static void closeSession(FILTER *instance, void *session) { - //FW_SESSION *my_session = (FW_SESSION *)session; } /** @@ -1092,7 +1121,9 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) } /** - * Generates a dummy error packet for the client. + * Generates a dummy error packet for the client with a custom message. + * @param session The FW_SESSION object + * @param msg Custom error message for the packet. * @return The dummy packet or NULL if an error occurred */ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) @@ -1151,6 +1182,10 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) return buf; } +/** + * Checks if the timerange object is active. + * @return Whether the timerange is active + */ bool inside_timerange(TIMERANGE* comp) { @@ -1185,6 +1220,11 @@ bool inside_timerange(TIMERANGE* comp) return false; } +/** + * Checks for active timeranges for a given rule. + * @param rule Pointer to a RULE object + * @return true if the rule is active + */ bool rule_is_active(RULE* rule) { TIMERANGE* times; @@ -1552,6 +1592,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) free(ipaddr); free(fullquery); + if(accept){ return my_session->down.routeQuery(my_session->down.instance, From 50a5a7892d9f807d9b52a65445d238f296fbd974 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 11 Nov 2014 14:27:23 +0200 Subject: [PATCH 18/31] Repurposed old code and deleted obsolete code. --- server/modules/filter/fwfilter.c | 190 ++----------------------------- 1 file changed, 11 insertions(+), 179 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 19bfb89b1..d5c0bbb42 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -286,54 +286,6 @@ static void* hrulefree(void* fval) } -/** - * Utility function to check if a string contains a valid IP address. - * The string handled as a null-terminated string. - * @param str String to parse - * @return True if the string contains a valid IP address. - */ -bool valid_ip(char* str) -{ - /**TODO: Obsolete code*/ - assert(str != NULL); - - int octval = 0; - bool valid = true; - char cmpbuff[32]; - char *source = str,*dest = cmpbuff,*end = strchr(str,'\0'); - - while(source < end && (int)(dest - cmpbuff) < 32 && valid){ - switch(*source){ - - case '.': - case '/': - case ' ': - case '\0': - /**End of IP, string or octet*/ - *(dest++) = '\0'; - octval = atoi(cmpbuff); - dest = cmpbuff; - valid = octval < 256 && octval > -1 ? true: false; - if(*source == '/' || *source == '\0' || *source == ' '){ - return valid; - }else{ - source++; - } - break; - - default: - /**In the IP octet, copy to buffer*/ - if(isdigit(*source)){ - *(dest++) = *(source++); - }else{ - return false; - } - break; - } - } - - return valid; -} /** * Replace all non-essential characters with whitespace from a null-terminated string. * This function modifies the passed string. @@ -342,96 +294,31 @@ bool valid_ip(char* str) char* strip_tags(char* str) { - /**TODO: repurpose for regex unquoting*/ - assert(str != NULL); - char *ptr = str, *lead = str, *tail = NULL; - int len = 0; + char *ptr = str,*re_start = NULL; + bool found = false; while(*ptr != '\0'){ if(*ptr == '"' || *ptr == '\''){ - *ptr = ' '; + if(found){ + *ptr = '\0'; + memmove(str,re_start,ptr - re_start); + break; + }else{ + *ptr = ' '; + re_start = ptr + 1; + found = true; + } } ptr++; } - /**Strip leading and trailing whitespace*/ - - while(*lead != '\0'){ - if(isspace(*lead)){ - lead++; - }else{ - tail = strchr(str,'\0') - 1; - while(tail > lead && isspace(*tail)){ - tail--; - } - len = (int)(tail - lead) + 1; - memmove(str,lead,len); - memset(str+len, 0, 1); - break; - } - } return str; } -/** - * Get one octet of IP - */ -int get_octet(char* str) -{ - - /**TODO: Obsolete code*/ - - assert(str != NULL); - - int octval = 0,retval = -1; - bool valid = false; - char cmpbuff[32]; - char *source = str,*dest = cmpbuff,*end = strchr(str,'\0') + 1; - - if(end == NULL){ - return retval; - } - - while(source < end && (int)(dest - cmpbuff) < 32 && !valid){ - switch(*source){ - - /**End of IP or string or the octet is done*/ - case '.': - case '/': - case ' ': - case '\0': - - *(dest++) = '\0'; - source++; - octval = atoi(cmpbuff); - dest = cmpbuff; - valid = octval < 256 && octval > -1 ? true: false; - if(valid) - { - retval = octval; - } - - break; - - default: - /**In the IP octet, copy to buffer*/ - if(isdigit(*source)){ - *(dest++) = *(source++); - }else{ - return -1; - } - break; - } - } - - return retval; - -} - /** * Parses a string that contains an IP address and converts the last octet to '%'. * This modifies the string passed as the parameter. @@ -467,61 +354,6 @@ char* next_ip_class(char* str) return str; } -/** - *Convert string with IP address to an unsigned 32-bit integer - * @param str String to convert - * @return Value of the IP converted to an unsigned 32-bit integer or zero in case of an error. - */ -uint32_t strtoip(char* str) -{ - - /**TODO: Obsolete code*/ - assert(str != NULL); - - uint32_t ip = 0,octet = 0; - char* tok = str; - if(!valid_ip(str)){ - return 0; - } - octet = get_octet(tok) << 24; - ip |= octet; - tok = strchr(tok,'.') + 1; - octet = get_octet(tok) << 16; - ip |= octet; - tok = strchr(tok,'.') + 1; - octet = get_octet(tok) << 8; - ip |= octet; - tok = strchr(tok,'.') + 1; - octet = get_octet(tok); - ip |= octet; - - return ip; -} - -/** - *Convert string with a subnet mask to an unsigned 32-bit integer - */ -uint32_t strtosubmask(char* str) -{ - - /**TODO: Obsolete code*/ - - assert(str != NULL); - - uint32_t mask = 0; - char *ptr; - - if(!valid_ip(str) || - (ptr = strchr(str,'/')) == NULL || - !valid_ip(++ptr)) - { - return mask; - } - - mask = strtoip(ptr); - return ~mask; -} - /** * Checks whether a null-terminated string contains two ISO-8601 compliant times separated * by a single dash. From ca13e18f539cea04ed4a3e7a38d2fd26927360a2 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 14 Nov 2014 15:59:15 +0200 Subject: [PATCH 19/31] Added query operation type requirements on where clauses and fixed a bug with regex rules. --- query_classifier/query_classifier.cc | 9 +- query_classifier/query_classifier.h | 16 +- server/modules/filter/fwfilter.c | 288 +++++++++++++++++---------- 3 files changed, 196 insertions(+), 117 deletions(-) diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index aece59d79..28291634a 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -1284,6 +1284,7 @@ char* skygw_get_affected_fields(GWBUF* buf) bool skygw_query_has_clause(GWBUF* buf) { LEX* lex; + SELECT_LEX* current; bool clause = false; if(!query_is_parsed(buf)){ @@ -1294,15 +1295,15 @@ bool skygw_query_has_clause(GWBUF* buf) return false; } - lex->current_select = lex->all_selects_list; + current = lex->all_selects_list; - while(lex->current_select) + while(current) { - if(lex->current_select->where || lex->current_select->having){ + if(current->where || current->having){ clause = true; } - lex->current_select = lex->current_select->next_select_in_list(); + current = current->next_select_in_list(); } return clause; } diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 77230d4b6..2c1a9d960 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -61,10 +61,18 @@ typedef enum { } skygw_query_type_t; typedef enum { - QUERY_OP_UNDEFINED, QUERY_OP_SELECT, QUERY_OP_CREATE_TABLE, QUERY_OP_CREATE_INDEX, - QUERY_OP_ALTER_TABLE, QUERY_OP_UPDATE, QUERY_OP_INSERT, QUERY_OP_INSERT_SELECT, - QUERY_OP_DELETE, QUERY_OP_TRUNCATE, QUERY_OP_DROP_TABLE, QUERY_OP_DROP_INDEX, - + QUERY_OP_UNDEFINED = 0, + QUERY_OP_SELECT = 1, + QUERY_OP_UPDATE = (1 << 1), + QUERY_OP_INSERT = (1 << 2), + QUERY_OP_DELETE = (1 << 3), + QUERY_OP_INSERT_SELECT = (1 << 4), + QUERY_OP_TRUNCATE = (1 << 5), + QUERY_OP_ALTER_TABLE = (1 << 6), + QUERY_OP_CREATE_TABLE = (1 << 7), + QUERY_OP_CREATE_INDEX = (1 << 8), + QUERY_OP_DROP_TABLE = (1 << 9), + QUERY_OP_DROP_INDEX = (1 << 10) }skygw_query_op_t; typedef struct parsing_info_st { diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index d5c0bbb42..e3a3fe720 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -36,9 +36,9 @@ * * users John@% Jane@192.168.0.1 match any rules block_salary * - * Rule syntax TODO: query type restrictions, update the documentation + * Rule syntax TODO: update the documentation * - * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF] [at_times VALUE...] + * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF|no_where_clause [select|insert|delete|update]] [at_times VALUE...] */ #include #include @@ -96,7 +96,7 @@ static FILTER_OBJECT MyObject = { /** * Query types */ -typedef enum{ +/*typedef enum{ NONE = 0, ALL = (1), SELECT = (1<<1), @@ -104,7 +104,7 @@ typedef enum{ UPDATE = (1<<3), DELETE = (1<<4) }querytype_t; - +*/ /** * Rule types */ @@ -114,7 +114,8 @@ typedef enum { RT_THROTTLE, RT_PERMISSION, RT_WILDCARD, - RT_REGEX + RT_REGEX, + RT_CLAUSE }ruletype_t; /** @@ -151,7 +152,7 @@ typedef struct rule_t{ void* data; char* name; ruletype_t type; - querytype_t on_queries; + skygw_query_op_t on_queries; bool allow; TIMERANGE* active; }RULE; @@ -354,6 +355,42 @@ char* next_ip_class(char* str) return str; } +bool parse_querytypes(char* str,RULE* rule) +{ + char buffer[512]; + char *ptr,*dest; + bool done = false; + rule->on_queries = 0; + ptr = str; + dest = buffer; + + while(ptr - buffer < 512) + { + if(*ptr == '|' || (done = *ptr == '\0')){ + *dest = '\0'; + if(strcmp(buffer,"select") == 0){ + rule->on_queries |= QUERY_OP_SELECT; + }else if(strcmp(buffer,"insert") == 0){ + rule->on_queries |= QUERY_OP_INSERT; + }else if(strcmp(buffer,"update") == 0){ + rule->on_queries |= QUERY_OP_UPDATE; + }else if(strcmp(buffer,"delete") == 0){ + rule->on_queries |= QUERY_OP_DELETE; + } + + if(done){ + return true; + } + + dest = buffer; + ptr++; + }else{ + *dest++ = *ptr++; + } + } + return false; +} + /** * Checks whether a null-terminated string contains two ISO-8601 compliant times separated * by a single dash. @@ -677,7 +714,7 @@ void parse_rule(char* rule, FW_INSTANCE* instance) rlist = (RULELIST*)calloc(1,sizeof(RULELIST)); ruledef->name = strdup(tok); ruledef->type = RT_UNDEFINED; - ruledef->on_queries = ALL; + ruledef->on_queries = QUERY_OP_UNDEFINED; rlist->rule = ruledef; rlist->next = instance->rules; instance->rules = rlist; @@ -744,10 +781,15 @@ void parse_rule(char* rule, FW_INSTANCE* instance) { bool escaped = false; regex_t *re; - char* start = tok, *str; - - tok += 6; + char* start, *str; + tok = strtok(NULL," "); + + while(*tok == '\'' || *tok == '"'){ + tok++; + } + start = tok; + while(isspace(*tok) || *tok == '\'' || *tok == '"'){ tok++; } @@ -797,6 +839,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance) ruledef->type = RT_THROTTLE; ruledef->data = (void*)qs; } + else if(strcmp(tok,"no_where_clause") == 0) + { + ruledef->type = RT_CLAUSE; + ruledef->data = (void*)mode; + } + else if(strcmp(tok,"on_operations") == 0) + { + tok = strtok(NULL," "); + if(!parse_querytypes(tok,ruledef)){ + skygw_log_write(LOGFILE_ERROR, + "fwfilter: Invalid query type" + "requirements on where/having clauses: %s." + ,tok); + } + } tok = strtok(NULL," ,"); } @@ -1106,7 +1163,8 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) STRLINK* strln = NULL; QUERYSPEED* queryspeed; int qlen; - + skygw_query_op_t optype; + ipaddr = strdup(dcb->remote); sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); @@ -1145,6 +1203,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) if(!query_is_parsed(queue)){ parse_query(queue); } + optype = query_classifier_get_operation(queue); modutil_extract_SQL(queue, &ptr, &qlen); fullquery = malloc((qlen + 1) * sizeof(char)); memcpy(fullquery,ptr,qlen); @@ -1160,138 +1219,149 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) rulelist = rulelist->next; continue; } - - switch(rulelist->rule->type){ - - case RT_UNDEFINED: - skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); - break; - - case RT_REGEX: + if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ - if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ + switch(rulelist->rule->type){ + + case RT_UNDEFINED: + skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); + break; + + case RT_REGEX: - accept = rulelist->rule->allow; + if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ + + accept = rulelist->rule->allow; + if(!rulelist->rule->allow){ + msg = strdup("Permission denied, query matched regular expression."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); + goto queryresolved; + }else{ + break; + } + } + + break; + + case RT_PERMISSION: if(!rulelist->rule->allow){ - msg = strdup("Permission denied, query matched regular expression."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); + accept = false; + msg = strdup("Permission denied at this time."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); goto queryresolved; }else{ break; } - } - - break; - - case RT_PERMISSION: - if(!rulelist->rule->allow){ - accept = false; - msg = strdup("Permission denied at this time."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); - goto queryresolved; - }else{ break; - } - break; - case RT_COLUMN: + case RT_COLUMN: - if(is_sql && is_real){ + if(is_sql && is_real){ - strln = (STRLINK*)rulelist->rule->data; - where = skygw_get_affected_fields(queue); + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); - if(where != NULL){ + if(where != NULL){ - while(strln){ - if(strstr(where,strln->value)){ + while(strln){ + if(strstr(where,strln->value)){ + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + sprintf(emsg,"Permission denied to column '%s'.",strln->value); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); + msg = strdup(emsg); + goto queryresolved; + }else{ + break; + } + } + strln = strln->next; + } + } + } + + break; + + case RT_WILDCARD: + + + if(is_sql && is_real){ + + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + if(strchr(where,'*')){ accept = rulelist->rule->allow; if(!rulelist->rule->allow){ - sprintf(emsg,"Permission denied to column '%s'.",strln->value); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); - msg = strdup(emsg); + msg = strdup("Usage of wildcard denied."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); goto queryresolved; - }else{ - break; } } - strln = strln->next; } } - } - break; + break; - case RT_WILDCARD: + case RT_THROTTLE: + queryspeed = (QUERYSPEED*)rulelist->rule->data; + if(queryspeed->count > queryspeed->limit) + { + queryspeed->triggered = time_now; + queryspeed->count = 0; + accept = false; - if(is_sql && is_real){ - - where = skygw_get_affected_fields(queue); - - if(where != NULL){ - if(strchr(where,'*')){ - - accept = rulelist->rule->allow; - - if(!rulelist->rule->allow){ - msg = strdup("Usage of wildcard denied."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); - goto queryresolved; - } + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", + rulelist->rule->name, + queryspeed->limit, + queryspeed->period, + uname_addr, + queryspeed->cooldown); } - } - } - - break; + else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) + { - case RT_THROTTLE: - queryspeed = (QUERYSPEED*)rulelist->rule->data; - if(queryspeed->count > queryspeed->limit) - { - queryspeed->triggered = time_now; - queryspeed->count = 0; - accept = false; + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); - - skygw_log_write(LOGFILE_TRACE, - "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", - rulelist->rule->name, - queryspeed->limit, - queryspeed->period, - uname_addr, - queryspeed->cooldown); - } - else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) - { - - double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); - - sprintf(emsg,"Queries denied for %f seconds",uname_addr,blocked_for); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); - msg = strdup(emsg); + sprintf(emsg,"Queries denied for %f seconds",blocked_for); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); + msg = strdup(emsg); - accept = false; - } - else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) - { - queryspeed->count++; - } - else - { - queryspeed->first_query = time_now; - } + accept = false; + } + else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) + { + queryspeed->count++; + } + else + { + queryspeed->first_query = time_now; + } - break; - - default: - break; + break; + case RT_CLAUSE: + if(is_sql && is_real && !skygw_query_has_clause(queue)) + { + accept = false; + msg = strdup("Required WHERE/HAVING clause is missing."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", + rulelist->rule->name); + } + break; + + default: + break; + + } } - rulelist = rulelist->next; } From 67030d0019d93e84aa722d393729f02bb77cd1d7 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 17 Nov 2014 14:52:26 +0200 Subject: [PATCH 20/31] Updated test harness. --- server/modules/filter/fwfilter.c | 60 ++++++++++----------- server/modules/filter/test/CMakeLists.txt | 2 +- server/modules/filter/test/harness.h | 14 ++++- server/modules/filter/test/harness_common.c | 60 ++++++++++++++++++--- server/modules/filter/test/harness_ui.c | 4 +- server/modules/filter/test/harness_util.c | 6 ++- 6 files changed, 103 insertions(+), 43 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index e3a3fe720..2aadec959 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -20,25 +20,37 @@ * @file fwfilter.c * Firewall Filter * - * A filter that acts as a firewall, denying queries that do not meet a set requirements. + * A filter that acts as a firewall, denying queries that do not meet a set of rules. * - * This filter uses "rules" to define the blcking parameters. Write the rules to a separate file and - * set the path to the file: + * Filter configuration parameters: * - * rules= + * rules= Location of the rule file * - * - * For example, to define a rule denying users from accessing the column 'salary' between 15:00 and 17:00, the following is needed in the configuration file: + * Rules are defined in a separate rule file that lists all the rules and the users to whom the rules are applied. + * Rules follow a simple syntax that denies the queries that meet the requirements of the rules. + * For example, to define a rule denying users from accessing the column 'salary' between + * the times 15:00 and 17:00, the following rule is to be configured into the configuration file: * * rule block_salary deny columns salary at_times 15:00:00-17:00:00 * - * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: + * The users are matched by username and network address. Wildcard values can be provided by using the '%' character. + * For example, to apply this rule to users John, connecting from any address + * that starts with the octets 198.168.%, and Jane, connecting from the address 192.168.0.1: * - * users John@% Jane@192.168.0.1 match any rules block_salary + * users John@192.168.% Jane@192.168.0.1 match any rules block_salary * - * Rule syntax TODO: update the documentation * - * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF|no_where_clause [select|insert|delete|update]] [at_times VALUE...] + * The 'match' keyword controls the way rules are matched. If it is set to 'any' the first active rule that is triggered will cause the query to be denied. + * If it is set to 'all' all the active rules need to match before the query is denied. + * + * Rule syntax + * + * rule NAME deny [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF | no_where_clause] [at_times VALUE...] [on_queries [select|update|insert|delete]] + * + * User syntax + * + * users NAME ... match [any|all] rules RULE ... + * */ #include #include @@ -91,20 +103,7 @@ static FILTER_OBJECT MyObject = { diagnostic, }; -#define QUERY_TYPES 5 -/** - * Query types - */ -/*typedef enum{ - NONE = 0, - ALL = (1), - SELECT = (1<<1), - INSERT = (1<<2), - UPDATE = (1<<3), - DELETE = (1<<4) -}querytype_t; -*/ /** * Rule types */ @@ -259,12 +258,6 @@ void* rlistdup(void* fval) return (void*)rule; } -/* - static void* hruledup(void* fval) - { - return fval; - }*/ - static void* hrulefree(void* fval) { @@ -291,6 +284,7 @@ static void* hrulefree(void* fval) * Replace all non-essential characters with whitespace from a null-terminated string. * This function modifies the passed string. * @param str String to purify + * @return Pointer to the modified string */ char* strip_tags(char* str) { @@ -354,7 +348,12 @@ char* next_ip_class(char* str) return str; } - +/** + * Parses the strign for the types of queries this rule should be applied to. + * @param str String to parse + * @param rule Poiter to a rule + * @return True if the string was parses successfully, false if an error occurred + */ bool parse_querytypes(char* str,RULE* rule) { char buffer[512]; @@ -562,6 +561,7 @@ RULE* find_rule(char* tok, FW_INSTANCE* instance) skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule not found: %s",tok); return NULL; } + /** * Adds the given rule string to the list of strings to be parsed for users. * @param rule The rule string, assumed to be null-terminated diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 1813a5a26..b24a2b2bc 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -10,4 +10,4 @@ add_executable(harness_ui harness_ui.c harness_common.c) add_executable(harness harness_util.c harness_common.c ${CORE}) target_link_libraries(harness_ui fullcore log_manager utils) target_link_libraries(harness fullcore) -add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 && diff ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output;exit $?") \ No newline at end of file +add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") \ No newline at end of file diff --git a/server/modules/filter/test/harness.h b/server/modules/filter/test/harness.h index c30c266c2..a306a6811 100644 --- a/server/modules/filter/test/harness.h +++ b/server/modules/filter/test/harness.h @@ -69,6 +69,7 @@ #include #include #include +#include /** * A single name-value pair and a link to the next item in the * configuration. @@ -117,6 +118,7 @@ typedef struct int running; int verbose; /**Whether to print to stdout*/ int infile; /**A file where the queries are loaded from*/ + int expected; int error; char* mod_dir; /**Module directory absolute path*/ char* infile_name; @@ -172,7 +174,7 @@ typedef packet_t PACKET; /** * Initialize the static instance. */ -int harness_init(int argc,char** argv); +int harness_init(int argc,char** argv,HARNESS_INSTANCE** inst); /** * Frees all the query buffers @@ -359,4 +361,14 @@ GWBUF* gen_packet(PACKET pkt); */ int process_opts(int argc, char** argv); +/** + * Compares the contents of two files. + * This function resets the offsets of the file descriptors and leaves them in an + * undefined state. + * @param a The first file + * @param b The second file + * @return 0 if the files do not differ and 1 if they do or an error occurred. + */ +int compare_files(int a, int b); + #endif diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index fe782bc0d..7564a891d 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -1,6 +1,6 @@ #include -int harness_init(int argc, char** argv){ +int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ int i = 0; if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ skygw_logmanager_init(0,NULL); @@ -14,9 +14,11 @@ int harness_init(int argc, char** argv){ return 1; } + *inst = &instance; instance.running = 1; instance.infile = -1; instance.outfile = -1; + instance.expected = -1; instance.buff_ind = -1; instance.last_ind = -1; instance.sess_ind = -1; @@ -84,15 +86,17 @@ void free_buffers() } int open_file(char* str, unsigned int write) { - int mode; + int mode,fd; if(write){ mode = O_RDWR|O_CREAT; }else{ mode = O_RDONLY; } - - return open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH); + if((fd = open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH)) < 0){ + printf("Error %d: %s\n",errno,strerror(errno)); + } + return fd; } @@ -863,9 +867,14 @@ void work_buffer(void* thr_num) index < instance.session_count && instance.buff_ind < instance.buffer_count) { - instance.head->instance->routeQuery(instance.head->filter, + if(instance.head->instance->routeQuery(instance.head->filter, instance.head->session[index], - instance.buffer[instance.buff_ind]); + instance.buffer[instance.buff_ind]) == 0){ + if(instance.outfile > 0){ + const char* msg = "Query returned 0.\n"; + write(instance.outfile,msg,strlen(msg)); + } + } if(instance.tail->instance->clientReply){ instance.tail->instance->clientReply(instance.tail->filter, instance.tail->session[index], @@ -929,10 +938,11 @@ GWBUF* gen_packet(PACKET pkt) } + int process_opts(int argc, char** argv) { int fd, buffsize = 1024; - int rd,rdsz, rval; + int rd,rdsz, rval = 0; size_t fsize; char *buff = calloc(buffsize,sizeof(char)), *tok = NULL; @@ -985,10 +995,18 @@ int process_opts(int argc, char** argv) close(fd); return 1; } + char* conf_name = NULL; - while((rd = getopt(argc,argv,"m:c:i:o:s:t:d:qh")) > 0){ + rval = 0; + + while((rd = getopt(argc,argv,"e:m:c:i:o:s:t:d:qh")) > 0){ switch(rd){ + case 'e': + instance.expected = open_file(optarg,0); + printf("Expected output is read from: %s\n",optarg); + break; + case 'o': instance.outfile = open_file(optarg,1); printf("Output is written to: %s\n",optarg); @@ -1053,6 +1071,7 @@ int process_opts(int argc, char** argv) } } printf("\n"); + if(conf_name && load_config(conf_name)){ load_query(); }else{ @@ -1061,5 +1080,30 @@ int process_opts(int argc, char** argv) free(conf_name); close(fd); + return rval; +} + +int compare_files(int a,int b) +{ + char in[4098]; + char exp[4098]; + int line = 1; + + if(a < 1 || b < 1){ + return 1; + } + + if(lseek(a,0,SEEK_SET) < 0 || + lseek(b,0,SEEK_SET) < 0){ + return 1; + } + + while(fdgets(a,in,4098) && fdgets(b,exp,4098)){ + if(strcmp(in,exp)){ + printf("The files differ at line %d:\n%s\n-------------------------------------\n%s\n",line,in,exp); + return 1; + } + line++; + } return 0; } diff --git a/server/modules/filter/test/harness_ui.c b/server/modules/filter/test/harness_ui.c index 9b38f648c..8921a88bc 100755 --- a/server/modules/filter/test/harness_ui.c +++ b/server/modules/filter/test/harness_ui.c @@ -6,9 +6,9 @@ int main(int argc, char** argv){ char* tk; FILTERCHAIN* tmp_chn; FILTERCHAIN* del_chn; - + HARNESS_INSTANCE* hinstance; - if(harness_init(argc,argv)){ + if(harness_init(argc,argv,&hinstance)){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); diff --git a/server/modules/filter/test/harness_util.c b/server/modules/filter/test/harness_util.c index fb6905445..705bc3c3b 100644 --- a/server/modules/filter/test/harness_util.c +++ b/server/modules/filter/test/harness_util.c @@ -1,7 +1,8 @@ #include int main(int argc,char** argv) { - if(harness_init(argc,argv) || instance.error){ + HARNESS_INSTANCE* inst; + if(harness_init(argc,argv,&inst) || inst->error){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); @@ -10,5 +11,8 @@ int main(int argc,char** argv) } route_buffers(); + if(inst->expected){ + return compare_files(inst->outfile,inst->expected); + } return 0; } From f233bfab661e9170516bb495b60e59e900fcfc80 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 17 Nov 2014 14:52:26 +0200 Subject: [PATCH 21/31] Updated test harness and added preliminary firewall filter tests --- server/modules/filter/fwfilter.c | 60 ++++---- server/modules/filter/test/CMakeLists.txt | 4 +- server/modules/filter/test/fwtest.cnf | 159 ++++++++++++++++++++ server/modules/filter/test/fwtest.cnf.in | 159 ++++++++++++++++++++ server/modules/filter/test/fwtest.input | 7 + server/modules/filter/test/harness.h | 14 +- server/modules/filter/test/harness_common.c | 73 +++++++-- server/modules/filter/test/harness_ui.c | 4 +- server/modules/filter/test/harness_util.c | 6 +- server/modules/filter/test/rules | 3 + 10 files changed, 442 insertions(+), 47 deletions(-) create mode 100644 server/modules/filter/test/fwtest.cnf create mode 100644 server/modules/filter/test/fwtest.cnf.in create mode 100644 server/modules/filter/test/fwtest.input create mode 100644 server/modules/filter/test/rules diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index e3a3fe720..2aadec959 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -20,25 +20,37 @@ * @file fwfilter.c * Firewall Filter * - * A filter that acts as a firewall, denying queries that do not meet a set requirements. + * A filter that acts as a firewall, denying queries that do not meet a set of rules. * - * This filter uses "rules" to define the blcking parameters. Write the rules to a separate file and - * set the path to the file: + * Filter configuration parameters: * - * rules= + * rules= Location of the rule file * - * - * For example, to define a rule denying users from accessing the column 'salary' between 15:00 and 17:00, the following is needed in the configuration file: + * Rules are defined in a separate rule file that lists all the rules and the users to whom the rules are applied. + * Rules follow a simple syntax that denies the queries that meet the requirements of the rules. + * For example, to define a rule denying users from accessing the column 'salary' between + * the times 15:00 and 17:00, the following rule is to be configured into the configuration file: * * rule block_salary deny columns salary at_times 15:00:00-17:00:00 * - * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: + * The users are matched by username and network address. Wildcard values can be provided by using the '%' character. + * For example, to apply this rule to users John, connecting from any address + * that starts with the octets 198.168.%, and Jane, connecting from the address 192.168.0.1: * - * users John@% Jane@192.168.0.1 match any rules block_salary + * users John@192.168.% Jane@192.168.0.1 match any rules block_salary * - * Rule syntax TODO: update the documentation * - * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF|no_where_clause [select|insert|delete|update]] [at_times VALUE...] + * The 'match' keyword controls the way rules are matched. If it is set to 'any' the first active rule that is triggered will cause the query to be denied. + * If it is set to 'all' all the active rules need to match before the query is denied. + * + * Rule syntax + * + * rule NAME deny [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF | no_where_clause] [at_times VALUE...] [on_queries [select|update|insert|delete]] + * + * User syntax + * + * users NAME ... match [any|all] rules RULE ... + * */ #include #include @@ -91,20 +103,7 @@ static FILTER_OBJECT MyObject = { diagnostic, }; -#define QUERY_TYPES 5 -/** - * Query types - */ -/*typedef enum{ - NONE = 0, - ALL = (1), - SELECT = (1<<1), - INSERT = (1<<2), - UPDATE = (1<<3), - DELETE = (1<<4) -}querytype_t; -*/ /** * Rule types */ @@ -259,12 +258,6 @@ void* rlistdup(void* fval) return (void*)rule; } -/* - static void* hruledup(void* fval) - { - return fval; - }*/ - static void* hrulefree(void* fval) { @@ -291,6 +284,7 @@ static void* hrulefree(void* fval) * Replace all non-essential characters with whitespace from a null-terminated string. * This function modifies the passed string. * @param str String to purify + * @return Pointer to the modified string */ char* strip_tags(char* str) { @@ -354,7 +348,12 @@ char* next_ip_class(char* str) return str; } - +/** + * Parses the strign for the types of queries this rule should be applied to. + * @param str String to parse + * @param rule Poiter to a rule + * @return True if the string was parses successfully, false if an error occurred + */ bool parse_querytypes(char* str,RULE* rule) { char buffer[512]; @@ -562,6 +561,7 @@ RULE* find_rule(char* tok, FW_INSTANCE* instance) skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule not found: %s",tok); return NULL; } + /** * Adds the given rule string to the list of strings to be parsed for users. * @param rule The rule string, assumed to be null-terminated diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 1813a5a26..5a94170c8 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -10,4 +10,6 @@ add_executable(harness_ui harness_ui.c harness_common.c) add_executable(harness harness_util.c harness_common.c ${CORE}) target_link_libraries(harness_ui fullcore log_manager utils) target_link_libraries(harness fullcore) -add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 && diff ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output;exit $?") \ No newline at end of file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwtest.cnf @ONLY) +add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") +add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.cnf b/server/modules/filter/test/fwtest.cnf new file mode 100644 index 000000000..ed27b67cd --- /dev/null +++ b/server/modules/filter/test/fwtest.cnf @@ -0,0 +1,159 @@ +# +# Example MaxScale.cnf configuration file +# +# +# +# Number of server threads +# Valid options are: +# threads= + +[maxscale] +threads=1 + +# Define a monitor that can be used to determine the state and role of +# the servers. +# +# Valid options are: +# +# module= +# servers=,,... +# user = +# passwd= +# monitor_interval= + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd + +# A series of service definition +# +# Valid options are: +# +# router= +# servers=,,... +# user= +# passwd= +# enable_root_user=<0 or 1, default is 0> +# version_string= +# +# Valid router modules currently are: +# readwritesplit, readconnroute and debugcli + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +#filters=MQ + +[RW Split Hint Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +filters=Hint + + +[Read Connection Router] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxuser +passwd=maxpwd + + +[HTTPD Router] +type=service +router=testroute +servers=server1,server2,server3 + +[Debug Interface] +type=service +router=debugcli + + +[Hint] +type=filter +module=fwfilter +rules=@CMAKE_CURRENT_BINARY_DIR@/rules + + +# Listener definitions for the services +# +# Valid options are: +# +# service= +# protocol= +# port= +# address=
+# socket= + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Hint Listener] +type=listener +service=RW Split Hint Router +protocol=MySQLClient +port=4009 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[Debug Listener] +type=listener +service=Debug Interface +protocol=telnetd +port=4442 +#address=127.0.0.1 + +[HTTPD Listener] +type=listener +service=HTTPD Router +protocol=HTTPD +port=6444 + +# Definition of the servers + +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend + +[server3] +type=server +address=127.0.0.1 +port=3002 +protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3003 +protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.cnf.in b/server/modules/filter/test/fwtest.cnf.in new file mode 100644 index 000000000..ef1623c8b --- /dev/null +++ b/server/modules/filter/test/fwtest.cnf.in @@ -0,0 +1,159 @@ +# +# Example MaxScale.cnf configuration file +# +# +# +# Number of server threads +# Valid options are: +# threads= + +[maxscale] +threads=1 + +# Define a monitor that can be used to determine the state and role of +# the servers. +# +# Valid options are: +# +# module= +# servers=,,... +# user = +# passwd= +# monitor_interval= + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd + +# A series of service definition +# +# Valid options are: +# +# router= +# servers=,,... +# user= +# passwd= +# enable_root_user=<0 or 1, default is 0> +# version_string= +# +# Valid router modules currently are: +# readwritesplit, readconnroute and debugcli + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +#filters=MQ + +[RW Split Hint Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +filters=Hint + + +[Read Connection Router] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxuser +passwd=maxpwd + + +[HTTPD Router] +type=service +router=testroute +servers=server1,server2,server3 + +[Debug Interface] +type=service +router=debugcli + + +[Hint] +type=filter +module=fwfilter +rules=@CMAKE_CURRENT_SOURCE_DIR@/rules + + +# Listener definitions for the services +# +# Valid options are: +# +# service= +# protocol= +# port= +# address=
+# socket= + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Hint Listener] +type=listener +service=RW Split Hint Router +protocol=MySQLClient +port=4009 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[Debug Listener] +type=listener +service=Debug Interface +protocol=telnetd +port=4442 +#address=127.0.0.1 + +[HTTPD Listener] +type=listener +service=HTTPD Router +protocol=HTTPD +port=6444 + +# Definition of the servers + +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend + +[server3] +type=server +address=127.0.0.1 +port=3002 +protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3003 +protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.input b/server/modules/filter/test/fwtest.input new file mode 100644 index 000000000..befe720c6 --- /dev/null +++ b/server/modules/filter/test/fwtest.input @@ -0,0 +1,7 @@ +select 1; +delete from t1; +select 1; +update t1 set id=1; +select 1; +select id from t1 union select User from mysql.users; +select 1; \ No newline at end of file diff --git a/server/modules/filter/test/harness.h b/server/modules/filter/test/harness.h index c30c266c2..a306a6811 100644 --- a/server/modules/filter/test/harness.h +++ b/server/modules/filter/test/harness.h @@ -69,6 +69,7 @@ #include #include #include +#include /** * A single name-value pair and a link to the next item in the * configuration. @@ -117,6 +118,7 @@ typedef struct int running; int verbose; /**Whether to print to stdout*/ int infile; /**A file where the queries are loaded from*/ + int expected; int error; char* mod_dir; /**Module directory absolute path*/ char* infile_name; @@ -172,7 +174,7 @@ typedef packet_t PACKET; /** * Initialize the static instance. */ -int harness_init(int argc,char** argv); +int harness_init(int argc,char** argv,HARNESS_INSTANCE** inst); /** * Frees all the query buffers @@ -359,4 +361,14 @@ GWBUF* gen_packet(PACKET pkt); */ int process_opts(int argc, char** argv); +/** + * Compares the contents of two files. + * This function resets the offsets of the file descriptors and leaves them in an + * undefined state. + * @param a The first file + * @param b The second file + * @return 0 if the files do not differ and 1 if they do or an error occurred. + */ +int compare_files(int a, int b); + #endif diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index fe782bc0d..c775333aa 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -1,6 +1,6 @@ #include -int harness_init(int argc, char** argv){ +int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ int i = 0; if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ skygw_logmanager_init(0,NULL); @@ -14,9 +14,11 @@ int harness_init(int argc, char** argv){ return 1; } + *inst = &instance; instance.running = 1; instance.infile = -1; instance.outfile = -1; + instance.expected = -1; instance.buff_ind = -1; instance.last_ind = -1; instance.sess_ind = -1; @@ -84,15 +86,17 @@ void free_buffers() } int open_file(char* str, unsigned int write) { - int mode; + int mode,fd; if(write){ mode = O_RDWR|O_CREAT; }else{ mode = O_RDONLY; } - - return open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH); + if((fd = open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH)) < 0){ + printf("Error %d: %s\n",errno,strerror(errno)); + } + return fd; } @@ -608,6 +612,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) { FILTER_PARAMETER** fparams = NULL; int i, paramc = -1; + int sess_err = 0; + int x; if(cnf == NULL){ fparams = read_params(¶mc); @@ -672,13 +678,16 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - int sess_err = 0; if(cnf && fc && fc->instance){ fc->filter = (FILTER*)fc->instance->createInstance(NULL,fparams); - + if(fc->filter == NULL){ + printf("Error loading filter:%s: createInstance returned NULL.\n",fc->name); + sess_err = 1; + goto error; + } for(i = 0;isession[i] = fc->instance->newSession(fc->filter, fc->session[i])) && @@ -746,8 +755,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - - int x; + error: + if(fparams){ for(x = 0;xinstance->routeQuery(instance.head->filter, + if(instance.head->instance->routeQuery(instance.head->filter, instance.head->session[index], - instance.buffer[instance.buff_ind]); + instance.buffer[instance.buff_ind]) == 0){ + if(instance.outfile > 0){ + const char* msg = "Query returned 0.\n"; + write(instance.outfile,msg,strlen(msg)); + } + } if(instance.tail->instance->clientReply){ instance.tail->instance->clientReply(instance.tail->filter, instance.tail->session[index], @@ -929,10 +943,11 @@ GWBUF* gen_packet(PACKET pkt) } + int process_opts(int argc, char** argv) { int fd, buffsize = 1024; - int rd,rdsz, rval; + int rd,rdsz, rval = 0; size_t fsize; char *buff = calloc(buffsize,sizeof(char)), *tok = NULL; @@ -985,10 +1000,18 @@ int process_opts(int argc, char** argv) close(fd); return 1; } + char* conf_name = NULL; - while((rd = getopt(argc,argv,"m:c:i:o:s:t:d:qh")) > 0){ + rval = 0; + + while((rd = getopt(argc,argv,"e:m:c:i:o:s:t:d:qh")) > 0){ switch(rd){ + case 'e': + instance.expected = open_file(optarg,0); + printf("Expected output is read from: %s\n",optarg); + break; + case 'o': instance.outfile = open_file(optarg,1); printf("Output is written to: %s\n",optarg); @@ -1053,6 +1076,7 @@ int process_opts(int argc, char** argv) } } printf("\n"); + if(conf_name && load_config(conf_name)){ load_query(); }else{ @@ -1061,5 +1085,30 @@ int process_opts(int argc, char** argv) free(conf_name); close(fd); + return rval; +} + +int compare_files(int a,int b) +{ + char in[4098]; + char exp[4098]; + int line = 1; + + if(a < 1 || b < 1){ + return 1; + } + + if(lseek(a,0,SEEK_SET) < 0 || + lseek(b,0,SEEK_SET) < 0){ + return 1; + } + + while(fdgets(a,in,4098) && fdgets(b,exp,4098)){ + if(strcmp(in,exp)){ + printf("The files differ at line %d:\n%s\n-------------------------------------\n%s\n",line,in,exp); + return 1; + } + line++; + } return 0; } diff --git a/server/modules/filter/test/harness_ui.c b/server/modules/filter/test/harness_ui.c index 9b38f648c..8921a88bc 100755 --- a/server/modules/filter/test/harness_ui.c +++ b/server/modules/filter/test/harness_ui.c @@ -6,9 +6,9 @@ int main(int argc, char** argv){ char* tk; FILTERCHAIN* tmp_chn; FILTERCHAIN* del_chn; - + HARNESS_INSTANCE* hinstance; - if(harness_init(argc,argv)){ + if(harness_init(argc,argv,&hinstance)){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); diff --git a/server/modules/filter/test/harness_util.c b/server/modules/filter/test/harness_util.c index fb6905445..705bc3c3b 100644 --- a/server/modules/filter/test/harness_util.c +++ b/server/modules/filter/test/harness_util.c @@ -1,7 +1,8 @@ #include int main(int argc,char** argv) { - if(harness_init(argc,argv) || instance.error){ + HARNESS_INSTANCE* inst; + if(harness_init(argc,argv,&inst) || inst->error){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); @@ -10,5 +11,8 @@ int main(int argc,char** argv) } route_buffers(); + if(inst->expected){ + return compare_files(inst->outfile,inst->expected); + } return 0; } diff --git a/server/modules/filter/test/rules b/server/modules/filter/test/rules new file mode 100644 index 000000000..8e6c4ab3c --- /dev/null +++ b/server/modules/filter/test/rules @@ -0,0 +1,3 @@ +rule union_regex deny regex '.*union.*' +rule dont_delete_everything deny no_where_clause on_operations delete|update at_times 12:00:00-18:00:00 +users %@% match any rules union_regex dont_delete_everything \ No newline at end of file From e4bf42fcf43aae4943fe335380aad12f6561c38b Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 17 Nov 2014 18:42:20 +0200 Subject: [PATCH 22/31] Added a dummy session and dcb to the filter harness. --- server/modules/filter/test/harness.h | 2 ++ server/modules/filter/test/harness_common.c | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/server/modules/filter/test/harness.h b/server/modules/filter/test/harness.h index c30c266c2..ac3982d19 100644 --- a/server/modules/filter/test/harness.h +++ b/server/modules/filter/test/harness.h @@ -69,6 +69,7 @@ #include #include #include +#include /** * A single name-value pair and a link to the next item in the * configuration. @@ -125,6 +126,7 @@ typedef struct FILTERCHAIN* head; /**The head of the filter chain*/ FILTERCHAIN* tail; /**The tail of the filter chain*/ GWBUF** buffer; /**Buffers that are fed to the filter chain*/ + SESSION* session; int buffer_count; int session_count; DOWNSTREAM dummyrouter; /**Dummy downstream router for data extraction*/ diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index fe782bc0d..0a853bae0 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -1,5 +1,11 @@ #include +int dcbfun(struct dcb* dcb, GWBUF * buffer) +{ + printf("Data was written to client DCB.\n"); + return 1; +} + int harness_init(int argc, char** argv){ int i = 0; if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ @@ -20,6 +26,14 @@ int harness_init(int argc, char** argv){ instance.buff_ind = -1; instance.last_ind = -1; instance.sess_ind = -1; + instance.session = calloc(1,sizeof(SESSION)); + MYSQL_session* mysqlsess = calloc(1,sizeof(MYSQL_session)); + DCB* dcb = calloc(1,sizeof(DCB)); + + sprintf(mysqlsess->user,"dummyuser"); + sprintf(mysqlsess->db,"dummydb"); + dcb->func.write = dcbfun; + instance.session->client = (void*)dcb; process_opts(argc,argv); @@ -681,7 +695,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) for(i = 0;isession[i] = fc->instance->newSession(fc->filter, fc->session[i])) && + if((fc->session[i] = fc->instance->newSession(fc->filter, instance.session)) && (fc->down[i] = calloc(1,sizeof(DOWNSTREAM))) && (fc->up[i] = calloc(1,sizeof(UPSTREAM)))){ @@ -693,7 +707,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) fc->instance->setUpstream(fc->filter, fc->session[i], fc->up[i]); }else{ skygw_log_write(LOGFILE_MESSAGE, - "Warning: The filter %s does not support client relies.\n",fc->name); + "Warning: The filter %s does not support client replies.\n",fc->name); } if(fc->next && fc->next->next){ From 2e7419e94225efb397c55cc701c9622375c3feb2 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 17 Nov 2014 19:33:34 +0200 Subject: [PATCH 23/31] Fixed an infinite loop if network address reached '%' and fixed some null pointer references. --- server/modules/filter/fwfilter.c | 8 +- server/modules/filter/test/CMakeLists.txt | 5 +- server/modules/filter/test/fwtest.cnf | 159 -------------------- server/modules/filter/test/fwtest.cnf.in | 157 +------------------ server/modules/filter/test/fwtest.output | 4 + server/modules/filter/test/harness.h | 15 +- server/modules/filter/test/harness_common.c | 78 ++++++++-- server/modules/filter/test/harness_util.c | 30 ++++ server/modules/filter/test/rules | 2 +- 9 files changed, 126 insertions(+), 332 deletions(-) delete mode 100644 server/modules/filter/test/fwtest.cnf create mode 100755 server/modules/filter/test/fwtest.output diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 2aadec959..67f12209e 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -318,12 +318,17 @@ char* strip_tags(char* str) * Parses a string that contains an IP address and converts the last octet to '%'. * This modifies the string passed as the parameter. * @param str String to parse - * @return Pointer to modified string or NULL if an error occurred + * @return Pointer to modified string or NULL if an error occurred or the string can't be made any less specific */ char* next_ip_class(char* str) { assert(str != NULL); + /**The least specific form is reached*/ + if(*str == '%'){ + return NULL; + } + char* ptr = strchr(str,'\0'); if(ptr == NULL){ @@ -346,6 +351,7 @@ char* next_ip_class(char* str) *++ptr = '%'; *++ptr = '\0'; + return str; } /** diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 5a94170c8..6733c10cd 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -8,8 +8,9 @@ endforeach() include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(harness_ui harness_ui.c harness_common.c) add_executable(harness harness_util.c harness_common.c ${CORE}) -target_link_libraries(harness_ui fullcore log_manager utils) -target_link_libraries(harness fullcore) +target_link_libraries(harness_ui fullcore log_manager utils ${EMBEDDED_LIB}) +target_link_libraries(harness fullcore ${EMBEDDED_LIB}) +file(COPY ${ERRMSG} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwtest.cnf @ONLY) add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.cnf b/server/modules/filter/test/fwtest.cnf deleted file mode 100644 index ed27b67cd..000000000 --- a/server/modules/filter/test/fwtest.cnf +++ /dev/null @@ -1,159 +0,0 @@ -# -# Example MaxScale.cnf configuration file -# -# -# -# Number of server threads -# Valid options are: -# threads= - -[maxscale] -threads=1 - -# Define a monitor that can be used to determine the state and role of -# the servers. -# -# Valid options are: -# -# module= -# servers=,,... -# user = -# passwd= -# monitor_interval= - -[MySQL Monitor] -type=monitor -module=mysqlmon -servers=server1,server2,server3,server4 -user=maxuser -passwd=maxpwd - -# A series of service definition -# -# Valid options are: -# -# router= -# servers=,,... -# user= -# passwd= -# enable_root_user=<0 or 1, default is 0> -# version_string= -# -# Valid router modules currently are: -# readwritesplit, readconnroute and debugcli - - -[RW Split Router] -type=service -router=readwritesplit -servers=server1,server2,server3,server4 -max_slave_connections=90% -user=maxuser -passwd=maxpwd -#filters=MQ - -[RW Split Hint Router] -type=service -router=readwritesplit -servers=server1,server2,server3,server4 -max_slave_connections=90% -user=maxuser -passwd=maxpwd -filters=Hint - - -[Read Connection Router] -type=service -router=readconnroute -router_options=master -servers=server1 -user=maxuser -passwd=maxpwd - - -[HTTPD Router] -type=service -router=testroute -servers=server1,server2,server3 - -[Debug Interface] -type=service -router=debugcli - - -[Hint] -type=filter -module=fwfilter -rules=@CMAKE_CURRENT_BINARY_DIR@/rules - - -# Listener definitions for the services -# -# Valid options are: -# -# service= -# protocol= -# port= -# address=
-# socket= - -[RW Split Listener] -type=listener -service=RW Split Router -protocol=MySQLClient -port=4006 - -[RW Split Hint Listener] -type=listener -service=RW Split Hint Router -protocol=MySQLClient -port=4009 - -[Read Connection Listener] -type=listener -service=Read Connection Router -protocol=MySQLClient -port=4008 -#socket=/tmp/readconn.sock - -[Debug Listener] -type=listener -service=Debug Interface -protocol=telnetd -port=4442 -#address=127.0.0.1 - -[HTTPD Listener] -type=listener -service=HTTPD Router -protocol=HTTPD -port=6444 - -# Definition of the servers - -[server1] -type=server -address=127.0.0.1 -port=3000 -protocol=MySQLBackend - -[server2] -type=server -address=127.0.0.1 -port=3001 -protocol=MySQLBackend - -[server3] -type=server -address=127.0.0.1 -port=3002 -protocol=MySQLBackend - -[server4] -type=server -address=127.0.0.1 -port=3003 -protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.cnf.in b/server/modules/filter/test/fwtest.cnf.in index ef1623c8b..13a1504b9 100644 --- a/server/modules/filter/test/fwtest.cnf.in +++ b/server/modules/filter/test/fwtest.cnf.in @@ -1,159 +1,4 @@ -# -# Example MaxScale.cnf configuration file -# -# -# -# Number of server threads -# Valid options are: -# threads= - -[maxscale] -threads=1 - -# Define a monitor that can be used to determine the state and role of -# the servers. -# -# Valid options are: -# -# module= -# servers=,,... -# user = -# passwd= -# monitor_interval= - -[MySQL Monitor] -type=monitor -module=mysqlmon -servers=server1,server2,server3,server4 -user=maxuser -passwd=maxpwd - -# A series of service definition -# -# Valid options are: -# -# router= -# servers=,,... -# user= -# passwd= -# enable_root_user=<0 or 1, default is 0> -# version_string= -# -# Valid router modules currently are: -# readwritesplit, readconnroute and debugcli - - -[RW Split Router] -type=service -router=readwritesplit -servers=server1,server2,server3,server4 -max_slave_connections=90% -user=maxuser -passwd=maxpwd -#filters=MQ - -[RW Split Hint Router] -type=service -router=readwritesplit -servers=server1,server2,server3,server4 -max_slave_connections=90% -user=maxuser -passwd=maxpwd -filters=Hint - - -[Read Connection Router] -type=service -router=readconnroute -router_options=master -servers=server1 -user=maxuser -passwd=maxpwd - - -[HTTPD Router] -type=service -router=testroute -servers=server1,server2,server3 - -[Debug Interface] -type=service -router=debugcli - - -[Hint] +[Firewall] type=filter module=fwfilter rules=@CMAKE_CURRENT_SOURCE_DIR@/rules - - -# Listener definitions for the services -# -# Valid options are: -# -# service= -# protocol= -# port= -# address=
-# socket= - -[RW Split Listener] -type=listener -service=RW Split Router -protocol=MySQLClient -port=4006 - -[RW Split Hint Listener] -type=listener -service=RW Split Hint Router -protocol=MySQLClient -port=4009 - -[Read Connection Listener] -type=listener -service=Read Connection Router -protocol=MySQLClient -port=4008 -#socket=/tmp/readconn.sock - -[Debug Listener] -type=listener -service=Debug Interface -protocol=telnetd -port=4442 -#address=127.0.0.1 - -[HTTPD Listener] -type=listener -service=HTTPD Router -protocol=HTTPD -port=6444 - -# Definition of the servers - -[server1] -type=server -address=127.0.0.1 -port=3000 -protocol=MySQLBackend - -[server2] -type=server -address=127.0.0.1 -port=3001 -protocol=MySQLBackend - -[server3] -type=server -address=127.0.0.1 -port=3002 -protocol=MySQLBackend - -[server4] -type=server -address=127.0.0.1 -port=3003 -protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.output b/server/modules/filter/test/fwtest.output new file mode 100755 index 000000000..a6e8ee1de --- /dev/null +++ b/server/modules/filter/test/fwtest.output @@ -0,0 +1,4 @@ +select 1; +select 1; +select 1; +select 1; diff --git a/server/modules/filter/test/harness.h b/server/modules/filter/test/harness.h index ac3982d19..85713d07a 100644 --- a/server/modules/filter/test/harness.h +++ b/server/modules/filter/test/harness.h @@ -69,7 +69,9 @@ #include #include #include +#include #include + /** * A single name-value pair and a link to the next item in the * configuration. @@ -118,6 +120,7 @@ typedef struct int running; int verbose; /**Whether to print to stdout*/ int infile; /**A file where the queries are loaded from*/ + int expected; int error; char* mod_dir; /**Module directory absolute path*/ char* infile_name; @@ -174,7 +177,7 @@ typedef packet_t PACKET; /** * Initialize the static instance. */ -int harness_init(int argc,char** argv); +int harness_init(int argc,char** argv,HARNESS_INSTANCE** inst); /** * Frees all the query buffers @@ -361,4 +364,14 @@ GWBUF* gen_packet(PACKET pkt); */ int process_opts(int argc, char** argv); +/** + * Compares the contents of two files. + * This function resets the offsets of the file descriptors and leaves them in an + * undefined state. + * @param a The first file + * @param b The second file + * @return 0 if the files do not differ and 1 if they do or an error occurred. + */ +int compare_files(int a, int b); + #endif diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index 0a853bae0..6efa31992 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -1,12 +1,15 @@ #include + int dcbfun(struct dcb* dcb, GWBUF * buffer) { printf("Data was written to client DCB.\n"); return 1; } -int harness_init(int argc, char** argv){ +int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ + + int i = 0; if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ skygw_logmanager_init(0,NULL); @@ -20,9 +23,11 @@ int harness_init(int argc, char** argv){ return 1; } + *inst = &instance; instance.running = 1; instance.infile = -1; instance.outfile = -1; + instance.expected = -1; instance.buff_ind = -1; instance.last_ind = -1; instance.sess_ind = -1; @@ -33,7 +38,10 @@ int harness_init(int argc, char** argv){ sprintf(mysqlsess->user,"dummyuser"); sprintf(mysqlsess->db,"dummydb"); dcb->func.write = dcbfun; + dcb->remote = strdup("0.0.0.0"); + dcb->user = strdup("user"); instance.session->client = (void*)dcb; + instance.session->data = (void*)mysqlsess; process_opts(argc,argv); @@ -98,15 +106,17 @@ void free_buffers() } int open_file(char* str, unsigned int write) { - int mode; + int mode,fd; if(write){ mode = O_RDWR|O_CREAT; }else{ mode = O_RDONLY; } - - return open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH); + if((fd = open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH)) < 0){ + printf("Error %d: %s\n",errno,strerror(errno)); + } + return fd; } @@ -622,6 +632,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) { FILTER_PARAMETER** fparams = NULL; int i, paramc = -1; + int sess_err = 0; + int x; if(cnf == NULL){ fparams = read_params(¶mc); @@ -686,13 +698,16 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - int sess_err = 0; if(cnf && fc && fc->instance){ fc->filter = (FILTER*)fc->instance->createInstance(NULL,fparams); - + if(fc->filter == NULL){ + printf("Error loading filter:%s: createInstance returned NULL.\n",fc->name); + sess_err = 1; + goto error; + } for(i = 0;isession[i] = fc->instance->newSession(fc->filter, instance.session)) && @@ -760,8 +775,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - - int x; + error: + if(fparams){ for(x = 0;xinstance->routeQuery(instance.head->filter, + if(instance.head->instance->routeQuery(instance.head->filter, instance.head->session[index], - instance.buffer[instance.buff_ind]); + instance.buffer[instance.buff_ind]) == 0){ + if(instance.outfile > 0){ + const char* msg = "Query returned 0.\n"; + write(instance.outfile,msg,strlen(msg)); + } + } if(instance.tail->instance->clientReply){ instance.tail->instance->clientReply(instance.tail->filter, instance.tail->session[index], @@ -946,7 +966,7 @@ GWBUF* gen_packet(PACKET pkt) int process_opts(int argc, char** argv) { int fd, buffsize = 1024; - int rd,rdsz, rval; + int rd,rdsz, rval = 0; size_t fsize; char *buff = calloc(buffsize,sizeof(char)), *tok = NULL; @@ -999,10 +1019,18 @@ int process_opts(int argc, char** argv) close(fd); return 1; } + char* conf_name = NULL; - while((rd = getopt(argc,argv,"m:c:i:o:s:t:d:qh")) > 0){ + rval = 0; + + while((rd = getopt(argc,argv,"e:m:c:i:o:s:t:d:qh")) > 0){ switch(rd){ + case 'e': + instance.expected = open_file(optarg,0); + printf("Expected output is read from: %s\n",optarg); + break; + case 'o': instance.outfile = open_file(optarg,1); printf("Output is written to: %s\n",optarg); @@ -1067,6 +1095,7 @@ int process_opts(int argc, char** argv) } } printf("\n"); + if(conf_name && load_config(conf_name)){ load_query(); }else{ @@ -1075,5 +1104,30 @@ int process_opts(int argc, char** argv) free(conf_name); close(fd); + return rval; +} + +int compare_files(int a,int b) +{ + char in[4098]; + char exp[4098]; + int line = 1; + + if(a < 1 || b < 1){ + return 1; + } + + if(lseek(a,0,SEEK_SET) < 0 || + lseek(b,0,SEEK_SET) < 0){ + return 1; + } + + while(fdgets(a,in,4098) && fdgets(b,exp,4098)){ + if(strcmp(in,exp)){ + printf("The files differ at line %d:\n%s\n-------------------------------------\n%s\n",line,in,exp); + return 1; + } + line++; + } return 0; } diff --git a/server/modules/filter/test/harness_util.c b/server/modules/filter/test/harness_util.c index 705bc3c3b..04b7e1b9b 100644 --- a/server/modules/filter/test/harness_util.c +++ b/server/modules/filter/test/harness_util.c @@ -1,7 +1,37 @@ +#include +#include #include + int main(int argc,char** argv) { + + static char* server_options[] = { + "MariaDB Corporation MaxScale", + "--datadir=./", + "--language=./", + "--skip-innodb", + "--default-storage-engine=myisam", + NULL + }; + + const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1; + + static char* server_groups[] = { + "embedded", + "server", + "server", + NULL + }; + + HARNESS_INSTANCE* inst; + + if(mysql_library_init(num_elements, server_options, server_groups)){ + printf("Embedded server init failed.\n"); + return 1; + } + + if(harness_init(argc,argv,&inst) || inst->error){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); diff --git a/server/modules/filter/test/rules b/server/modules/filter/test/rules index 8e6c4ab3c..a75ecb666 100644 --- a/server/modules/filter/test/rules +++ b/server/modules/filter/test/rules @@ -1,3 +1,3 @@ rule union_regex deny regex '.*union.*' -rule dont_delete_everything deny no_where_clause on_operations delete|update at_times 12:00:00-18:00:00 +rule dont_delete_everything deny no_where_clause on_operations delete|update users %@% match any rules union_regex dont_delete_everything \ No newline at end of file From a2f4b47e54c690929a71f23e8cf30a7f83f1e7cc Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 18 Nov 2014 06:37:00 +0200 Subject: [PATCH 24/31] Fixed errors in filter harness file comparing. --- server/modules/filter/fwfilter.c | 12 ++++-- server/modules/filter/test/CMakeLists.txt | 12 +++++- server/modules/filter/test/fwtest.expected | 4 ++ server/modules/filter/test/fwtest2.expected | 3 ++ server/modules/filter/test/fwtest2.input | 8 ++++ server/modules/filter/test/harness_common.c | 46 ++++++++++++++------- server/modules/filter/test/harness_util.c | 3 +- server/modules/filter/test/rules | 3 +- 8 files changed, 68 insertions(+), 23 deletions(-) create mode 100755 server/modules/filter/test/fwtest.expected create mode 100644 server/modules/filter/test/fwtest2.expected create mode 100644 server/modules/filter/test/fwtest2.input diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 67f12209e..a66667ab6 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -922,10 +922,16 @@ createInstance(char **options, FILTER_PARAMETER **params) { if(fgets(buffer,2048,file) == NULL){ - free(my_instance); - return NULL; + if(ferror(file)){ + free(my_instance); + return NULL; + } + + if(feof(file)){ + break; + } } - + if((nl = strchr(buffer,'\n')) != NULL && ((char*)nl - (char*)buffer) < 2048){ *nl = '\0'; } diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 9c87ee926..d17d3348f 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -11,6 +11,14 @@ add_executable(harness harness_util.c harness_common.c ${CORE}) target_link_libraries(harness_ui fullcore log_manager utils) target_link_libraries(harness fullcore) execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR}) -add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf) -add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regextest.expected") \ No newline at end of file + + +add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected ") + +add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regextest.expected ") + +add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected ") + +add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.expected ") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.expected b/server/modules/filter/test/fwtest.expected new file mode 100755 index 000000000..a6e8ee1de --- /dev/null +++ b/server/modules/filter/test/fwtest.expected @@ -0,0 +1,4 @@ +select 1; +select 1; +select 1; +select 1; diff --git a/server/modules/filter/test/fwtest2.expected b/server/modules/filter/test/fwtest2.expected new file mode 100644 index 000000000..adde1069e --- /dev/null +++ b/server/modules/filter/test/fwtest2.expected @@ -0,0 +1,3 @@ +select 1; +select 1; +select 1; diff --git a/server/modules/filter/test/fwtest2.input b/server/modules/filter/test/fwtest2.input new file mode 100644 index 000000000..6308361c4 --- /dev/null +++ b/server/modules/filter/test/fwtest2.input @@ -0,0 +1,8 @@ +select 1; +select 1 union select 2; +select 1; +delete from test.table; +select 1; +select 1; +select 1; +select 1; \ No newline at end of file diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index 03338a0d5..fa6bfc6bd 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -318,16 +318,16 @@ int clientReply(void* ins, void* session, GWBUF* queue) int fdgets(int fd, char* buff, int size) { int i = 0; - - while(i < size - 1 && read(fd,&buff[i],1)) - { - if(buff[i] == '\n' || buff[i] == '\0') - { - break; - } - i++; - } - + if(fd > 0){ + while(i < size - 1 && read(fd,&buff[i],1)) + { + if(buff[i] == '\n' || buff[i] == '\0') + { + break; + } + i++; + } + } buff[i] = '\0'; return i; } @@ -1053,7 +1053,11 @@ int process_opts(int argc, char** argv) case 'e': instance.expected = open_file(optarg,0); - printf("Expected output is read from: %s\n",optarg); + if(instance.expected > 0){ + printf("Expected output is read from: %s\n",optarg); + }else{ + printf("Error: Failed to open file: %s\n",optarg); + } break; case 'o': @@ -1079,12 +1083,12 @@ int process_opts(int argc, char** argv) case 's': instance.session_count = atoi(optarg); - printf("Sessions: %i ",instance.session_count); + printf("Sessions: %i\n",instance.session_count); break; case 't': instance.thrcount = atoi(optarg); - printf("Threads: %i ",instance.thrcount); + printf("Threads: %i\n",instance.thrcount); break; case 'd': @@ -1136,19 +1140,29 @@ int compare_files(int a,int b) { char in[4098]; char exp[4098]; - int line = 1; + int line = 1,ard, brd,running = 1; if(a < 1 || b < 1){ + printf("Invalid file descriptors: %d %d\n",a,b); return 1; } if(lseek(a,0,SEEK_SET) < 0 || lseek(b,0,SEEK_SET) < 0){ + printf("Failed lseek() call on file descriptors: %d %d\n",a,b); return 1; } - while(fdgets(a,in,4098) && fdgets(b,exp,4098)){ - if(strcmp(in,exp)){ + while(running){ + + ard = fdgets(a,in,4098); + brd = fdgets(b,exp,4098); + + if(ard == 0 && brd == 0){ + break; + } + + if(ard == 0 || brd == 0 || strcmp(in,exp)){ printf("The files differ at line %d:\n%s\n-------------------------------------\n%s\n",line,in,exp); return 1; } diff --git a/server/modules/filter/test/harness_util.c b/server/modules/filter/test/harness_util.c index 04b7e1b9b..5b3baf31e 100644 --- a/server/modules/filter/test/harness_util.c +++ b/server/modules/filter/test/harness_util.c @@ -41,7 +41,8 @@ int main(int argc,char** argv) } route_buffers(); - if(inst->expected){ + + if(inst->expected > 0){ return compare_files(inst->outfile,inst->expected); } return 0; diff --git a/server/modules/filter/test/rules b/server/modules/filter/test/rules index a75ecb666..5a81be3f0 100644 --- a/server/modules/filter/test/rules +++ b/server/modules/filter/test/rules @@ -1,3 +1,4 @@ rule union_regex deny regex '.*union.*' rule dont_delete_everything deny no_where_clause on_operations delete|update -users %@% match any rules union_regex dont_delete_everything \ No newline at end of file +rule limit_speed deny limit_queries 3 100.0 10.0 +users %@% match any rules union_regex dont_delete_everything limit_speed From e80d709ad8d2fa1c4f46dedf4bb16b26d8c117b0 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 18 Nov 2014 17:05:44 +0200 Subject: [PATCH 25/31] Cleaning up code --- server/modules/filter/fwfilter.c | 726 +++++++++++++++++++++---------- 1 file changed, 490 insertions(+), 236 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index a66667ab6..e80f1da49 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -1146,6 +1146,264 @@ bool rule_is_active(RULE* rule) return true; } +bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, RULELIST *rulelist, char* query) +{ + char *ptr,*where; + int qlen; + bool is_sql, is_real, accept; + skygw_query_op_t optype; + STRLINK* strln = NULL; + QUERYSPEED* queryspeed = NULL; + time_t time_now; + struct tm* tm_now; + + time(&time_now); + tm_now = localtime(&time_now); + + accept = false; + is_sql = modutil_is_SQL(queue); + + if(is_sql){ + if(!query_is_parsed(queue)){ + parse_query(queue); + } + optype = query_classifier_get_operation(queue); + modutil_extract_SQL(queue, &ptr, &qlen); + is_real = skygw_is_real_query(queue); + } + + if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ + + switch(rulelist->rule->type){ + + case RT_UNDEFINED: + skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); + break; + + case RT_REGEX: + + if(query && regexec(rulelist->rule->data,query,0,NULL,0) == 0){ + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + //msg = strdup("Permission denied, query matched regular expression."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); + goto queryresolved; + }else{ + break; + } + } + + break; + + case RT_PERMISSION: + if(!rulelist->rule->allow){ + accept = true; + //msg = strdup("Permission denied at this time."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); + goto queryresolved; + }else{ + break; + } + break; + + case RT_COLUMN: + + if(is_sql && is_real){ + + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + + while(strln){ + if(strstr(where,strln->value)){ + + accept = rulelist->rule->allow; + + if(!rulelist->rule->allow){ + //sprintf(emsg,"Permission denied to column '%s'.",strln->value); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); + //msg = strdup(emsg); + goto queryresolved; + }else{ + break; + } + } + strln = strln->next; + } + } + } + + break; + + case RT_WILDCARD: + + + if(is_sql && is_real){ + char * strptr; + where = skygw_get_affected_fields(queue); + + if(where != NULL){ + strptr = where; + }else{ + strptr = query; + } + if(strchr(strptr,'*')){ + + accept = true; + //msg = strdup("Usage of wildcard denied."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); + goto queryresolved; + } + } + + break; + + case RT_THROTTLE: + queryspeed = (QUERYSPEED*)rulelist->rule->data; + if(queryspeed->count > queryspeed->limit) + { + queryspeed->triggered = time_now; + queryspeed->count = 0; + accept = true; + + + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user for %f seconds.", + rulelist->rule->name, + queryspeed->limit, + queryspeed->period, + queryspeed->cooldown); + } + else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) + { + + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + + //sprintf(emsg,"Queries denied for %f seconds",blocked_for); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user denied for %f seconds",rulelist->rule->name,blocked_for); + //msg = strdup(emsg); + + accept = true; + } + else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) + { + queryspeed->count++; + } + else + { + queryspeed->first_query = time_now; + } + + break; + + case RT_CLAUSE: + if(is_sql && is_real && !skygw_query_has_clause(queue)) + { + accept = true; + //msg = strdup("Required WHERE/HAVING clause is missing."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", + rulelist->rule->name); + } + break; + + default: + break; + + } + } + + queryresolved: + + return accept; +} + +bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, USER* user) +{ + bool is_sql, rval = false; + int qlen; + char *fullquery = NULL,*ptr; + + RULELIST* rulelist; + is_sql = modutil_is_SQL(queue); + + if(is_sql){ + if(!query_is_parsed(queue)){ + parse_query(queue); + } + modutil_extract_SQL(queue, &ptr, &qlen); + fullquery = malloc((qlen + 1) * sizeof(char)); + memcpy(fullquery,ptr,qlen); + memset(fullquery + qlen,0,1); + } + + rulelist = user->rules_or; + + while(rulelist){ + + if(!rule_is_active(rulelist->rule)){ + rulelist = rulelist->next; + continue; + } + if((rval = rule_matches(my_instance,my_session,queue,rulelist,fullquery))){ + goto retblock; + } + rulelist = rulelist->next; + } + + retblock: + + free(fullquery); + + return rval; +} + +bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, USER* user) +{ + bool is_sql, rval; + int qlen; + char *fullquery = NULL,*ptr; + + RULELIST* rulelist; + is_sql = modutil_is_SQL(queue); + + if(is_sql){ + if(!query_is_parsed(queue)){ + parse_query(queue); + } + modutil_extract_SQL(queue, &ptr, &qlen); + fullquery = malloc((qlen + 1) * sizeof(char)); + memcpy(fullquery,ptr,qlen); + memset(fullquery + qlen,0,1); + + + } + + rulelist = user->rules_or; + + while(rulelist){ + + if(!rule_is_active(rulelist->rule)){ + rulelist = rulelist->next; + continue; + } + + if(!rule_matches(my_instance,my_session,queue,rulelist,fullquery)){ + rval = false; + goto retblock; + } + rulelist = rulelist->next; + } + + retblock: + + free(fullquery); + + return rval; +} + /** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the @@ -1161,27 +1419,15 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) { FW_SESSION *my_session = (FW_SESSION *)session; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; - time_t time_now; - struct tm* tm_now; - bool accept = my_instance->def_op, - is_sql = false, - is_real = false,rule_match; - char *where, *msg = NULL, *fullquery = NULL,*ptr,*ipaddr; + bool accept = my_instance->def_op; + char *msg = NULL, *fullquery = NULL,*ipaddr; char uname_addr[128]; - char emsg[1024]; DCB* dcb = my_session->session->client; - RULELIST *rulelist = NULL; USER* user = NULL; - STRLINK* strln = NULL; - QUERYSPEED* queryspeed; - int qlen; - skygw_query_op_t optype; ipaddr = strdup(dcb->remote); sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); - time(&time_now); - tm_now = localtime(&time_now); if((user = (USER*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL){ while(user == NULL && next_ip_class(ipaddr)){ @@ -1209,298 +1455,306 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } - is_sql = modutil_is_SQL(queue); + /* is_sql = modutil_is_SQL(queue); */ - if(is_sql){ - if(!query_is_parsed(queue)){ - parse_query(queue); - } - optype = query_classifier_get_operation(queue); - modutil_extract_SQL(queue, &ptr, &qlen); - fullquery = malloc((qlen + 1) * sizeof(char)); - memcpy(fullquery,ptr,qlen); - memset(fullquery + qlen,0,1); - is_real = skygw_is_real_query(queue); - } + /* if(is_sql){ */ + /* if(!query_is_parsed(queue)){ */ + /* parse_query(queue); */ + /* } */ + /* optype = query_classifier_get_operation(queue); */ + /* modutil_extract_SQL(queue, &ptr, &qlen); */ + /* fullquery = malloc((qlen + 1) * sizeof(char)); */ + /* memcpy(fullquery,ptr,qlen); */ + /* memset(fullquery + qlen,0,1); */ + /* is_real = skygw_is_real_query(queue); */ + /* } */ - rulelist = user->rules_or; + /* rulelist = user->rules_or; */ - while(rulelist){ + /* while(rulelist){ */ - if(!rule_is_active(rulelist->rule)){ - rulelist = rulelist->next; - continue; - } - if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ + /* if(!rule_is_active(rulelist->rule)){ */ + /* rulelist = rulelist->next; */ + /* continue; */ + /* } */ + /* if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ */ - switch(rulelist->rule->type){ + /* switch(rulelist->rule->type){ */ - case RT_UNDEFINED: - skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); - break; + /* case RT_UNDEFINED: */ + /* skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); */ + /* break; */ - case RT_REGEX: + /* case RT_REGEX: */ - if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ + /* if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ */ - accept = rulelist->rule->allow; + /* accept = rulelist->rule->allow; */ - if(!rulelist->rule->allow){ - msg = strdup("Permission denied, query matched regular expression."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); - goto queryresolved; - }else{ - break; - } - } + /* if(!rulelist->rule->allow){ */ + /* msg = strdup("Permission denied, query matched regular expression."); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); */ + /* goto queryresolved; */ + /* }else{ */ + /* break; */ + /* } */ + /* } */ - break; + /* break; */ - case RT_PERMISSION: - if(!rulelist->rule->allow){ - accept = false; - msg = strdup("Permission denied at this time."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); - goto queryresolved; - }else{ - break; - } - break; + /* case RT_PERMISSION: */ + /* if(!rulelist->rule->allow){ */ + /* accept = false; */ + /* msg = strdup("Permission denied at this time."); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); */ + /* goto queryresolved; */ + /* }else{ */ + /* break; */ + /* } */ + /* break; */ - case RT_COLUMN: + /* case RT_COLUMN: */ - if(is_sql && is_real){ + /* if(is_sql && is_real){ */ - strln = (STRLINK*)rulelist->rule->data; - where = skygw_get_affected_fields(queue); + /* strln = (STRLINK*)rulelist->rule->data; */ + /* where = skygw_get_affected_fields(queue); */ - if(where != NULL){ + /* if(where != NULL){ */ - while(strln){ - if(strstr(where,strln->value)){ + /* while(strln){ */ + /* if(strstr(where,strln->value)){ */ - accept = rulelist->rule->allow; + /* accept = rulelist->rule->allow; */ - if(!rulelist->rule->allow){ - sprintf(emsg,"Permission denied to column '%s'.",strln->value); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); - msg = strdup(emsg); - goto queryresolved; - }else{ - break; - } - } - strln = strln->next; - } - } - } + /* if(!rulelist->rule->allow){ */ + /* sprintf(emsg,"Permission denied to column '%s'.",strln->value); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); */ + /* msg = strdup(emsg); */ + /* goto queryresolved; */ + /* }else{ */ + /* break; */ + /* } */ + /* } */ + /* strln = strln->next; */ + /* } */ + /* } */ + /* } */ - break; + /* break; */ - case RT_WILDCARD: + /* case RT_WILDCARD: */ - if(is_sql && is_real){ + /* if(is_sql && is_real){ */ - where = skygw_get_affected_fields(queue); + /* where = skygw_get_affected_fields(queue); */ - if(where != NULL){ - if(strchr(where,'*')){ + /* if(where != NULL){ */ + /* if(strchr(where,'*')){ */ - accept = rulelist->rule->allow; + /* accept = rulelist->rule->allow; */ - if(!rulelist->rule->allow){ - msg = strdup("Usage of wildcard denied."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); - goto queryresolved; - } - } - } - } + /* if(!rulelist->rule->allow){ */ + /* msg = strdup("Usage of wildcard denied."); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); */ + /* goto queryresolved; */ + /* } */ + /* } */ + /* } */ + /* } */ - break; + /* break; */ - case RT_THROTTLE: - queryspeed = (QUERYSPEED*)rulelist->rule->data; - if(queryspeed->count > queryspeed->limit) - { - queryspeed->triggered = time_now; - queryspeed->count = 0; - accept = false; + /* case RT_THROTTLE: */ + /* queryspeed = (QUERYSPEED*)rulelist->rule->data; */ + /* if(queryspeed->count > queryspeed->limit) */ + /* { */ + /* queryspeed->triggered = time_now; */ + /* queryspeed->count = 0; */ + /* accept = false; */ - skygw_log_write(LOGFILE_TRACE, - "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", - rulelist->rule->name, - queryspeed->limit, - queryspeed->period, - uname_addr, - queryspeed->cooldown); - } - else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) - { + /* skygw_log_write(LOGFILE_TRACE, */ + /* "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", */ + /* rulelist->rule->name, */ + /* queryspeed->limit, */ + /* queryspeed->period, */ + /* uname_addr, */ + /* queryspeed->cooldown); */ + /* } */ + /* else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) */ + /* { */ - double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + /* double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); */ - sprintf(emsg,"Queries denied for %f seconds",blocked_for); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); - msg = strdup(emsg); + /* sprintf(emsg,"Queries denied for %f seconds",blocked_for); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); */ + /* msg = strdup(emsg); */ - accept = false; - } - else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) - { - queryspeed->count++; - } - else - { - queryspeed->first_query = time_now; - } + /* accept = false; */ + /* } */ + /* else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) */ + /* { */ + /* queryspeed->count++; */ + /* } */ + /* else */ + /* { */ + /* queryspeed->first_query = time_now; */ + /* } */ - break; + /* break; */ - case RT_CLAUSE: - if(is_sql && is_real && !skygw_query_has_clause(queue)) - { - accept = false; - msg = strdup("Required WHERE/HAVING clause is missing."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", - rulelist->rule->name); - } - break; + /* case RT_CLAUSE: */ + /* if(is_sql && is_real && !skygw_query_has_clause(queue)) */ + /* { */ + /* accept = false; */ + /* msg = strdup("Required WHERE/HAVING clause is missing."); */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", */ + /* rulelist->rule->name); */ + /* } */ + /* break; */ - default: - break; + /* default: */ + /* break; */ - } - } - rulelist = rulelist->next; + /* } */ + /* } */ + /* rulelist = rulelist->next; */ + /* } */ + + if(check_match_any(my_instance,my_session,queue,user)){ + accept = false; + goto queryresolved; } - + if(check_match_all(my_instance,my_session,queue,user)){ + accept = false; + goto queryresolved; + } - rulelist = user->rules_and; - rule_match = (rulelist != NULL); - while(rulelist && rule_match){ + /* rulelist = user->rules_and; */ + /* rule_match = (rulelist != NULL); */ + /* while(rulelist && rule_match){ */ - if(!rule_is_active(rulelist->rule)){ - rule_match = false; - break; - } + /* if(!rule_is_active(rulelist->rule)){ */ + /* rule_match = false; */ + /* break; */ + /* } */ - switch(rulelist->rule->type){ + /* switch(rulelist->rule->type){ */ - case RT_UNDEFINED: - break; + /* case RT_UNDEFINED: */ + /* break; */ - case RT_REGEX: + /* case RT_REGEX: */ - if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) != 0){ - rule_match = false; - } + /* if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) != 0){ */ + /* rule_match = false; */ + /* } */ - break; + /* break; */ - case RT_PERMISSION: - if(!rulelist->rule->allow){ - rule_match = false; - } - break; + /* case RT_PERMISSION: */ + /* if(!rulelist->rule->allow){ */ + /* rule_match = false; */ + /* } */ + /* break; */ - case RT_COLUMN: + /* case RT_COLUMN: */ - if(is_sql && is_real){ + /* if(is_sql && is_real){ */ - strln = (STRLINK*)rulelist->rule->data; - where = skygw_get_affected_fields(queue); - rule_match = false; + /* strln = (STRLINK*)rulelist->rule->data; */ + /* where = skygw_get_affected_fields(queue); */ + /* rule_match = false; */ - if(where != NULL){ + /* if(where != NULL){ */ - while(strln){ + /* while(strln){ */ - /**At least one value matched*/ + /* /\**At least one value matched*\/ */ - if(strstr(where,strln->value)){ - rule_match = true; - break; - } + /* if(strstr(where,strln->value)){ */ + /* rule_match = true; */ + /* break; */ + /* } */ - strln = strln->next; - } - } - } + /* strln = strln->next; */ + /* } */ + /* } */ + /* } */ - break; + /* break; */ - case RT_WILDCARD: + /* case RT_WILDCARD: */ - if(is_sql && is_real){ + /* if(is_sql && is_real){ */ - where = skygw_get_affected_fields(queue); + /* where = skygw_get_affected_fields(queue); */ - if(where != NULL){ - if(strchr(where,'*') == NULL){ - rule_match = false; - } - } - } + /* if(where != NULL){ */ + /* if(strchr(where,'*') == NULL){ */ + /* rule_match = false; */ + /* } */ + /* } */ + /* } */ - break; - case RT_THROTTLE: - queryspeed = (QUERYSPEED*)rulelist->rule->data; - if(queryspeed->count > queryspeed->limit) - { + /* break; */ + /* case RT_THROTTLE: */ + /* queryspeed = (QUERYSPEED*)rulelist->rule->data; */ + /* if(queryspeed->count > queryspeed->limit) */ + /* { */ - skygw_log_write(LOGFILE_TRACE, - "fwfilter: rule '%s': query limit triggered (%d queries during the last %f seconds), denying queries from user %s for %f seconds.", - rulelist->rule->name, - queryspeed->count, - queryspeed->period, - uname_addr, - queryspeed->cooldown); - queryspeed->triggered = time_now; - queryspeed->count = 0; - accept = false; - } - else if(difftime(queryspeed->triggered,time_now) < queryspeed->cooldown) - { + /* skygw_log_write(LOGFILE_TRACE, */ + /* "fwfilter: rule '%s': query limit triggered (%d queries during the last %f seconds), denying queries from user %s for %f seconds.", */ + /* rulelist->rule->name, */ + /* queryspeed->count, */ + /* queryspeed->period, */ + /* uname_addr, */ + /* queryspeed->cooldown); */ + /* queryspeed->triggered = time_now; */ + /* queryspeed->count = 0; */ + /* accept = false; */ + /* } */ + /* else if(difftime(queryspeed->triggered,time_now) < queryspeed->cooldown) */ + /* { */ - double blocked_for = queryspeed->cooldown - difftime(queryspeed->triggered,time_now); + /* double blocked_for = queryspeed->cooldown - difftime(queryspeed->triggered,time_now); */ - sprintf(emsg,"Access denied for user %s for %f seconds.",uname_addr,blocked_for); - msg = strdup(emsg); - skygw_log_write(LOGFILE_TRACE, - "fwfilter: rule '%s': user %s blocked for %f seconds.", - rulelist->rule->name, - uname_addr, - blocked_for); - accept = false; - } - else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) - { - queryspeed->count++; - } - else - { - queryspeed->first_query = time_now; - } + /* sprintf(emsg,"Access denied for user %s for %f seconds.",uname_addr,blocked_for); */ + /* msg = strdup(emsg); */ + /* skygw_log_write(LOGFILE_TRACE, */ + /* "fwfilter: rule '%s': user %s blocked for %f seconds.", */ + /* rulelist->rule->name, */ + /* uname_addr, */ + /* blocked_for); */ + /* accept = false; */ + /* } */ + /* else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) */ + /* { */ + /* queryspeed->count++; */ + /* } */ + /* else */ + /* { */ + /* queryspeed->first_query = time_now; */ + /* } */ - break; + /* break; */ - default: - break; + /* default: */ + /* break; */ - } + /* } */ - rulelist = rulelist->next; - } + /* rulelist = rulelist->next; */ + /* } */ - if(rule_match){ - /**AND rules match*/ - skygw_log_write(LOGFILE_TRACE, "fwfilter: all rules match, query is %s.",accept ? "allowed":"denied"); - accept = !my_instance->def_op; - } + /* if(rule_match){ */ + /* /\**AND rules match*\/ */ + /* skygw_log_write(LOGFILE_TRACE, "fwfilter: all rules match, query is %s.",accept ? "allowed":"denied"); */ + /* accept = !my_instance->def_op; */ + /* } */ queryresolved: From 7aa7474b77c6601ccc492013bf3bcdc19bb0fccd Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 21 Nov 2014 12:18:58 +0200 Subject: [PATCH 26/31] Updated the fwfilter tests and documentation --- server/modules/filter/fwfilter.c | 381 ++++---------------- server/modules/filter/test/CMakeLists.txt | 4 +- server/modules/filter/test/fwtest.expected | 12 +- server/modules/filter/test/fwtest.input | 15 +- server/modules/filter/test/fwtest.output | 12 +- server/modules/filter/test/fwtest2.expected | 11 +- server/modules/filter/test/fwtest2.input | 18 +- server/modules/filter/test/rules | 4 +- 8 files changed, 110 insertions(+), 347 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index e80f1da49..55314938b 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -197,6 +197,7 @@ typedef struct { DOWNSTREAM down; UPSTREAM up; SESSION* session; + char* errmsg; } FW_SESSION; static int hashkeyfun(void* key); @@ -371,7 +372,7 @@ bool parse_querytypes(char* str,RULE* rule) while(ptr - buffer < 512) { - if(*ptr == '|' || (done = *ptr == '\0')){ + if(*ptr == '|' || *ptr == ' ' || (done = *ptr == '\0')){ *dest = '\0'; if(strcmp(buffer,"select") == 0){ rule->on_queries |= QUERY_OP_SELECT; @@ -1003,6 +1004,10 @@ static void freeSession(FILTER *instance, void *session) { FW_SESSION *my_session = (FW_SESSION *)session; + if(my_session->errmsg){ + free(my_session->errmsg); + + } free(my_session); } @@ -1148,19 +1153,25 @@ bool rule_is_active(RULE* rule) bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, RULELIST *rulelist, char* query) { - char *ptr,*where; + char *ptr,*where,*msg = NULL; + char emsg[512]; int qlen; - bool is_sql, is_real, accept; + bool is_sql, is_real, matches; skygw_query_op_t optype; STRLINK* strln = NULL; QUERYSPEED* queryspeed = NULL; time_t time_now; struct tm* tm_now; + if(my_session->errmsg){ + free(my_session->errmsg); + my_session->errmsg = NULL; + } + time(&time_now); tm_now = localtime(&time_now); - accept = false; + matches = false; is_sql = modutil_is_SQL(queue); if(is_sql){ @@ -1184,10 +1195,10 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue if(query && regexec(rulelist->rule->data,query,0,NULL,0) == 0){ - accept = rulelist->rule->allow; + matches = true; if(!rulelist->rule->allow){ - //msg = strdup("Permission denied, query matched regular expression."); + msg = strdup("Permission denied, query matched regular expression."); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); goto queryresolved; }else{ @@ -1199,8 +1210,8 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue case RT_PERMISSION: if(!rulelist->rule->allow){ - accept = true; - //msg = strdup("Permission denied at this time."); + matches = true; + msg = strdup("Permission denied at this time."); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); goto queryresolved; }else{ @@ -1220,12 +1231,12 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue while(strln){ if(strstr(where,strln->value)){ - accept = rulelist->rule->allow; + matches = true; if(!rulelist->rule->allow){ - //sprintf(emsg,"Permission denied to column '%s'.",strln->value); + sprintf(emsg,"Permission denied to column '%s'.",strln->value); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); - //msg = strdup(emsg); + msg = strdup(emsg); goto queryresolved; }else{ break; @@ -1252,8 +1263,8 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue } if(strchr(strptr,'*')){ - accept = true; - //msg = strdup("Usage of wildcard denied."); + matches = true; + msg = strdup("Usage of wildcard denied."); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); goto queryresolved; } @@ -1267,7 +1278,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue { queryspeed->triggered = time_now; queryspeed->count = 0; - accept = true; + matches = true; skygw_log_write(LOGFILE_TRACE, @@ -1282,11 +1293,11 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); - //sprintf(emsg,"Queries denied for %f seconds",blocked_for); + sprintf(emsg,"Queries denied for %f seconds",blocked_for); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user denied for %f seconds",rulelist->rule->name,blocked_for); - //msg = strdup(emsg); + msg = strdup(emsg); - accept = true; + matches = true; } else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) { @@ -1300,10 +1311,12 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue break; case RT_CLAUSE: - if(is_sql && is_real && !skygw_query_has_clause(queue)) + + if(is_sql && is_real && + !skygw_query_has_clause(queue)) { - accept = true; - //msg = strdup("Required WHERE/HAVING clause is missing."); + matches = true; + msg = strdup("Required WHERE/HAVING clause is missing."); skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", rulelist->rule->name); } @@ -1316,10 +1329,20 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue } queryresolved: - - return accept; + if(msg){ + my_session->errmsg = msg; + } + return matches; } +/** + * Check if the query matches any of the rules in the user's rulelist. + * @param my_instance Fwfilter instance + * @param my_session Fwfilter session + * @param queue The GWBUF containing the query + * @param user The user whose rulelist is checked + * @return True if the query matches at least one of the rules otherwise false + */ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, USER* user) { bool is_sql, rval = false; @@ -1360,6 +1383,14 @@ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu return rval; } +/** + * Check if the query matches all rules in the user's rulelist. + * @param my_instance Fwfilter instance + * @param my_session Fwfilter session + * @param queue The GWBUF containing the query + * @param user The user whose rulelist is checked + * @return True if the query matches all of the rules otherwise false + */ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, USER* user) { bool is_sql, rval; @@ -1424,7 +1455,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) char uname_addr[128]; DCB* dcb = my_session->session->client; USER* user = NULL; - + GWBUF* forward; ipaddr = strdup(dcb->remote); sprintf(uname_addr,"%s@%s",dcb->user,ipaddr); @@ -1453,175 +1484,6 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) goto queryresolved; } - - - /* is_sql = modutil_is_SQL(queue); */ - - /* if(is_sql){ */ - /* if(!query_is_parsed(queue)){ */ - /* parse_query(queue); */ - /* } */ - /* optype = query_classifier_get_operation(queue); */ - /* modutil_extract_SQL(queue, &ptr, &qlen); */ - /* fullquery = malloc((qlen + 1) * sizeof(char)); */ - /* memcpy(fullquery,ptr,qlen); */ - /* memset(fullquery + qlen,0,1); */ - /* is_real = skygw_is_real_query(queue); */ - /* } */ - - /* rulelist = user->rules_or; */ - - /* while(rulelist){ */ - - /* if(!rule_is_active(rulelist->rule)){ */ - /* rulelist = rulelist->next; */ - /* continue; */ - /* } */ - /* if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ */ - - /* switch(rulelist->rule->type){ */ - - /* case RT_UNDEFINED: */ - /* skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); */ - /* break; */ - - /* case RT_REGEX: */ - - /* if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) == 0){ */ - - /* accept = rulelist->rule->allow; */ - - /* if(!rulelist->rule->allow){ */ - /* msg = strdup("Permission denied, query matched regular expression."); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); */ - /* goto queryresolved; */ - /* }else{ */ - /* break; */ - /* } */ - /* } */ - - /* break; */ - - /* case RT_PERMISSION: */ - /* if(!rulelist->rule->allow){ */ - /* accept = false; */ - /* msg = strdup("Permission denied at this time."); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); */ - /* goto queryresolved; */ - /* }else{ */ - /* break; */ - /* } */ - /* break; */ - - /* case RT_COLUMN: */ - - /* if(is_sql && is_real){ */ - - /* strln = (STRLINK*)rulelist->rule->data; */ - /* where = skygw_get_affected_fields(queue); */ - - /* if(where != NULL){ */ - - /* while(strln){ */ - /* if(strstr(where,strln->value)){ */ - - /* accept = rulelist->rule->allow; */ - - /* if(!rulelist->rule->allow){ */ - /* sprintf(emsg,"Permission denied to column '%s'.",strln->value); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); */ - /* msg = strdup(emsg); */ - /* goto queryresolved; */ - /* }else{ */ - /* break; */ - /* } */ - /* } */ - /* strln = strln->next; */ - /* } */ - /* } */ - /* } */ - - /* break; */ - - /* case RT_WILDCARD: */ - - - /* if(is_sql && is_real){ */ - - /* where = skygw_get_affected_fields(queue); */ - - /* if(where != NULL){ */ - /* if(strchr(where,'*')){ */ - - /* accept = rulelist->rule->allow; */ - - /* if(!rulelist->rule->allow){ */ - /* msg = strdup("Usage of wildcard denied."); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); */ - /* goto queryresolved; */ - /* } */ - /* } */ - /* } */ - /* } */ - - /* break; */ - - /* case RT_THROTTLE: */ - /* queryspeed = (QUERYSPEED*)rulelist->rule->data; */ - /* if(queryspeed->count > queryspeed->limit) */ - /* { */ - /* queryspeed->triggered = time_now; */ - /* queryspeed->count = 0; */ - /* accept = false; */ - - - /* skygw_log_write(LOGFILE_TRACE, */ - /* "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user %s for %f seconds.", */ - /* rulelist->rule->name, */ - /* queryspeed->limit, */ - /* queryspeed->period, */ - /* uname_addr, */ - /* queryspeed->cooldown); */ - /* } */ - /* else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) */ - /* { */ - - /* double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); */ - - /* sprintf(emsg,"Queries denied for %f seconds",blocked_for); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user %s denied for %f seconds",rulelist->rule->name,uname_addr,blocked_for); */ - /* msg = strdup(emsg); */ - - /* accept = false; */ - /* } */ - /* else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) */ - /* { */ - /* queryspeed->count++; */ - /* } */ - /* else */ - /* { */ - /* queryspeed->first_query = time_now; */ - /* } */ - - /* break; */ - - /* case RT_CLAUSE: */ - /* if(is_sql && is_real && !skygw_query_has_clause(queue)) */ - /* { */ - /* accept = false; */ - /* msg = strdup("Required WHERE/HAVING clause is missing."); */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", */ - /* rulelist->rule->name); */ - /* } */ - /* break; */ - - /* default: */ - /* break; */ - - /* } */ - /* } */ - /* rulelist = rulelist->next; */ - /* } */ if(check_match_any(my_instance,my_session,queue,user)){ accept = false; @@ -1633,129 +1495,6 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) goto queryresolved; } - /* rulelist = user->rules_and; */ - /* rule_match = (rulelist != NULL); */ - /* while(rulelist && rule_match){ */ - - /* if(!rule_is_active(rulelist->rule)){ */ - /* rule_match = false; */ - /* break; */ - /* } */ - - /* switch(rulelist->rule->type){ */ - - /* case RT_UNDEFINED: */ - /* break; */ - - /* case RT_REGEX: */ - - /* if(fullquery && regexec(rulelist->rule->data,fullquery,0,NULL,0) != 0){ */ - /* rule_match = false; */ - /* } */ - - /* break; */ - - /* case RT_PERMISSION: */ - /* if(!rulelist->rule->allow){ */ - /* rule_match = false; */ - /* } */ - /* break; */ - - /* case RT_COLUMN: */ - - /* if(is_sql && is_real){ */ - - /* strln = (STRLINK*)rulelist->rule->data; */ - /* where = skygw_get_affected_fields(queue); */ - /* rule_match = false; */ - - /* if(where != NULL){ */ - - /* while(strln){ */ - - /* /\**At least one value matched*\/ */ - - /* if(strstr(where,strln->value)){ */ - /* rule_match = true; */ - /* break; */ - /* } */ - - /* strln = strln->next; */ - /* } */ - /* } */ - /* } */ - - /* break; */ - - /* case RT_WILDCARD: */ - - /* if(is_sql && is_real){ */ - - /* where = skygw_get_affected_fields(queue); */ - - /* if(where != NULL){ */ - /* if(strchr(where,'*') == NULL){ */ - /* rule_match = false; */ - /* } */ - /* } */ - /* } */ - - /* break; */ - /* case RT_THROTTLE: */ - /* queryspeed = (QUERYSPEED*)rulelist->rule->data; */ - /* if(queryspeed->count > queryspeed->limit) */ - /* { */ - - /* skygw_log_write(LOGFILE_TRACE, */ - /* "fwfilter: rule '%s': query limit triggered (%d queries during the last %f seconds), denying queries from user %s for %f seconds.", */ - /* rulelist->rule->name, */ - /* queryspeed->count, */ - /* queryspeed->period, */ - /* uname_addr, */ - /* queryspeed->cooldown); */ - /* queryspeed->triggered = time_now; */ - /* queryspeed->count = 0; */ - /* accept = false; */ - /* } */ - /* else if(difftime(queryspeed->triggered,time_now) < queryspeed->cooldown) */ - /* { */ - - /* double blocked_for = queryspeed->cooldown - difftime(queryspeed->triggered,time_now); */ - - /* sprintf(emsg,"Access denied for user %s for %f seconds.",uname_addr,blocked_for); */ - /* msg = strdup(emsg); */ - /* skygw_log_write(LOGFILE_TRACE, */ - /* "fwfilter: rule '%s': user %s blocked for %f seconds.", */ - /* rulelist->rule->name, */ - /* uname_addr, */ - /* blocked_for); */ - /* accept = false; */ - /* } */ - /* else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) */ - /* { */ - /* queryspeed->count++; */ - /* } */ - /* else */ - /* { */ - /* queryspeed->first_query = time_now; */ - /* } */ - - /* break; */ - - /* default: */ - /* break; */ - - /* } */ - - /* rulelist = rulelist->next; */ - /* } */ - - /* if(rule_match){ */ - /* /\**AND rules match*\/ */ - /* skygw_log_write(LOGFILE_TRACE, "fwfilter: all rules match, query is %s.",accept ? "allowed":"denied"); */ - /* accept = !my_instance->def_op; */ - /* } */ - queryresolved: free(ipaddr); @@ -1768,9 +1507,14 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) }else{ gwbuf_free(queue); - GWBUF* forward = gen_dummy_error(my_session,msg); - if(msg){ - free(msg); + + if(my_session->errmsg){ + msg = my_session->errmsg; + } + forward = gen_dummy_error(my_session,msg); + if(my_session->errmsg){ + free(my_session->errmsg); + my_session->errmsg = NULL; } return dcb->func.write(dcb,forward); } @@ -1796,4 +1540,3 @@ diagnostic(FILTER *instance, void *fsession, DCB *dcb) dcb_printf(dcb, "\t\tFirewall Filter\n"); } } - diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index d17d3348f..39967be6f 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -21,4 +21,6 @@ add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMA add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected ") -add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.expected ") \ No newline at end of file +add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.expected ") + +add_test(TestFwfilter3 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest3.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest3.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest3.expected ") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.expected b/server/modules/filter/test/fwtest.expected index a6e8ee1de..b10d04a79 100755 --- a/server/modules/filter/test/fwtest.expected +++ b/server/modules/filter/test/fwtest.expected @@ -1,4 +1,8 @@ -select 1; -select 1; -select 1; -select 1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.input b/server/modules/filter/test/fwtest.input index befe720c6..7b9a5071c 100644 --- a/server/modules/filter/test/fwtest.input +++ b/server/modules/filter/test/fwtest.input @@ -1,7 +1,10 @@ -select 1; delete from t1; -select 1; -update t1 set id=1; -select 1; -select id from t1 union select User from mysql.users; -select 1; \ No newline at end of file +select id from t1; +select id from t1; +select id from t1; +delete from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.output b/server/modules/filter/test/fwtest.output index a6e8ee1de..b10d04a79 100755 --- a/server/modules/filter/test/fwtest.output +++ b/server/modules/filter/test/fwtest.output @@ -1,4 +1,8 @@ -select 1; -select 1; -select 1; -select 1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; \ No newline at end of file diff --git a/server/modules/filter/test/fwtest2.expected b/server/modules/filter/test/fwtest2.expected index adde1069e..b10d04a79 100644 --- a/server/modules/filter/test/fwtest2.expected +++ b/server/modules/filter/test/fwtest2.expected @@ -1,3 +1,8 @@ -select 1; -select 1; -select 1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; \ No newline at end of file diff --git a/server/modules/filter/test/fwtest2.input b/server/modules/filter/test/fwtest2.input index 6308361c4..9cd95178d 100644 --- a/server/modules/filter/test/fwtest2.input +++ b/server/modules/filter/test/fwtest2.input @@ -1,8 +1,10 @@ -select 1; -select 1 union select 2; -select 1; -delete from test.table; -select 1; -select 1; -select 1; -select 1; \ No newline at end of file +select id from t1; +select id from t1 union select name from t2; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1; +select id from t1 union select name from t2; +select id from t1; +select id from t1; \ No newline at end of file diff --git a/server/modules/filter/test/rules b/server/modules/filter/test/rules index 5a81be3f0..050386139 100644 --- a/server/modules/filter/test/rules +++ b/server/modules/filter/test/rules @@ -1,4 +1,4 @@ rule union_regex deny regex '.*union.*' rule dont_delete_everything deny no_where_clause on_operations delete|update -rule limit_speed deny limit_queries 3 100.0 10.0 -users %@% match any rules union_regex dont_delete_everything limit_speed +rule no_wildcard deny wildcard +users %@% match any rules union_regex dont_delete_everything no_wildcard From eaac050bffab898b4ca7427a6649e4f43ba8ad7a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 25 Nov 2014 18:02:31 +0200 Subject: [PATCH 27/31] Fixed error messages causing a disconnect from the database. --- server/modules/filter/fwfilter.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 55314938b..4e81a7b4f 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -1038,10 +1038,10 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) char* errmsg; DCB* dcb = session->session->client; MYSQL_session* mysql_session = (MYSQL_session*)session->session->data; - unsigned int errlen, pktlen; + unsigned int errlen; errlen = msg != NULL ? strlen(msg) : 0; - errmsg = malloc((512 + errlen)*sizeof(char)); + errmsg = (char*)malloc((512 + errlen)*sizeof(char)); if(errmsg == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL."); @@ -1069,22 +1069,9 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) sprintf(ptr,": %s",msg); } - - errlen = strlen(errmsg); - pktlen = errlen + 9; - buf = gwbuf_alloc(13 + errlen); - if(buf){ - strcpy(buf->start + 7,"#HY000"); - memcpy(buf->start + 13,errmsg,errlen); - *((unsigned char*)buf->start + 0) = pktlen; - *((unsigned char*)buf->start + 1) = pktlen >> 8; - *((unsigned char*)buf->start + 2) = pktlen >> 16; - *((unsigned char*)buf->start + 3) = 0x01; - *((unsigned char*)buf->start + 4) = 0xff; - *((unsigned char*)buf->start + 5) = (unsigned char)1141; - *((unsigned char*)buf->start + 6) = (unsigned char)(1141 >> 8); - } + buf = modutil_create_mysql_err_msg(1,0,1141,"HY000", (const char*)errmsg); + return buf; } @@ -1512,6 +1499,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) msg = my_session->errmsg; } forward = gen_dummy_error(my_session,msg); + if(my_session->errmsg){ free(my_session->errmsg); my_session->errmsg = NULL; From e40519885b26342c2f209a757d216fbeeee8aead Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 30 Nov 2014 04:40:02 +0200 Subject: [PATCH 28/31] Updated documentation and cleaned up tests --- server/modules/filter/fwfilter.c | 9 +++++++++ server/modules/filter/test/CMakeLists.txt | 10 +++++----- .../modules/filter/test/{ => fwfilter}/fwtest.cnf.in | 0 .../modules/filter/test/{ => fwfilter}/fwtest.expected | 0 server/modules/filter/test/{ => fwfilter}/fwtest.input | 0 .../modules/filter/test/{ => fwfilter}/fwtest.output | 0 .../filter/test/{ => fwfilter}/fwtest2.expected | 0 .../modules/filter/test/{ => fwfilter}/fwtest2.input | 0 .../filter/test/{ => hintfilter}/hint_testing.cnf | 0 .../filter/test/{ => hintfilter}/hint_testing.expected | 0 .../filter/test/{ => hintfilter}/hint_testing.input | 0 .../modules/filter/test/{ => hintfilter}/hint_tests.sh | 0 .../filter/test/{ => regexfilter}/regextest.cnf | 0 .../filter/test/{ => regexfilter}/regextest.expected | 0 .../filter/test/{ => regexfilter}/regextest.input | 0 15 files changed, 14 insertions(+), 5 deletions(-) rename server/modules/filter/test/{ => fwfilter}/fwtest.cnf.in (100%) rename server/modules/filter/test/{ => fwfilter}/fwtest.expected (100%) rename server/modules/filter/test/{ => fwfilter}/fwtest.input (100%) rename server/modules/filter/test/{ => fwfilter}/fwtest.output (100%) rename server/modules/filter/test/{ => fwfilter}/fwtest2.expected (100%) rename server/modules/filter/test/{ => fwfilter}/fwtest2.input (100%) rename server/modules/filter/test/{ => hintfilter}/hint_testing.cnf (100%) rename server/modules/filter/test/{ => hintfilter}/hint_testing.expected (100%) rename server/modules/filter/test/{ => hintfilter}/hint_testing.input (100%) rename server/modules/filter/test/{ => hintfilter}/hint_tests.sh (100%) rename server/modules/filter/test/{ => regexfilter}/regextest.cnf (100%) rename server/modules/filter/test/{ => regexfilter}/regextest.expected (100%) rename server/modules/filter/test/{ => regexfilter}/regextest.input (100%) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 4e81a7b4f..823553fa7 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -1138,6 +1138,15 @@ bool rule_is_active(RULE* rule) return true; } +/** + * Check if a query matches a single rule + * @param my_instance Fwfilter instance + * @param my_session Fwfilter session + * @param queue The GWBUF containing the query + * @param rulelist The rule to check + * @param query Pointer to the null-terminated query string + * @return true if the query matches the rule + */ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, RULELIST *rulelist, char* query) { char *ptr,*where,*msg = NULL; diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 39967be6f..2de313c8b 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -15,12 +15,12 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_ -add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected ") +add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hintfilter/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.expected ") -add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regextest.expected ") +add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regexfilter/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.expected ") -add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected ") +add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.expected ") -add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest2.expected ") +add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.expected ") -add_test(TestFwfilter3 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest3.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest3.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest3.expected ") \ No newline at end of file +add_test(TestFwfilter3 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest3.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.expected ") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.cnf.in b/server/modules/filter/test/fwfilter/fwtest.cnf.in similarity index 100% rename from server/modules/filter/test/fwtest.cnf.in rename to server/modules/filter/test/fwfilter/fwtest.cnf.in diff --git a/server/modules/filter/test/fwtest.expected b/server/modules/filter/test/fwfilter/fwtest.expected similarity index 100% rename from server/modules/filter/test/fwtest.expected rename to server/modules/filter/test/fwfilter/fwtest.expected diff --git a/server/modules/filter/test/fwtest.input b/server/modules/filter/test/fwfilter/fwtest.input similarity index 100% rename from server/modules/filter/test/fwtest.input rename to server/modules/filter/test/fwfilter/fwtest.input diff --git a/server/modules/filter/test/fwtest.output b/server/modules/filter/test/fwfilter/fwtest.output similarity index 100% rename from server/modules/filter/test/fwtest.output rename to server/modules/filter/test/fwfilter/fwtest.output diff --git a/server/modules/filter/test/fwtest2.expected b/server/modules/filter/test/fwfilter/fwtest2.expected similarity index 100% rename from server/modules/filter/test/fwtest2.expected rename to server/modules/filter/test/fwfilter/fwtest2.expected diff --git a/server/modules/filter/test/fwtest2.input b/server/modules/filter/test/fwfilter/fwtest2.input similarity index 100% rename from server/modules/filter/test/fwtest2.input rename to server/modules/filter/test/fwfilter/fwtest2.input diff --git a/server/modules/filter/test/hint_testing.cnf b/server/modules/filter/test/hintfilter/hint_testing.cnf similarity index 100% rename from server/modules/filter/test/hint_testing.cnf rename to server/modules/filter/test/hintfilter/hint_testing.cnf diff --git a/server/modules/filter/test/hint_testing.expected b/server/modules/filter/test/hintfilter/hint_testing.expected similarity index 100% rename from server/modules/filter/test/hint_testing.expected rename to server/modules/filter/test/hintfilter/hint_testing.expected diff --git a/server/modules/filter/test/hint_testing.input b/server/modules/filter/test/hintfilter/hint_testing.input similarity index 100% rename from server/modules/filter/test/hint_testing.input rename to server/modules/filter/test/hintfilter/hint_testing.input diff --git a/server/modules/filter/test/hint_tests.sh b/server/modules/filter/test/hintfilter/hint_tests.sh similarity index 100% rename from server/modules/filter/test/hint_tests.sh rename to server/modules/filter/test/hintfilter/hint_tests.sh diff --git a/server/modules/filter/test/regextest.cnf b/server/modules/filter/test/regexfilter/regextest.cnf similarity index 100% rename from server/modules/filter/test/regextest.cnf rename to server/modules/filter/test/regexfilter/regextest.cnf diff --git a/server/modules/filter/test/regextest.expected b/server/modules/filter/test/regexfilter/regextest.expected similarity index 100% rename from server/modules/filter/test/regextest.expected rename to server/modules/filter/test/regexfilter/regextest.expected diff --git a/server/modules/filter/test/regextest.input b/server/modules/filter/test/regexfilter/regextest.input similarity index 100% rename from server/modules/filter/test/regextest.input rename to server/modules/filter/test/regexfilter/regextest.input From 27913edc286a3fcf7add50b3c8ea7065dfa83f86 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 3 Dec 2014 10:56:22 +0200 Subject: [PATCH 29/31] Fixed query throttling using the rule to store the values instead of the user. --- server/modules/filter/fwfilter.c | 81 +++++++++++++++++++---- server/modules/filter/test/CMakeLists.txt | 2 +- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 823553fa7..d3537c662 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -138,6 +138,8 @@ typedef struct queryspeed_t{ double cooldown; int count; int limit; + long id; + struct queryspeed_t* next; }QUERYSPEED; @@ -166,6 +168,8 @@ typedef struct rulelist_t{ typedef struct user_t{ char* name; + SPINLOCK* lock; + QUERYSPEED* qs_limit; RULELIST* rules_or; RULELIST* rules_and; }USER; @@ -187,17 +191,18 @@ typedef struct { RULELIST* rules; STRLINK* userstrings; bool def_op; - + SPINLOCK* lock; + long idgen; /**UID generator*/ } FW_INSTANCE; /** * The session structure for Firewall filter. */ typedef struct { - DOWNSTREAM down; - UPSTREAM up; SESSION* session; char* errmsg; + DOWNSTREAM down; + UPSTREAM up; } FW_SESSION; static int hashkeyfun(void* key); @@ -661,12 +666,21 @@ void link_rules(char* rule, FW_INSTANCE* instance) /**New user*/ user = (USER*)calloc(1,sizeof(USER)); + if(user == NULL){ return; } + + if((user->lock = (SPINLOCK*)malloc(sizeof(SPINLOCK))) == NULL){ + free(user); + return; + } + + spinlock_init(user->lock); } user->name = (char*)strdup(userptr); + user->qs_limit = NULL; tl = (RULELIST*)rlistdup(rulelist); tail = tl; while(tail && tail->next){ @@ -700,7 +714,7 @@ void link_rules(char* rule, FW_INSTANCE* instance) */ void parse_rule(char* rule, FW_INSTANCE* instance) { - assert(rule != NULL && instance != NULL); + ss_dassert(rule != NULL && instance != NULL); char *rulecpy = strdup(rule); char *tok = strtok(rulecpy," ,"); @@ -835,7 +849,11 @@ void parse_rule(char* rule, FW_INSTANCE* instance) { QUERYSPEED* qs = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); - + + spinlock_acquire(instance->lock); + qs->id = ++instance->idgen; + spinlock_release(instance->lock); + tok = strtok(NULL," "); qs->limit = atoi(tok); @@ -890,9 +908,13 @@ createInstance(char **options, FILTER_PARAMETER **params) char *filename = NULL, *nl; char buffer[2048]; FILE* file; - if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL){ + + if ((my_instance = calloc(1, sizeof(FW_INSTANCE))) == NULL || + (my_instance->lock = (SPINLOCK*)malloc(sizeof(SPINLOCK))) == NULL){ return NULL; } + + spinlock_init(my_instance->lock); if((ht = hashtable_alloc(7, hashkeyfun, hashcmpfun)) == NULL){ skygw_log_write(LOGFILE_ERROR, "Unable to allocate hashtable."); @@ -936,7 +958,6 @@ createInstance(char **options, FILTER_PARAMETER **params) if((nl = strchr(buffer,'\n')) != NULL && ((char*)nl - (char*)buffer) < 2048){ *nl = '\0'; } - parse_rule(buffer,my_instance); } @@ -1147,7 +1168,7 @@ bool rule_is_active(RULE* rule) * @param query Pointer to the null-terminated query string * @return true if the query matches the rule */ -bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, RULELIST *rulelist, char* query) +bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue, USER* user, RULELIST *rulelist, char* query) { char *ptr,*where,*msg = NULL; char emsg[512]; @@ -1156,6 +1177,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue skygw_query_op_t optype; STRLINK* strln = NULL; QUERYSPEED* queryspeed = NULL; + QUERYSPEED* rule_qs = NULL; time_t time_now; struct tm* tm_now; @@ -1269,7 +1291,39 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue break; case RT_THROTTLE: - queryspeed = (QUERYSPEED*)rulelist->rule->data; + + /** + * Check if this is the first time this rule is matched and if so, allocate + * and initialize a new QUERYSPEED struct for this session. + */ + + spinlock_acquire(my_instance->lock); + rule_qs = (QUERYSPEED*)rulelist->rule->data; + spinlock_release(my_instance->lock); + + spinlock_acquire(user->lock); + queryspeed = user->qs_limit; + + + while(queryspeed){ + if(queryspeed->id == rule_qs->id){ + break; + } + queryspeed = queryspeed->next; + } + + if(queryspeed == NULL){ + + /**No match found*/ + queryspeed = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); + queryspeed->period = rule_qs->period; + queryspeed->cooldown = rule_qs->cooldown; + queryspeed->limit = rule_qs->limit; + queryspeed->id = rule_qs->id; + queryspeed->next = user->qs_limit; + user->qs_limit = queryspeed; + } + if(queryspeed->count > queryspeed->limit) { queryspeed->triggered = time_now; @@ -1283,6 +1337,9 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue queryspeed->limit, queryspeed->period, queryspeed->cooldown); + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + sprintf(emsg,"Queries denied for %f seconds",blocked_for); + msg = strdup(emsg); } else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) { @@ -1303,7 +1360,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue { queryspeed->first_query = time_now; } - + spinlock_release(user->lock); break; case RT_CLAUSE: @@ -1366,7 +1423,7 @@ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu rulelist = rulelist->next; continue; } - if((rval = rule_matches(my_instance,my_session,queue,rulelist,fullquery))){ + if((rval = rule_matches(my_instance,my_session,queue,user,rulelist,fullquery))){ goto retblock; } rulelist = rulelist->next; @@ -1417,7 +1474,7 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu continue; } - if(!rule_matches(my_instance,my_session,queue,rulelist,fullquery)){ + if(!rule_matches(my_instance,my_session,queue,user,rulelist,fullquery)){ rval = false; goto retblock; } diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 2de313c8b..2356affd2 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -11,7 +11,7 @@ add_executable(harness harness_util.c harness_common.c ${CORE}) target_link_libraries(harness_ui fullcore log_manager utils) target_link_libraries(harness fullcore) execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR}) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf) From 0060bbb7df7062785c211324f9d325fe40596319 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 4 Dec 2014 17:04:16 +0200 Subject: [PATCH 30/31] Updated the diagnostic to print rules, their types and how many times they have been matched. --- server/modules/filter/fwfilter.c | 45 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index d3537c662..19c31cf7e 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -108,7 +108,7 @@ static FILTER_OBJECT MyObject = { * Rule types */ typedef enum { - RT_UNDEFINED, + RT_UNDEFINED = 0x00, RT_COLUMN, RT_THROTTLE, RT_PERMISSION, @@ -117,6 +117,17 @@ typedef enum { RT_CLAUSE }ruletype_t; +const char* rule_names[] = { + "RT_UNDEFINED", + "RT_COLUMN", + "RT_THROTTLE", + "RT_PERMISSION", + "RT_WILDCARD", + "RT_REGEX", + "RT_CLAUSE" +}; + + /** * Linked list of strings. */ @@ -155,6 +166,7 @@ typedef struct rule_t{ ruletype_t type; skygw_query_op_t on_queries; bool allow; + int times_matched; TIMERANGE* active; }RULE; @@ -1384,7 +1396,12 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue queryresolved: if(msg){ my_session->errmsg = msg; - } + } + + if(matches){ + rulelist->rule->times_matched++; + } + return matches; } @@ -1588,9 +1605,29 @@ static void diagnostic(FILTER *instance, void *fsession, DCB *dcb) { FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; - + RULELIST* rules; + int type; + if (my_instance) { - dcb_printf(dcb, "\t\tFirewall Filter\n"); + spinlock_acquire(my_instance->lock); + rules = my_instance->rules; + + dcb_printf(dcb, "Firewall Filter\n"); + dcb_printf(dcb, "%-24s%-24s%-24s\n","Rule","Type","Times Matched"); + while(rules){ + if((int)rules->rule->type > 0 && + (int)rules->rule->type < sizeof(rule_names)/sizeof(char**)){ + type = (int)rules->rule->type; + }else{ + type = 0; + } + dcb_printf(dcb,"%-24s%-24s%-24d\n", + rules->rule->name, + rule_names[type], + rules->rule->times_matched); + rules = rules->next; + } + spinlock_release(my_instance->lock); } } From d41d16a1f3b73d8e0b055e186dd7c414a46c849c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 18 Dec 2014 19:51:24 +0200 Subject: [PATCH 31/31] Cleaned up the code and documented fucntions. --- server/modules/filter/fwfilter.c | 798 ++++++++++++++++--------------- 1 file changed, 402 insertions(+), 396 deletions(-) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 19c31cf7e..2a15d126f 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -68,11 +68,12 @@ #include #include #include +#include #include #include #include MODULE_INFO info = { - MODULE_API_FILTER, + MODULE_API_FILTER, MODULE_ALPHA_RELEASE, FILTER_VERSION, "Firewall Filter" @@ -92,7 +93,7 @@ static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); static FILTER_OBJECT MyObject = { - createInstance, + createInstance, newSession, closeSession, freeSession, @@ -108,7 +109,7 @@ static FILTER_OBJECT MyObject = { * Rule types */ typedef enum { - RT_UNDEFINED = 0x00, + RT_UNDEFINED = 0x00, RT_COLUMN, RT_THROTTLE, RT_PERMISSION, @@ -118,13 +119,13 @@ typedef enum { }ruletype_t; const char* rule_names[] = { - "RT_UNDEFINED", - "RT_COLUMN", - "RT_THROTTLE", - "RT_PERMISSION", - "RT_WILDCARD", - "RT_REGEX", - "RT_CLAUSE" + "UNDEFINED", + "COLUMN", + "THROTTLE", + "PERMISSION", + "WILDCARD", + "REGEX", + "CLAUSE" }; @@ -132,25 +133,25 @@ const char* rule_names[] = { * Linked list of strings. */ typedef struct strlink_t{ - struct strlink_t *next; - char* value; + struct strlink_t *next; + char* value; }STRLINK; typedef struct timerange_t{ - struct timerange_t* next; - struct tm start; - struct tm end; + struct timerange_t* next; + struct tm start; + struct tm end; }TIMERANGE; typedef struct queryspeed_t{ - time_t first_query; - time_t triggered; - double period; - double cooldown; - int count; - int limit; - long id; - struct queryspeed_t* next; + time_t first_query; + time_t triggered; + double period; + double cooldown; + int count; + int limit; + long id; + struct queryspeed_t* next; }QUERYSPEED; @@ -161,29 +162,29 @@ typedef struct queryspeed_t{ * This allows to match an arbitrary set of rules against a user. */ typedef struct rule_t{ - void* data; - char* name; - ruletype_t type; - skygw_query_op_t on_queries; - bool allow; - int times_matched; - TIMERANGE* active; + void* data; + char* name; + ruletype_t type; + skygw_query_op_t on_queries; + bool allow; + int times_matched; + TIMERANGE* active; }RULE; /** * Linked list of pointers to a global pool of RULE structs */ typedef struct rulelist_t{ - RULE* rule; - struct rulelist_t* next; + RULE* rule; + struct rulelist_t* next; }RULELIST; typedef struct user_t{ - char* name; - SPINLOCK* lock; - QUERYSPEED* qs_limit; - RULELIST* rules_or; - RULELIST* rules_and; + char* name; + SPINLOCK* lock; + QUERYSPEED* qs_limit; + RULELIST* rules_or; + RULELIST* rules_and; }USER; /** @@ -299,9 +300,9 @@ static void* hrulefree(void* fval) /** - * Replace all non-essential characters with whitespace from a null-terminated string. + * Strips the single or double quotes from a string. * This function modifies the passed string. - * @param str String to purify + * @param str String to parse * @return Pointer to the modified string */ char* strip_tags(char* str) @@ -388,29 +389,29 @@ bool parse_querytypes(char* str,RULE* rule) dest = buffer; while(ptr - buffer < 512) - { - if(*ptr == '|' || *ptr == ' ' || (done = *ptr == '\0')){ - *dest = '\0'; - if(strcmp(buffer,"select") == 0){ - rule->on_queries |= QUERY_OP_SELECT; - }else if(strcmp(buffer,"insert") == 0){ - rule->on_queries |= QUERY_OP_INSERT; - }else if(strcmp(buffer,"update") == 0){ - rule->on_queries |= QUERY_OP_UPDATE; - }else if(strcmp(buffer,"delete") == 0){ - rule->on_queries |= QUERY_OP_DELETE; - } + { + if(*ptr == '|' || *ptr == ' ' || (done = *ptr == '\0')){ + *dest = '\0'; + if(strcmp(buffer,"select") == 0){ + rule->on_queries |= QUERY_OP_SELECT; + }else if(strcmp(buffer,"insert") == 0){ + rule->on_queries |= QUERY_OP_INSERT; + }else if(strcmp(buffer,"update") == 0){ + rule->on_queries |= QUERY_OP_UPDATE; + }else if(strcmp(buffer,"delete") == 0){ + rule->on_queries |= QUERY_OP_DELETE; + } - if(done){ - return true; - } + if(done){ + return true; + } - dest = buffer; - ptr++; - }else{ - *dest++ = *ptr++; - } - } + dest = buffer; + ptr++; + }else{ + *dest++ = *ptr++; + } + } return false; } @@ -435,9 +436,14 @@ bool check_time(char* str) return numbers == 12 && colons == 4 && dashes == 1; } -#define CHK_TIMES(t)(assert(t->tm_sec > -1 && t->tm_sec < 62 \ - && t->tm_min > -1 && t->tm_min < 60 \ - && t->tm_hour > -1 && t->tm_hour < 24)) + +#ifdef SS_DEBUG +#define CHK_TIMES(t)(ss_dassert(t->tm_sec > -1 && t->tm_sec < 62 \ + && t->tm_min > -1 && t->tm_min < 60 \ + && t->tm_hour > -1 && t->tm_hour < 24)) +#else +#define CHK_TIMES(t) +#endif #define IS_RVRS_TIME(tr) (mktime(&tr->end) < mktime(&tr->start)) /** @@ -485,9 +491,9 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance) tmptr->tm_hour = intbuffer[0]; tmptr->tm_min = intbuffer[1]; tmptr->tm_sec = intbuffer[2]; - + CHK_TIMES(tmptr); - + if(*ptr == '\0'){ return tr; } @@ -543,9 +549,9 @@ version() } /** - * The module initialisation routine, called when the module - * is first loaded. - */ +* The module initialisation routine, called when the module +* is first loaded. +*/ void ModuleInit() { @@ -647,19 +653,19 @@ void link_rules(char* rule, FW_INSTANCE* instance) tok = strtok(NULL," "); while(tok) - { - RULE* rule_found = NULL; + { + RULE* rule_found = NULL; - if((rule_found = find_rule(tok,instance)) != NULL) - { - RULELIST* tmp_rl = (RULELIST*)calloc(1,sizeof(RULELIST)); - tmp_rl->rule = rule_found; - tmp_rl->next = rulelist; - rulelist = tmp_rl; + if((rule_found = find_rule(tok,instance)) != NULL) + { + RULELIST* tmp_rl = (RULELIST*)calloc(1,sizeof(RULELIST)); + tmp_rl->rule = rule_found; + tmp_rl->next = rulelist; + rulelist = tmp_rl; - } - tok = strtok(NULL," "); - } + } + tok = strtok(NULL," "); + } /** * Apply this list of rules to all the listed users @@ -670,51 +676,51 @@ void link_rules(char* rule, FW_INSTANCE* instance) userptr = strtok(NULL," "); while(userptr) - { - USER* user; - RULELIST *tl = NULL,*tail = NULL; + { + USER* user; + RULELIST *tl = NULL,*tail = NULL; - if((user = (USER*)hashtable_fetch(instance->htable,userptr)) == NULL){ + if((user = (USER*)hashtable_fetch(instance->htable,userptr)) == NULL){ - /**New user*/ - user = (USER*)calloc(1,sizeof(USER)); + /**New user*/ + user = (USER*)calloc(1,sizeof(USER)); - if(user == NULL){ - return; - } + if(user == NULL){ + return; + } - if((user->lock = (SPINLOCK*)malloc(sizeof(SPINLOCK))) == NULL){ - free(user); - return; - } + if((user->lock = (SPINLOCK*)malloc(sizeof(SPINLOCK))) == NULL){ + free(user); + return; + } - spinlock_init(user->lock); - } + spinlock_init(user->lock); + } - user->name = (char*)strdup(userptr); - user->qs_limit = NULL; - tl = (RULELIST*)rlistdup(rulelist); - tail = tl; - while(tail && tail->next){ - tail = tail->next; - } + user->name = (char*)strdup(userptr); + user->qs_limit = NULL; + tl = (RULELIST*)rlistdup(rulelist); + tail = tl; + while(tail && tail->next){ + tail = tail->next; + } - if(match_any){ - tail->next = user->rules_or; - user->rules_or = tl; - }else{ - tail->next = user->rules_and; - user->rules_and = tl; - } + if(match_any){ + tail->next = user->rules_or; + user->rules_or = tl; + }else{ + tail->next = user->rules_and; + user->rules_and = tl; + } - hashtable_add(instance->htable, - (void *)userptr, - (void *)user); + hashtable_add(instance->htable, + (void *)userptr, + (void *)user); - userptr = strtok(NULL," "); + userptr = strtok(NULL," "); - } + } } @@ -773,131 +779,131 @@ void parse_rule(char* rule, FW_INSTANCE* instance) while(tok){ if(strcmp(tok,"wildcard") == 0) - { - ruledef->type = RT_WILDCARD; - } + { + ruledef->type = RT_WILDCARD; + } else if(strcmp(tok,"columns") == 0) - { - STRLINK *tail = NULL,*current; - ruledef->type = RT_COLUMN; - tok = strtok(NULL, " ,"); - while(tok && strcmp(tok,"at_times") != 0){ - current = malloc(sizeof(STRLINK)); - current->value = strdup(tok); - current->next = tail; - tail = current; - tok = strtok(NULL, " ,"); - } + { + STRLINK *tail = NULL,*current; + ruledef->type = RT_COLUMN; + tok = strtok(NULL, " ,"); + while(tok && strcmp(tok,"at_times") != 0){ + current = malloc(sizeof(STRLINK)); + current->value = strdup(tok); + current->next = tail; + tail = current; + tok = strtok(NULL, " ,"); + } - ruledef->data = (void*)tail; - continue; + ruledef->data = (void*)tail; + continue; - } + } else if(strcmp(tok,"at_times") == 0) - { + { - tok = strtok(NULL, " ,"); - TIMERANGE *tr = NULL; - while(tok){ - TIMERANGE *tmp = parse_time(tok,instance); + tok = strtok(NULL, " ,"); + TIMERANGE *tr = NULL; + while(tok){ + TIMERANGE *tmp = parse_time(tok,instance); - if(IS_RVRS_TIME(tmp)){ - tmp = split_reverse_time(tmp); - } - tmp->next = tr; - tr = tmp; - tok = strtok(NULL, " ,"); - } - ruledef->active = tr; - } + if(IS_RVRS_TIME(tmp)){ + tmp = split_reverse_time(tmp); + } + tmp->next = tr; + tr = tmp; + tok = strtok(NULL, " ,"); + } + ruledef->active = tr; + } else if(strcmp(tok,"regex") == 0) - { - bool escaped = false; - regex_t *re; - char* start, *str; - tok = strtok(NULL," "); + { + bool escaped = false; + regex_t *re; + char* start, *str; + tok = strtok(NULL," "); - while(*tok == '\'' || *tok == '"'){ - tok++; - } + while(*tok == '\'' || *tok == '"'){ + tok++; + } - start = tok; + start = tok; - while(isspace(*tok) || *tok == '\'' || *tok == '"'){ - tok++; - } + while(isspace(*tok) || *tok == '\'' || *tok == '"'){ + tok++; + } - while(true){ + while(true){ - if((*tok == '\'' || *tok == '"') && !escaped){ - break; - } - escaped = (*tok == '\\'); - tok++; - } + if((*tok == '\'' || *tok == '"') && !escaped){ + break; + } + escaped = (*tok == '\\'); + tok++; + } - str = calloc(((tok - start) + 1),sizeof(char)); - re = (regex_t*)malloc(sizeof(regex_t)); + str = calloc(((tok - start) + 1),sizeof(char)); + re = (regex_t*)malloc(sizeof(regex_t)); - if(re == NULL || str == NULL){ - skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL."); + if(re == NULL || str == NULL){ + skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL."); - return; - } + return; + } - memcpy(str, start, (tok-start)); + memcpy(str, start, (tok-start)); - if(regcomp(re, str,REG_NOSUB)){ - skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); - free(re); - } + if(regcomp(re, str,REG_NOSUB)){ + skygw_log_write(LOGFILE_ERROR, "fwfilter: Invalid regular expression '%s'.", str); + free(re); + } - ruledef->type = RT_REGEX; - ruledef->data = (void*) re; - free(str); + ruledef->type = RT_REGEX; + ruledef->data = (void*) re; + free(str); - } + } else if(strcmp(tok,"limit_queries") == 0) - { + { - QUERYSPEED* qs = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); + QUERYSPEED* qs = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); - spinlock_acquire(instance->lock); - qs->id = ++instance->idgen; - spinlock_release(instance->lock); + spinlock_acquire(instance->lock); + qs->id = ++instance->idgen; + spinlock_release(instance->lock); - tok = strtok(NULL," "); - qs->limit = atoi(tok); + tok = strtok(NULL," "); + qs->limit = atoi(tok); - tok = strtok(NULL," "); - qs->period = atof(tok); - tok = strtok(NULL," "); - qs->cooldown = atof(tok); - ruledef->type = RT_THROTTLE; - ruledef->data = (void*)qs; - } + tok = strtok(NULL," "); + qs->period = atof(tok); + tok = strtok(NULL," "); + qs->cooldown = atof(tok); + ruledef->type = RT_THROTTLE; + ruledef->data = (void*)qs; + } else if(strcmp(tok,"no_where_clause") == 0) - { - ruledef->type = RT_CLAUSE; - ruledef->data = (void*)mode; - } + { + ruledef->type = RT_CLAUSE; + ruledef->data = (void*)mode; + } else if(strcmp(tok,"on_operations") == 0) - { - tok = strtok(NULL," "); - if(!parse_querytypes(tok,ruledef)){ - skygw_log_write(LOGFILE_ERROR, - "fwfilter: Invalid query type" - "requirements on where/having clauses: %s." - ,tok); - } - } + { + tok = strtok(NULL," "); + if(!parse_querytypes(tok,ruledef)){ + skygw_log_write(LOGFILE_ERROR, + "fwfilter: Invalid query type" + "requirements on where/having clauses: %s." + ,tok); + } + } tok = strtok(NULL," ,"); } goto retblock; } - retblock: + retblock: free(rulecpy); } @@ -954,25 +960,25 @@ createInstance(char **options, FILTER_PARAMETER **params) free(filename); while(!feof(file)) - { + { - if(fgets(buffer,2048,file) == NULL){ - if(ferror(file)){ - free(my_instance); - return NULL; - } + if(fgets(buffer,2048,file) == NULL){ + if(ferror(file)){ + free(my_instance); + return NULL; + } - if(feof(file)){ - break; - } - } + if(feof(file)){ + break; + } + } - if((nl = strchr(buffer,'\n')) != NULL && ((char*)nl - (char*)buffer) < 2048){ - *nl = '\0'; - } - parse_rule(buffer,my_instance); + if((nl = strchr(buffer,'\n')) != NULL && ((char*)nl - (char*)buffer) < 2048){ + *nl = '\0'; + } + parse_rule(buffer,my_instance); - } + } fclose(file); @@ -1083,19 +1089,19 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) if(mysql_session->db[0] == '\0') - { - sprintf(errmsg, - "Access denied for user '%s'@'%s'", - dcb->user, - dcb->remote); - }else - { - sprintf(errmsg, - "Access denied for user '%s'@'%s' to database '%s'", - dcb->user, - dcb->remote, - mysql_session->db); - } + { + sprintf(errmsg, + "Access denied for user '%s'@'%s'", + dcb->user, + dcb->remote); + }else + { + sprintf(errmsg, + "Access denied for user '%s'@'%s' to database '%s'", + dcb->user, + dcb->remote, + mysql_session->db); + } if(msg != NULL){ char* ptr = strchr(errmsg,'\0'); @@ -1215,185 +1221,185 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ - switch(rulelist->rule->type){ + switch(rulelist->rule->type){ - case RT_UNDEFINED: - skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); - break; + case RT_UNDEFINED: + skygw_log_write_flush(LOGFILE_ERROR, "Error: Undefined rule type found."); + break; - case RT_REGEX: + case RT_REGEX: - if(query && regexec(rulelist->rule->data,query,0,NULL,0) == 0){ + if(query && regexec(rulelist->rule->data,query,0,NULL,0) == 0){ - matches = true; + matches = true; - if(!rulelist->rule->allow){ - msg = strdup("Permission denied, query matched regular expression."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); - goto queryresolved; - }else{ - break; - } - } + if(!rulelist->rule->allow){ + msg = strdup("Permission denied, query matched regular expression."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': regex matched on query",rulelist->rule->name); + goto queryresolved; + }else{ + break; + } + } - break; + break; - case RT_PERMISSION: - if(!rulelist->rule->allow){ - matches = true; - msg = strdup("Permission denied at this time."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); - goto queryresolved; - }else{ - break; - } - break; + case RT_PERMISSION: + if(!rulelist->rule->allow){ + matches = true; + msg = strdup("Permission denied at this time."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query denied at: %s",rulelist->rule->name,asctime(tm_now)); + goto queryresolved; + }else{ + break; + } + break; - case RT_COLUMN: + case RT_COLUMN: - if(is_sql && is_real){ + if(is_sql && is_real){ - strln = (STRLINK*)rulelist->rule->data; - where = skygw_get_affected_fields(queue); + strln = (STRLINK*)rulelist->rule->data; + where = skygw_get_affected_fields(queue); - if(where != NULL){ + if(where != NULL){ - while(strln){ - if(strstr(where,strln->value)){ + while(strln){ + if(strstr(where,strln->value)){ - matches = true; + matches = true; - if(!rulelist->rule->allow){ - sprintf(emsg,"Permission denied to column '%s'.",strln->value); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); - msg = strdup(emsg); - goto queryresolved; - }else{ - break; - } - } - strln = strln->next; - } - } - } + if(!rulelist->rule->allow){ + sprintf(emsg,"Permission denied to column '%s'.",strln->value); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query targets forbidden column: %s",rulelist->rule->name,strln->value); + msg = strdup(emsg); + goto queryresolved; + }else{ + break; + } + } + strln = strln->next; + } + } + } - break; + break; - case RT_WILDCARD: + case RT_WILDCARD: - if(is_sql && is_real){ - char * strptr; - where = skygw_get_affected_fields(queue); + if(is_sql && is_real){ + char * strptr; + where = skygw_get_affected_fields(queue); - if(where != NULL){ - strptr = where; - }else{ - strptr = query; - } - if(strchr(strptr,'*')){ + if(where != NULL){ + strptr = where; + }else{ + strptr = query; + } + if(strchr(strptr,'*')){ - matches = true; - msg = strdup("Usage of wildcard denied."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); - goto queryresolved; - } - } + matches = true; + msg = strdup("Usage of wildcard denied."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query contains a wildcard.",rulelist->rule->name); + goto queryresolved; + } + } - break; + break; - case RT_THROTTLE: + case RT_THROTTLE: - /** - * Check if this is the first time this rule is matched and if so, allocate - * and initialize a new QUERYSPEED struct for this session. - */ + /** + * Check if this is the first time this rule is matched and if so, allocate + * and initialize a new QUERYSPEED struct for this session. + */ - spinlock_acquire(my_instance->lock); - rule_qs = (QUERYSPEED*)rulelist->rule->data; - spinlock_release(my_instance->lock); + spinlock_acquire(my_instance->lock); + rule_qs = (QUERYSPEED*)rulelist->rule->data; + spinlock_release(my_instance->lock); - spinlock_acquire(user->lock); - queryspeed = user->qs_limit; + spinlock_acquire(user->lock); + queryspeed = user->qs_limit; - while(queryspeed){ - if(queryspeed->id == rule_qs->id){ - break; - } - queryspeed = queryspeed->next; - } + while(queryspeed){ + if(queryspeed->id == rule_qs->id){ + break; + } + queryspeed = queryspeed->next; + } - if(queryspeed == NULL){ + if(queryspeed == NULL){ - /**No match found*/ - queryspeed = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); - queryspeed->period = rule_qs->period; - queryspeed->cooldown = rule_qs->cooldown; - queryspeed->limit = rule_qs->limit; - queryspeed->id = rule_qs->id; - queryspeed->next = user->qs_limit; - user->qs_limit = queryspeed; - } + /**No match found*/ + queryspeed = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED)); + queryspeed->period = rule_qs->period; + queryspeed->cooldown = rule_qs->cooldown; + queryspeed->limit = rule_qs->limit; + queryspeed->id = rule_qs->id; + queryspeed->next = user->qs_limit; + user->qs_limit = queryspeed; + } - if(queryspeed->count > queryspeed->limit) - { - queryspeed->triggered = time_now; - queryspeed->count = 0; - matches = true; + if(queryspeed->count > queryspeed->limit) + { + queryspeed->triggered = time_now; + queryspeed->count = 0; + matches = true; - skygw_log_write(LOGFILE_TRACE, - "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user for %f seconds.", - rulelist->rule->name, - queryspeed->limit, - queryspeed->period, - queryspeed->cooldown); - double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); - sprintf(emsg,"Queries denied for %f seconds",blocked_for); - msg = strdup(emsg); - } - else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) - { + skygw_log_write(LOGFILE_TRACE, + "fwfilter: rule '%s': query limit triggered (%d queries in %f seconds), denying queries from user for %f seconds.", + rulelist->rule->name, + queryspeed->limit, + queryspeed->period, + queryspeed->cooldown); + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + sprintf(emsg,"Queries denied for %f seconds",blocked_for); + msg = strdup(emsg); + } + else if(difftime(time_now,queryspeed->triggered) < queryspeed->cooldown) + { - double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); + double blocked_for = queryspeed->cooldown - difftime(time_now,queryspeed->triggered); - sprintf(emsg,"Queries denied for %f seconds",blocked_for); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user denied for %f seconds",rulelist->rule->name,blocked_for); - msg = strdup(emsg); + sprintf(emsg,"Queries denied for %f seconds",blocked_for); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': user denied for %f seconds",rulelist->rule->name,blocked_for); + msg = strdup(emsg); - matches = true; - } - else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) - { - queryspeed->count++; - } - else - { - queryspeed->first_query = time_now; - } - spinlock_release(user->lock); - break; + matches = true; + } + else if(difftime(time_now,queryspeed->first_query) < queryspeed->period) + { + queryspeed->count++; + } + else + { + queryspeed->first_query = time_now; + } + spinlock_release(user->lock); + break; - case RT_CLAUSE: + case RT_CLAUSE: - if(is_sql && is_real && - !skygw_query_has_clause(queue)) - { - matches = true; - msg = strdup("Required WHERE/HAVING clause is missing."); - skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", - rulelist->rule->name); - } - break; + if(is_sql && is_real && + !skygw_query_has_clause(queue)) + { + matches = true; + msg = strdup("Required WHERE/HAVING clause is missing."); + skygw_log_write(LOGFILE_TRACE, "fwfilter: rule '%s': query has no where/having clause, query is denied.", + rulelist->rule->name); + } + break; - default: - break; + default: + break; - } - } + } + } - queryresolved: + queryresolved: if(msg){ my_session->errmsg = msg; } @@ -1446,7 +1452,7 @@ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu rulelist = rulelist->next; } - retblock: + retblock: free(fullquery); @@ -1498,7 +1504,7 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu rulelist = rulelist->next; } - retblock: + retblock: free(fullquery); @@ -1565,7 +1571,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) goto queryresolved; } - queryresolved: + queryresolved: free(ipaddr); free(fullquery); @@ -1609,25 +1615,25 @@ diagnostic(FILTER *instance, void *fsession, DCB *dcb) int type; if (my_instance) - { - spinlock_acquire(my_instance->lock); - rules = my_instance->rules; + { + spinlock_acquire(my_instance->lock); + rules = my_instance->rules; - dcb_printf(dcb, "Firewall Filter\n"); - dcb_printf(dcb, "%-24s%-24s%-24s\n","Rule","Type","Times Matched"); - while(rules){ - if((int)rules->rule->type > 0 && - (int)rules->rule->type < sizeof(rule_names)/sizeof(char**)){ - type = (int)rules->rule->type; - }else{ - type = 0; - } - dcb_printf(dcb,"%-24s%-24s%-24d\n", - rules->rule->name, - rule_names[type], - rules->rule->times_matched); - rules = rules->next; - } - spinlock_release(my_instance->lock); - } + dcb_printf(dcb, "Firewall Filter\n"); + dcb_printf(dcb, "%-24s%-24s%-24s\n","Rule","Type","Times Matched"); + while(rules){ + if((int)rules->rule->type > 0 && + (int)rules->rule->type < sizeof(rule_names)/sizeof(char**)){ + type = (int)rules->rule->type; + }else{ + type = 0; + } + dcb_printf(dcb,"%-24s%-24s%-24d\n", + rules->rule->name, + rule_names[type], + rules->rule->times_matched); + rules = rules->next; + } + spinlock_release(my_instance->lock); + } }