diff --git a/include/maxscale/config.h b/include/maxscale/config.h index aad311a2f..157e695fe 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -369,6 +369,28 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params, const char *key, uint32_t options, uint32_t* output_ovec_size); +/** + * Get multiple compiled regular expressions and the maximum ovector size of + * the patterns. The returned regex codes should be freed by the caller. + * + * @param params List of configuration parameters + * @param keys An array of parameter names. If an element is not found in @c params, + * the corresponding spot in @c out_codes is set to NULL. + * @param keys_size Size of both @c keys and @c out_arr + * @param options PCRE2 compilation options + * @param out_ovec_size If not NULL, the maximum ovector size of successfully + * compiled patterns is written here. + * @param out_codes An array of handles to compiled codes. The referenced pointers + * will be set to point to the compiled codes. The array size must be equal to + * @c keys array size and it must contain valid pointers. + * + * @return True, if all patterns given by @c keys were successfully compiled. + * False otherwise. + */ +bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params, + const char* keys[], int keys_size, + uint32_t options, uint32_t* out_ovec_size, + pcre2_code** out_codes[]); /** * Parse a list of server names and write the results in an array of strings * with one server name in each. The output array and its elements should be diff --git a/include/maxscale/pcre2.h b/include/maxscale/pcre2.h index 0696a3f4c..70be6a8e5 100644 --- a/include/maxscale/pcre2.h +++ b/include/maxscale/pcre2.h @@ -55,4 +55,26 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename, int line_num, const char* func_name); +/** + * Check that @c subject is valid. A valid subject matches @c re_match yet does + * not match @c re_exclude. If an error occurs, an error code is written to + * @c match_error_out. + * + * @param re_match If not NULL, the subject must match this to be valid. If NULL, + * all inputs are considered valid. + * @param re_exclude If not NULL, will invalidate a matching subject. Even subjects + * validated by @c re_match can be invalidated. If NULL, invalidates nothing. + * @param md PCRE2 match data block + * @param subject Subject string. Should NOT be an empty string. + * @param length Length of subject. Can be zero for 0-terminated strings. + * @param calling_module Which module the function was called from. Can be NULL. + * Used for log messages. + * + * @return True, if subject is considered valid. False if subject is not valid or + * an error occurred. + */ +bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude, + pcre2_match_data* md, const char* subject, + int length, const char* calling_module); + MXS_END_DECLS diff --git a/server/core/config.cc b/server/core/config.cc index b191ec263..9682d1794 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -1256,6 +1256,40 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params, return code; } +bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params, + const char* keys[], int keys_size, + uint32_t options, uint32_t* out_ovec_size, + pcre2_code** out_codes[]) +{ + bool rval = true; + uint32_t max_ovec_size = 0; + uint32_t ovec_size_temp = 0; + for (int i = 0; i < keys_size; i++) + { + ss_dassert(out_codes[i]); + *out_codes[i] = config_get_compiled_regex(params, keys[i], options, + &ovec_size_temp); + if (*out_codes[i]) + { + if (ovec_size_temp > max_ovec_size) + { + max_ovec_size = ovec_size_temp; + } + } + /* config_get_compiled_regex() returns null also if the config setting + * didn't exist. Check that before setting error state. */ + else if (*(config_get_value_string(params, keys[i]))) + { + rval = false; + } + } + if (out_ovec_size) + { + *out_ovec_size = max_ovec_size; + } + return rval; +} + MXS_CONFIG_PARAMETER* config_clone_param(const MXS_CONFIG_PARAMETER* param) { MXS_CONFIG_PARAMETER *p2 = (MXS_CONFIG_PARAMETER*)MXS_MALLOC(sizeof(MXS_CONFIG_PARAMETER)); diff --git a/server/core/maxscale_pcre2.cc b/server/core/maxscale_pcre2.cc index b1391e615..ec0b3d640 100644 --- a/server/core/maxscale_pcre2.cc +++ b/server/core/maxscale_pcre2.cc @@ -141,7 +141,6 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename, int line_num, const char* func_name) { - ss_dassert(module_name); ss_dassert(filename); ss_dassert(func_name); @@ -158,3 +157,53 @@ void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *f "message."); } } + +bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude, + pcre2_match_data* md, const char* subject, + int length, const char* calling_module) +{ + ss_dassert((!re_match && !re_exclude) || (md && subject)); + bool rval = true; + int string_len = ((size_t)length == PCRE2_ZERO_TERMINATED) ? strlen(subject) : length; + if (re_match) + { + int result = pcre2_match(re_match, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL); + if (result == PCRE2_ERROR_NOMATCH) + { + rval = false; // Didn't match the "match"-regex + if (mxs_log_priority_is_enabled(LOG_INFO)) + { + mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__, + "Subject does not match the 'match' pattern: %.*s", + string_len, subject); + } + } + else if (result < 0) + { + rval = false; + /* The __FILE__ etc macros here do not match calling_module, but + * the values are only used for throttling messages. */ + mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__); + } + } + if (rval && re_exclude) + { + int result = pcre2_match(re_exclude, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL); + if (result >= 0) + { + rval = false; // Matched the "exclude"-regex + if (mxs_log_priority_is_enabled(LOG_INFO)) + { + mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__, + "Query matches the 'exclude' pattern: %.*s", + string_len, subject); + } + } + else if (result != PCRE2_ERROR_NOMATCH) + { + rval = false; + mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__); + } + } + return rval; +} diff --git a/server/modules/filter/ccrfilter/ccrfilter.c b/server/modules/filter/ccrfilter/ccrfilter.c index 8dee80ade..5cd221689 100644 --- a/server/modules/filter/ccrfilter/ccrfilter.c +++ b/server/modules/filter/ccrfilter/ccrfilter.c @@ -13,16 +13,18 @@ #define MXS_MODULE_NAME "ccrfilter" +#include + #include +#include +#include #include +#include +#include #include #include -#include -#include -#include +#include #include -#include -#include /** * @file ccrfilter.c - a very simple filter designed to send queries to the @@ -81,8 +83,9 @@ typedef struct int count; /*< Number of hints to add after each operation * that modifies data. */ LAGSTATS stats; - regex_t re; /* Compiled regex text of match */ - regex_t nore; /* Compiled regex text of ignore */ + pcre2_code* re; /* Compiled regex text of match */ + pcre2_code* nore; /* Compiled regex text of ignore */ + uint32_t ovector_size; /* PCRE2 match data ovector size */ } CCR_INSTANCE; /** @@ -93,16 +96,20 @@ typedef struct MXS_DOWNSTREAM down; /*< The downstream filter */ int hints_left; /*< Number of hints left to add to queries*/ time_t last_modification; /*< Time of the last data modifying operation */ + pcre2_match_data* md; /*< PCRE2 match data */ } CCR_SESSION; static const MXS_ENUM_VALUE option_values[] = { - {"ignorecase", REG_ICASE}, + {"ignorecase", PCRE2_CASELESS}, {"case", 0}, - {"extended", REG_EXTENDED}, + {"extended", PCRE2_EXTENDED}, {NULL} }; +static const char PARAM_MATCH[] = "match"; +static const char PARAM_IGNORE[] = "ignore"; + typedef enum ccr_hint_value_t { CCR_HINT_NONE, @@ -154,8 +161,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { {"count", MXS_MODULE_PARAM_COUNT, "0"}, {"time", MXS_MODULE_PARAM_COUNT, CCR_DEFAULT_TIME}, - {"match", MXS_MODULE_PARAM_STRING}, - {"ignore", MXS_MODULE_PARAM_STRING}, + {PARAM_MATCH, MXS_MODULE_PARAM_REGEX}, + {PARAM_IGNORE, MXS_MODULE_PARAM_REGEX}, { "options", MXS_MODULE_PARAM_ENUM, @@ -192,23 +199,26 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) my_instance->stats.n_add_count = 0; my_instance->stats.n_add_time = 0; my_instance->stats.n_modified = 0; + my_instance->ovector_size = 0; + my_instance->re = NULL; + my_instance->nore = NULL; int cflags = config_get_enum(params, "options", option_values); + my_instance->match = config_copy_string(params, PARAM_MATCH); + my_instance->nomatch = config_copy_string(params, PARAM_IGNORE); + const char* keys[] = {PARAM_MATCH, PARAM_IGNORE}; + pcre2_code** code_arr[] = {&my_instance->re, &my_instance->nore}; - if ((my_instance->match = config_copy_string(params, "match"))) + if (!config_get_compiled_regexes(params, keys, sizeof(keys)/sizeof(char*), + cflags, &my_instance->ovector_size, + code_arr)) { - if (regcomp(&my_instance->re, my_instance->match, cflags)) - { - MXS_ERROR("Failed to compile regex '%s'.", my_instance->match); - } - } - - if ((my_instance->nomatch = config_copy_string(params, "ignore"))) - { - if (regcomp(&my_instance->nore, my_instance->nomatch, cflags)) - { - MXS_ERROR("Failed to compile regex '%s'.", my_instance->nomatch); - } + MXS_FREE(my_instance->match); + MXS_FREE(my_instance->nomatch); + pcre2_code_free(my_instance->re); + pcre2_code_free(my_instance->nore); + MXS_FREE(my_instance); + my_instance = NULL; } } @@ -226,12 +236,23 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) static MXS_FILTER_SESSION * newSession(MXS_FILTER *instance, MXS_SESSION *session) { + CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance; CCR_SESSION *my_session = MXS_MALLOC(sizeof(CCR_SESSION)); if (my_session) { + bool error = false; my_session->hints_left = 0; my_session->last_modification = 0; + if (my_instance->ovector_size) + { + my_session->md = pcre2_match_data_create(my_instance->ovector_size, NULL); + if (!my_session->md) + { + MXS_FREE(my_session); + my_session = NULL; + } + } } return (MXS_FILTER_SESSION*)my_session; @@ -296,7 +317,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance; CCR_SESSION *my_session = (CCR_SESSION *)session; char *sql; - regmatch_t limits[] = {{0, 0}}; + int length; time_t now = time(NULL); if (modutil_is_SQL(queue)) @@ -307,7 +328,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) */ if (qc_query_is_type(qc_get_type_mask(queue), QUERY_TYPE_WRITE)) { - if (modutil_extract_SQL(queue, &sql, &limits[0].rm_eo)) + if (modutil_extract_SQL(queue, &sql, &length)) { bool trigger_ccr = true; bool decided = false; // Set by hints to take precedence. @@ -323,18 +344,10 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) } if (!decided) { - if (my_instance->nomatch && - regexec(&my_instance->nore, sql, 0, limits, 0) == 0) - { - // Nomatch was present and sql matched it. - trigger_ccr = false; - } - else if (my_instance->match && - (regexec(&my_instance->re, sql, 0, limits, 0) != 0)) - { - // Match was present but sql did *not* match it. - trigger_ccr = false; - } + trigger_ccr = + mxs_pcre2_check_match_exclude(my_instance->re, my_instance->nore, + my_session->md, sql, length, + MXS_MODULE_NAME); } if (trigger_ccr) { @@ -435,7 +448,7 @@ static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESS if (my_instance->match) { - json_object_set_new(rval, "match", json_string(my_instance->match)); + json_object_set_new(rval, PARAM_MATCH, json_string(my_instance->match)); } if (my_instance->nomatch) diff --git a/server/modules/filter/qlafilter/qlafilter.cc b/server/modules/filter/qlafilter/qlafilter.cc index d89cf0ec3..3ebe17f9a 100644 --- a/server/modules/filter/qlafilter/qlafilter.cc +++ b/server/modules/filter/qlafilter/qlafilter.cc @@ -101,8 +101,6 @@ typedef struct /* Avoid repeatedly printing some errors/warnings. */ bool write_warning_given; - bool match_error_printed; - bool exclude_error_printed; } QLA_INSTANCE; /* The session structure for this QLA filter. */ @@ -122,8 +120,6 @@ typedef struct static FILE* open_log_file(uint32_t, QLA_INSTANCE *, const char *); static int write_log_entry(uint32_t, FILE*, QLA_INSTANCE*, QLA_SESSION*, const char*, const char*, size_t); -static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session, - const char* ptr, int length); static const MXS_ENUM_VALUE option_values[] = { @@ -283,8 +279,6 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) my_instance->ovec_size = 0; my_instance->unified_fp = NULL; my_instance->write_warning_given = false; - my_instance->match_error_printed = false; - my_instance->exclude_error_printed = false; my_instance->source = config_copy_string(params, PARAM_SOURCE); my_instance->user_name = config_copy_string(params, PARAM_USER); @@ -302,29 +296,13 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) bool error = false; int cflags = config_get_enum(params, PARAM_OPTIONS, option_values); - if (my_instance->match) - { - my_instance->re_match = - config_get_compiled_regex(params, PARAM_MATCH, cflags, &my_instance->ovec_size); - if (!my_instance->re_match) - { - error = true; - } - } - if (my_instance->exclude) + const char* keys[] = {PARAM_MATCH, PARAM_EXCLUDE}; + pcre2_code** code_arr[] = {&my_instance->re_match, &my_instance->re_exclude}; + if (!config_get_compiled_regexes(params, keys, sizeof(keys) / sizeof(char*), + cflags, &my_instance->ovec_size, code_arr)) { - uint32_t ovec_size_temp = 0; - my_instance->re_exclude = - config_get_compiled_regex(params, PARAM_EXCLUDE, cflags, &ovec_size_temp); - if (ovec_size_temp > my_instance->ovec_size) - { - my_instance->ovec_size = ovec_size_temp; - } - if (!my_instance->re_exclude) - { - error = true; - } + error = true; } // Try to open the unified log file @@ -531,7 +509,8 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) if (my_session->active && modutil_extract_SQL(queue, &ptr, &length) && - regex_check(my_instance, my_session, ptr, length)) + mxs_pcre2_check_match_exclude(my_instance->re_match, my_instance->re_exclude, + my_session->match_data, ptr, length, MXS_MODULE_NAME)) { char buffer[QLA_DATE_BUFFER_SIZE]; gettimeofday(&tv, NULL); @@ -924,46 +903,3 @@ static int write_log_entry(uint32_t data_flags, FILE *logfile, QLA_INSTANCE *ins return rval; } } - -static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session, - const char* ptr, int length) -{ - bool rval = true; - if (my_instance->re_match) - { - int result = pcre2_match(my_instance->re_match, (PCRE2_SPTR)ptr, - length, 0, 0, my_session->match_data, NULL); - if (result == PCRE2_ERROR_NOMATCH) - { - rval = false; // Didn't match the "match"-regex - } - else if (result < 0) - { - rval = false; - if (!my_instance->match_error_printed) - { - MXS_PCRE2_PRINT_ERROR(result); - my_instance->match_error_printed = true; - } - } - } - if (rval && my_instance->re_exclude) - { - int result = pcre2_match(my_instance->re_exclude, (PCRE2_SPTR)ptr, - length, 0, 0, my_session->match_data, NULL); - if (result >= 0) - { - rval = false; // Matched the "exclude"-regex - } - else if (result != PCRE2_ERROR_NOMATCH) - { - rval = false; - if (!my_instance->exclude_error_printed) - { - MXS_PCRE2_PRINT_ERROR(result); - my_instance->exclude_error_printed = true; - } - } - } - return rval; -}