/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/

#include "modsecurity.h"
#include "re.h"
#include "msc_pcre.h"
#include "msc_geo.h"
#include "msc_gsb.h"
#include "apr_lib.h"
#include "apr_strmatch.h"
#include "acmp.h"
#include "msc_util.h"
#include "msc_tree.h"
#include "msc_crypt.h"
#include "msc_remote_rules.h"
#include "msc_status_engine.h"
#include <apr_sha1.h>

#if APR_HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#ifdef WITH_SSDEEP
#include "fuzzy.h"
#endif 

#include "others/libinjection/src/libinjection.h"
#include "others/libinjection/src/libinjection_error.h"

#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
#ifndef WITH_PCRE
/**
 * @brief Set the JIT compile return code and JIT compile status.
 * \param regex regex structure
 * \param rc return code of the JIT compile
 * \param jit JIT compile status
 * \return void
 */
static void msc_op_set_jitrc(msc_regex_t *regex, int *rc, int *jit) {
    *rc = regex->jit_compile_rc;
    if (*rc == 0) {
        msc_fullinfo(regex, PCRE2_INFO_JITSIZE, jit);
        *jit = (*jit > 0) ? 1 : 0;
    }
    return;
}
#endif
#endif
#endif

/**
 *
 */
void msre_engine_op_register(msre_engine *engine, const char *name,
    fn_op_param_init_t fn1, fn_op_execute_t fn2)
{
    assert(engine != NULL);
    assert(name != NULL);
    msre_op_metadata *metadata = (msre_op_metadata *)apr_pcalloc(engine->mp,
        sizeof(msre_op_metadata));
    if (metadata == NULL) return;

    metadata->name = name;
    metadata->param_init = fn1;
    metadata->execute = fn2;
    apr_table_setn(engine->operators, name, (void *)metadata);
}

/**
 *
 */
msre_op_metadata *msre_engine_op_resolve(msre_engine *engine, const char *name) {
    assert(engine != NULL);
    return (msre_op_metadata *)apr_table_get(engine->operators, name);
}



/* -- Operators -- */

/* unconditionalMatch */

static int msre_op_unconditionalmatch_execute(modsec_rec *msr, msre_rule *rule,
    msre_var *var, char **error_msg)
{
    assert(error_msg != NULL);
    *error_msg = "Unconditional match in SecAction.";

    /* Always match. */
    return 1;
}

/* noMatch */

static int msre_op_nomatch_execute(modsec_rec *msr, msre_rule *rule,
    msre_var *var, char **error_msg)
{
    assert(error_msg != NULL);
    *error_msg = "No match.";

    /* Never match. */
    return 0;
}

/* ipmatch */

/**
* \brief Init function to ipmatch operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 1 On Success
* \retval 0 On Fail
*/
static int msre_op_ipmatch_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(error_msg != NULL);
    // Normally useless code, left to be safe for the moment
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_ipmatch_param_init: error_msg is NULL");
        return -1;
    }
    char *param = NULL;
    int res = 0;

    *error_msg = NULL;

    param = apr_pstrdup(rule->ruleset->mp, rule->op_param);

    res = ip_tree_from_param(rule->ruleset->mp, param, &rule->ip_op,
        error_msg);

    if (res)
        return 0;

    return 1;
}

/**
* \brief Execution function to ipmatch operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_ipmatch_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    TreeRoot *rtree = NULL;
    int res = 0;

    *error_msg = NULL;

    if (rule == NULL || rule->ip_op == NULL) {
        msr_log(msr, 1, "ipMatch Internal Error: ipmatch value is null.");
        return 0;
    }

    rtree = rule->ip_op;

    res = tree_contains_ip(msr->mp, rtree, var->value, NULL, error_msg);

    if (res < 0) {
        msr_log(msr, 1, "%s", *error_msg);
        *error_msg = NULL;
    }
    
    if (res > 0) {
        *error_msg = apr_psprintf(msr->mp, "IPmatch: \"%s\" matched at %s.", var->value, var->name);
    }

    return res;
}


/**
* \brief Init function to ipmatchFromFile operator
*
* \param rule Pointer to the rule
* \param error_msg Pointer to error msg
*
* \retval 1 On Success
* \retval 0 On Fail
*/
static int msre_op_ipmatchFromFile_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(error_msg != NULL);
    const char *rootpath = NULL;
    const char *filepath = NULL;
    const char *ipfile_path = NULL;
    char *fn = NULL;
    int res = 0;
    TreeRoot *rtree = NULL;

    if ((rule->op_param == NULL) || (strlen(rule->op_param) == 0))
    {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for " \
            "operator 'ipmatchFromFile'.");
        return 0;
    }

    fn = apr_pstrdup(rule->ruleset->mp, rule->op_param);
    while ((apr_isspace(*fn) != 0) && (*fn != '\0'))
    {
        fn++;
    }
    if (*fn == '\0') {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Empty file specification " \
            "for operator ipmatchFromFile \"%s\"", fn);
        return 0;
    }
    filepath = fn;

    if (strlen(fn) > strlen("http://") &&
            strncmp(fn, "http://", strlen("http://")) == 0)
    {
        *error_msg = apr_psprintf(rule->ruleset->mp, "HTTPS address or file " \
            "path are expected for operator ipmatchFromFile \"%s\"", fn);
        return 0;
    }
    else if (strlen(fn) > strlen("https://") &&
            strncmp(fn, "https://", strlen("https://")) == 0)
    {
#ifdef WITH_CURL
        res = ip_tree_from_uri(&rtree, fn, rule->ruleset->mp, error_msg);
        if (res == -2)
        {
            /* Failed to download but we won't stop the webserver to start. */
            return 1;
        }
        else if (res)
        {
            return 0;
        }
#else
        *error_msg = apr_psprintf(rule->ruleset->mp, "ModSecurity was not " \
            "compiled with Curl support, it cannot load: \"%s\"", fn);
        return 0;
#endif
    }
    else
    {
        ipfile_path = apr_pstrndup(rule->ruleset->mp, rule->filename,
            strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename)));
        if (apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME,
            rule->ruleset->mp) != APR_SUCCESS) {
            apr_filepath_merge(&fn, ipfile_path, fn, APR_FILEPATH_TRUENAME, rule->ruleset->mp);
        }

        res = ip_tree_from_file(&rtree, fn, rule->ruleset->mp, error_msg);
        if (res)
        {
            return 0;
        }
    }

    rule->op_param_data = rtree;
    return 1;
}

/**
* \brief Execution function to ipmatchFromFile operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_ipmatchFromFile_execute(modsec_rec *msr, msre_rule *rule,
    msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    TreeRoot *rtree = (TreeRoot *)rule->op_param_data;
    int res = 0;

    *error_msg = NULL;

    if (rtree == NULL)
    {
        if (msr->txcfg->debuglog_level >= 6)
        {
            msr_log(msr, 1, "ipMatchFromFile: tree value is null.");
        }
        return 0;
    }

    if (msr->txcfg->debuglog_level >= 4) {
        msr_log(msr, 4, "IPmatchFromFile: Total tree entries: %d, ipv4 %d " \
            "ipv6 %d", rtree->ipv4_tree->count+rtree->ipv6_tree->count,
            rtree->ipv4_tree->count, rtree->ipv6_tree->count);
    }

    res = tree_contains_ip(msr->mp, rtree, var->value, msr,
        error_msg);

    if (res < 0)
        msr_log(msr, 9, "%s", *error_msg);

    if (res > 0)
        *error_msg = apr_psprintf(msr->mp, "IPmatchFromFile: \"%s\" matched at " \
        "%s.", var->value, var->name);

    return res;
}

/* rsub */

static char *param_remove_escape(msre_rule *rule, char *str, int len)  {
    assert(rule != NULL);
    assert(str != NULL);
    char *parm = apr_pcalloc(rule->ruleset->mp, len);
    char *ret = parm;

    for(;*str!='\0';str++)    {
        if(*str != '\\')    {
            *parm++ = *str;
        } else {
            str++;
            if(*str != '/') {
                str--;
                *parm++ = *str;
            } else {
                *parm++ = *str;
            }
        }
    }

    *parm = '\0';
    return ret;
}

/**
 * \brief Init function to rsub operator
 *
 * \param rule Pointer to the rule
 * \param error_msg Pointer to error msg
 *
 * \retval 1 On Success
 * \retval 0 On Fail
 */
#if !defined(MSC_TEST)
static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    // Normally useless code, left to be safe for the moment
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_rsub_param_init: error_msg is NULL");
        return -1;
    }
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
    ap_regex_t *regex;
#else
    regex_t     *regex;
#endif
    const char *pattern = NULL;
    const char *line = NULL;
    char *reg_pattern = NULL;
    char *replace = NULL;
    char *e_pattern = NULL;
    char *parsed_replace = NULL;
    char *flags = NULL;
    char *data = NULL;
    char delim;
    int ignore_case = 0;
    unsigned short int op_len = 0;

    *error_msg = NULL;

    line = rule->op_param;

    if (apr_tolower(*line) != 's') {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator format, must be s/ pattern");
        return -1;
    }

    data = apr_pstrdup(rule->ruleset->mp, line);
    delim = *++data;
    if (delim)
        reg_pattern = ++data;
    if (reg_pattern) {

        if (*data != delim) {
            for(;*data != '\0' ;data++)  {
                if(*data == delim)   {
                    data--;
                    if(*data == '\\')   {
                        data++;
                        continue;
                    }
                    break;
                }
            }
        }

        if (*data) {
            *++data = '\0';
            ++data;
            replace = data;
        }

    }

    if (replace) {

        if (*data != delim) {
            for(;*data != '\0' ;data++)  {
                if(*data == delim)   {
                    data--;
                    if(*data == '\\')   {
                        data++;
                        continue;
                    }
                    break;
                }
            }
        }

        if (*data) {
            *++data = '\0';
            flags = ++data;
        }

    }

    if (!delim || !reg_pattern || !replace) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator format - must be s/regex/str/[flags]");
        return -1;
    }

    op_len = strlen(replace);
    parsed_replace = apr_pstrdup(rule->ruleset->mp, parse_pm_content(param_remove_escape(rule, replace, strlen(replace)),
        op_len, rule, error_msg));

    if(!parsed_replace) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator parsing input data");
        return -1;
    }

    rule->sub_str = apr_pstrmemdup(rule->ruleset->mp, parsed_replace, strlen(parsed_replace));

    if (flags) {
        while (*flags) {
            delim = apr_tolower(*flags);
            if (delim == 'i')
                ignore_case = 1;
            else if (delim == 'd')
                rule->escape_re = 1;
            else
                *error_msg = apr_psprintf(rule->ruleset->mp, "Regex flag not supported");
            flags++;
        }
    }

    e_pattern = param_remove_escape(rule, reg_pattern, strlen(reg_pattern));
    pattern = apr_pstrndup(rule->ruleset->mp, e_pattern, strlen(e_pattern));

    if(strstr(pattern,"%{") == NULL)    {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
        regex = ap_pregcomp(rule->ruleset->mp, pattern, AP_REG_EXTENDED |
                (ignore_case ? AP_REG_ICASE : 0));
#else
        regex = ap_pregcomp(rule->ruleset->mp, pattern, REG_EXTENDED |
                (ignore_case ? REG_ICASE : 0));
#endif
        rule->sub_regex = regex;
    } else {
        rule->re_precomp = 1;
        rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
        rule->sub_regex = NULL;
    }

    return 1; /* OK */
}

/**
* \brief Execution function to rsub operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (!str) {
        msr_log(msr, 1, "rsub: Memory allocation error");
        return -1;
    }
    msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (!re_pattern) {
        msr_log(msr, 1, "rsub: Memory allocation error");
        return -1;
    }
    char *offset = NULL;
    char *data = NULL, *pattern = NULL;
    char *data_out = NULL;
    unsigned int size = 0;
    unsigned int maxsize=0;
    int output_body = 0, input_body = 0, sl;
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
    ap_regmatch_t pmatch[AP_MAX_REG_MATCH];
#else
    regmatch_t  pmatch[AP_MAX_REG_MATCH];
#endif

    *error_msg = NULL;

    if(strcmp(var->name,"STREAM_OUTPUT_BODY") == 0 )  {
        output_body = 1;
    } else if(strcmp(var->name,"STREAM_INPUT_BODY") == 0 )  {
        input_body = 1;
    } else  {
        msr_log(msr,9,"Operator rsub only works with STREAM_* variables");
        return -1;
    }
    if(rule->re_precomp == 1)    {
        re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
        re_pattern->value_len = strlen(re_pattern->value);

        expand_macros(msr, re_pattern, rule, msr->mp);

        if(strlen(re_pattern->value) > 0)   {
            if(rule->escape_re == 1)    {
                pattern = log_escape_re(msr->mp, re_pattern->value);
                if (msr->txcfg->debuglog_level >= 6) {
                    msr_log(msr, 6, "Escaping pattern [%s]",pattern);
                }
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
                rule->sub_regex = ap_pregcomp(msr->mp, pattern, AP_REG_EXTENDED);
#else
                rule->sub_regex = ap_pregcomp(msr->mp, pattern, REG_EXTENDED);
#endif
            } else  {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
                rule->sub_regex = ap_pregcomp(msr->mp, re_pattern->value, AP_REG_EXTENDED);
#else
                rule->sub_regex = ap_pregcomp(msr->mp, re_pattern->value, REG_EXTENDED);
#endif
            }
        } else    {
            rule->sub_regex = NULL;
        }

    }

    if(rule->sub_regex == NULL)   {
        *error_msg = "Internal Error: regex data is null.";
        return -1;
    }

    str->value = apr_pstrndup(msr->mp, rule->sub_str, strlen(rule->sub_str));
    str->value_len = strlen(str->value);

    if(strstr(rule->sub_str,"%{") != NULL)
        expand_macros(msr, str, rule, msr->mp);

    maxsize=var->value_len+(AP_MAX_REG_MATCH*1024)+1;

nextround:

    data = apr_pcalloc(msr->mp, maxsize+1);

    if(data == NULL) {
        *error_msg = "Internal Error: cannot allocate memory";
        return -1;
    }

    data_out=data;
    size=0;
    for (offset = (char*)var->value; !ap_regexec(rule->sub_regex,  offset, AP_MAX_REG_MATCH, pmatch, 0); )  {
        //Copy of data before the regex match
        int i;
        int s = pmatch [0].rm_so;
        int p_len=pmatch [0].rm_eo - pmatch [0].rm_so;
        if (size+s>maxsize) {
            maxsize*=2;
            goto nextround;
        }
        memcpy(data_out,offset,s);
        data_out+=s;
        size+=s;

        //Copy of regex match with replacing data \1..\9
        for(i=0;i<str->value_len;)  {
            char *x = str->value+i;
            if (*x == '\\' && *(x + 1) > '0' && *(x + 1) <= '9') {
                int capture=*(x + 1) - 48;
                int capture_len=pmatch[capture].rm_eo-pmatch[capture].rm_so;

                if (size+capture_len>maxsize)
                {
                    maxsize*=2;
                    goto nextround;
                }
                memcpy(data_out,offset+pmatch[capture].rm_so,capture_len);
                data_out+= capture_len;
                size+=capture_len;
                i+=2;
            } else {

                if (size+1>maxsize) {
                    maxsize*=2;
                    goto nextround;
                }
                *data_out=*(str->value+i);
                data_out++;
                size++;
                i++;
            }
        }
        offset+=s;
        offset+=p_len;
    }

    //Copy of data after the last regex match
    sl = strlen(offset);
    if (size+sl>maxsize) {
        maxsize*=2;
        goto nextround;
    }
    memcpy(data_out,offset,sl);
    data_out+=sl;
    size+=sl;
    *data_out=0;

    if (msr->stream_output_data != NULL && output_body == 1) {
        free(msr->stream_output_data);
        msr->stream_output_data = NULL;
        msr->stream_output_length = 0;
        msr->stream_output_data = (char *)malloc(size+1);
        if (msr->stream_output_data == NULL) return -1;

        msr->stream_output_length = size;
        msr->of_stream_changed = 1;
        memcpy(msr->stream_output_data, data, size);
        msr->stream_output_data[size] = '\0';

        var->value_len = size;
        var->value = msr->stream_output_data;
    }

    if (msr->stream_input_data != NULL && input_body == 1) {
        free(msr->stream_input_data);
        msr->stream_input_data = NULL;
        msr->stream_input_length = 0;
#ifdef MSC_LARGE_STREAM_INPUT
        msr->stream_input_allocated_length  = 0;
#endif
        msr->stream_input_data = (char *)malloc(size+1);
        if(msr->stream_input_data == NULL) return -1;

        msr->stream_input_length = size;
#ifdef MSC_LARGE_STREAM_INPUT
        msr->stream_input_allocated_length = size;
#endif
        msr->if_stream_changed = 1;

        memcpy(msr->stream_input_data, data, size);
        msr->stream_input_data[size] = '\0';

        var->value_len = size;
        var->value = msr->stream_input_data;
    }

    if (! *error_msg) {
        *error_msg = apr_psprintf(msr->mp, "Operator rsub succeeded.");
    }

    return 1;
}
#endif /* MSC_TEST */

/**
 * \brief Init function to validateHash
 *
 * \param rule ModSecurity rule struct
 * \param error_msg Error message
 *
 * \retval 1 On success
 * \retval 0 On fail
 */
static int msre_op_validateHash_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(error_msg != NULL);
    const char *errptr = NULL;
    int erroffset;
    msc_regex_t *regex;
    const char *pattern = rule->op_param;
    #ifdef WITH_PCRE_STUDY
        #ifdef WITH_PCRE_JIT
    int rc, jit = 0;
        #endif
    #endif

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    /* Compile pattern */
    if(strstr(pattern,"%{") == NULL)    {
#ifndef WITH_PCRE
        int options = PCRE2_DOTALL | PCRE2_DOLLAR_ENDONLY;
#else
        int options = PCRE_DOTALL | PCRE_DOLLAR_ENDONLY;
#endif
        regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
        if (regex == NULL) {
            *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
                    erroffset, errptr);
            return 0;
        }

        #ifdef WITH_PCRE_STUDY
            #ifdef WITH_PCRE_JIT
                #ifndef WITH_PCRE
        msc_op_set_jitrc(regex, &rc, &jit);
                #else
        rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
                #endif
        if ((rc != 0) || (jit != 1)) {
            *error_msg = apr_psprintf(rule->ruleset->mp,
                    "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                    "Execution error - "
                    "Does not support JIT (%d)",
                    rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                            (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                    rule->filename != NULL ? rule->filename : "-",
                    rule->line_num,rc);
        }
            #endif
        #endif

        rule->op_param_data = regex;
    } else {
        rule->re_precomp = 1;
        rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
        rule->op_param_data = NULL;
    }

    return 1; /* OK */
}

/**
 * \brief Execute function to validateHash
 *
 * \param msr ModSecurity transaction resource
 * \param rule ModSecurity rule struct
 * \param var ModSecurity variable struct
 * \param error_msg Error message
 *
 * \retval 1 On success
 * \retval 0 On fail
 */
static int msre_op_validateHash_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (!re_pattern) {
        msr_log(msr, 1, "validateHash: Memory allocation error");
        return -1;
    }
    const char *target;
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    unsigned int target_length;
    char *my_error_msg = NULL;
    int ovector[33];
    int rc;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int jit = 0;
       #endif
    #endif

    *error_msg = NULL;

    if (msr->txcfg->hash_enforcement == HASH_DISABLED || msr->txcfg->hash_is_enabled == HASH_DISABLED)
        return 0;

    if (regex == NULL) {
        if(rule->re_precomp == 0)   {
            *error_msg = "Internal Error: regex data is null.";
            return -1;
        } else  {

            if(re_pattern == NULL)  {
                *error_msg = "Internal Error: regex variable data is null.";
                return -1;
            }

            re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
            re_pattern->value_len = strlen(re_pattern->value);

            expand_macros(msr, re_pattern, rule, msr->mp);

            const char *pattern = log_escape_re(msr->mp, re_pattern->value);
            if (msr->txcfg->debuglog_level >= 6) {
                msr_log(msr, 6, "Escaping pattern [%s]",pattern);
            }

#ifndef WITH_PCRE
            options = PCRE2_DOTALL | PCRE2_DOLLAR_ENDONLY;
#else
            options = PCRE_DOTALL | PCRE_DOLLAR_ENDONLY;
#endif
            regex = msc_pregcomp_ex(msr->mp, pattern, options, &errptr,
                    &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
            if (regex == NULL) {
                *error_msg = apr_psprintf(msr->mp, "Error compiling pattern (offset %d): %s",
                        erroffset, errptr);
                return 0;
            }

            #ifdef WITH_PCRE_STUDY
                #ifdef WITH_PCRE_JIT
            if (msr->txcfg->debuglog_level >= 4) {
                    #ifndef WITH_PCRE
                msc_op_set_jitrc(regex, &rc, &jit);
                    #else
                rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
                    #endif
                if ((rc != 0) || (jit != 1)) {
                    *error_msg = apr_psprintf(msr->mp,
                            "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                            "Execution error - "
                            "Does not support JIT (%d)",
                            rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                                    (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                            rule->filename != NULL ? rule->filename : "-",
                            rule->line_num,rc);
                    msr_log(msr, 4, "%s.", *error_msg);
                }
            }
                #endif
            #endif
        }
    }

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* We always use capture so that ovector can be used as working space
     * and no memory has to be allocated for any backreferences.
     */
    rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
    if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
    if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
        msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

        if (s == NULL) return -1;
        s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
        if (s->name == NULL) return -1;
        s->name_len = strlen(s->name);
        s->value = apr_pstrdup(msr->mp, "1");
        if (s->value == NULL) return -1;
        s->value_len = 1;
        apr_table_setn(msr->tx_vars, s->name, (void *)s);

        *error_msg = apr_psprintf(msr->mp,
                "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                "Execution error - "
                "PCRE limits exceeded (%d): %s",
                rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
                rule->filename != NULL ? rule->filename : "-",
                rule->line_num,rc, my_error_msg);

        msr_log(msr, 3, "%s.", *error_msg);

        return 0; /* No match. */
    }
    else if (rc < -1) {
        *error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                rc, my_error_msg);
        return -1;
    }

#ifndef WITH_PCRE
    if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
    if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
        /* We no longer escape the pattern here as it is done when logging */
        char *pattern = apr_pstrdup(msr->mp, log_escape(msr->mp, regex->pattern ? regex->pattern : "<Unknown Match>"));
        char *hmac = NULL, *valid = NULL;
        char *hash_link = NULL, *nlink = NULL;

        if (strlen(pattern) > 252) {
            *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s.",
                    pattern, var->name);
        } else {
            *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s.",
                    pattern, var->name);
        }

        valid = strstr(target, msr->txcfg->crypto_param_name);

        if(valid == NULL)   {

            if (msr->txcfg->debuglog_level >= 9)
                msr_log(msr, 9, "Request URI without hash parameter [%s]", target);

            if (strlen(pattern) > 252) {
                *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. No Hash parameter",
                        pattern, var->name);
            } else {
                *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. No Hash parameter",
                        pattern, var->name);
            }
            return 1;
        }   else    {

            if(strlen(valid) < strlen(msr->txcfg->crypto_param_name)+1)
                return 1;

            hmac  = valid+strlen(msr->txcfg->crypto_param_name)+1;

            nlink = apr_pstrmemdup(msr->mp, target, strlen(target) - strlen(valid) - 1);

            msr_log(msr, 9, "Validating URI %s size %zu",nlink,strlen(nlink));

            hash_link = do_hash_link(msr, (char *)nlink, HASH_ONLY);

            if(strcmp(hmac, hash_link) != 0)    {

                if (strlen(pattern) > 252) {
                    *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. Hash parameter hash value = [%s] Requested URI hash value = [%s]",
                            pattern, var->name, hmac, hash_link);
                } else {
                    *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. Hash parameter hash value = [%s] Requested URI hash value = [%s]",
                            pattern, var->name, hmac, hash_link);
                }
                return 1;
            }
        }

        return 0;
    }

    return 0;
}

/* rx */

static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(error_msg != NULL);
    const char *errptr = NULL;
    int erroffset;
    msc_regex_t *regex;
    const char *pattern = rule->op_param;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int rc, jit = 0;
       #endif
    #endif

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    /* Compile pattern */
    if(strstr(pattern,"%{") == NULL)    {
#ifndef WITH_PCRE
        int options = PCRE2_DOTALL | PCRE2_DOLLAR_ENDONLY;
#else
        int options = PCRE_DOTALL | PCRE_DOLLAR_ENDONLY;
#endif
        regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
        if (regex == NULL) {
            *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
                    erroffset, errptr);
            return 0;
        }

        #ifdef WITH_PCRE_STUDY
            #ifdef WITH_PCRE_JIT
                #ifndef WITH_PCRE
        msc_op_set_jitrc(regex, &rc, &jit);
                #else
        rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
                #endif
        if ((rc != 0) || (jit != 1)) {
            *error_msg = apr_psprintf(rule->ruleset->mp,
                    "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                    "Execution error - "
                    "Does not support JIT (%d)",
                    rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                            (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                    rule->filename != NULL ? rule->filename : "-",
                    rule->line_num,rc);
        }
            #endif
        #endif

        rule->op_param_data = regex;
    } else {
        rule->re_precomp = 1;
        rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
        rule->op_param_data = NULL;
    }

    return 1; /* OK */
}

static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (!re_pattern) {
        msr_log(msr, 1, "rx: Memory allocation error");
        return -1;
    }
    const char *target;
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    unsigned int target_length;
    char *my_error_msg = NULL;
    int ovector[33];
    int capture = 0;
    int matched_bytes = 0;
    int matched = 0;
    int rc;
    char *qspos = NULL;
    const char *parm = NULL;
    msc_parm *mparm = NULL;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int jit = 0;
       #endif
    #endif

    *error_msg = NULL;

    if (regex == NULL) {
        if(rule->re_precomp == 0)   {
            *error_msg = "Internal Error: regex data is null.";
            return -1;
        } else  {

            if(re_pattern == NULL)  {
                *error_msg = "Internal Error: regex variable data is null.";
                return -1;
            }

            re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
            re_pattern->value_len = strlen(re_pattern->value);

            expand_macros(msr, re_pattern, rule, msr->mp);

            if (msr->txcfg->debuglog_level >= 6) {
                char *pattern = log_escape_re(msr->mp, re_pattern->value);
                msr_log(msr, 6, "Expanded-macro pattern [%s]",pattern);
            }

#ifndef WITH_PCRE
        options = PCRE2_DOTALL | PCRE2_DOLLAR_ENDONLY;
#else
        options = PCRE_DOTALL | PCRE_DOLLAR_ENDONLY;
#endif
            regex = msc_pregcomp_ex(msr->mp, re_pattern->value, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
            if (regex == NULL) {
                *error_msg = apr_psprintf(msr->mp, "Error compiling pattern (offset %d): %s",
                        erroffset, errptr);
                return 0;
            }

            #ifdef WITH_PCRE_STUDY
                #ifdef WITH_PCRE_JIT
            if (msr->txcfg->debuglog_level >= 4) {
                    #ifndef WITH_PCRE
                msc_op_set_jitrc(regex, &rc, &jit);
                    #else
                rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
                    #endif
                if ((rc != 0) || (jit != 1)) {
                    *error_msg = apr_psprintf(msr->mp,
                            "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                            "Execution error - "
                            "Does not support JIT (%d)",
                            rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                                    (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                            rule->filename != NULL ? rule->filename : "-",
                            rule->line_num,rc);
                    msr_log(msr, 4, "%s.", *error_msg);
                }
            }
                #endif
            #endif


        }
    }

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    if (rule->actionset->actions) {
        /* Are we supposed to capture subexpressions? */
        capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
        matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
        if (!matched_bytes)
            matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;

        matched = apr_table_get(rule->actionset->actions, "sanitizeMatched") ? 1 : 0;
        if (!matched)
            matched = apr_table_get(rule->actionset->actions, "sanitiseMatched") ? 1 : 0;
    }
    else capture = 0;

    /* Show when the regex captures but "capture" is not set */
    if (msr->txcfg->debuglog_level >= 6) {
        int capcount = 0;
#ifndef WITH_PCRE
        rc = msc_fullinfo(regex, PCRE2_INFO_CAPTURECOUNT, &capcount);
#else
        rc = msc_fullinfo(regex, PCRE_INFO_CAPTURECOUNT, &capcount);
#endif
        if (msr->txcfg->debuglog_level >= 6) {
            if ((capture == 0) && (capcount > 0)) {
                msr_log(msr, 6, "Ignoring regex captures since \"capture\" action is not enabled.");
            }
        }
    }

    /* We always use capture so that ovector can be used as working space
     * and no memory has to be allocated for any backreferences.
     */
    rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
    if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
    if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
        msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

        if (s == NULL) return -1;
        s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
        if (s->name == NULL) return -1;
        s->name_len = strlen(s->name);
        s->value = apr_pstrdup(msr->mp, "1");
        if (s->value == NULL) return -1;
        s->value_len = 1;
        apr_table_setn(msr->tx_vars, s->name, (void *)s);

        *error_msg = apr_psprintf(msr->mp,
                "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                "Execution error - "
                "PCRE limits exceeded (%d): %s",
                rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
                rule->filename != NULL ? rule->filename : "-",
                rule->line_num,rc, my_error_msg);

        msr_log(msr, 3, "%s.", *error_msg);

        return 0; /* No match. */
    }
    else if (rc < -1) {
        *error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                rc, my_error_msg);
        return -1;
    }

    /* Handle captured subexpressions. */
    if (capture && rc > 0) {
        int i;

        /* Unset any of the previously set capture variables. */
        apr_table_unset(msr->tx_vars, "0");
        apr_table_unset(msr->tx_vars, "1");
        apr_table_unset(msr->tx_vars, "2");
        apr_table_unset(msr->tx_vars, "3");
        apr_table_unset(msr->tx_vars, "4");
        apr_table_unset(msr->tx_vars, "5");
        apr_table_unset(msr->tx_vars, "6");
        apr_table_unset(msr->tx_vars, "7");
        apr_table_unset(msr->tx_vars, "8");
        apr_table_unset(msr->tx_vars, "9");

        /* Use the available captures. */
        for(i = 0; i < rc; i++) {
            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
            if (s == NULL) return -1;
            s->name = apr_psprintf(msr->mp, "%d", i);
            if (s->name == NULL) return -1;
            s->name_len = strlen(s->name);
            s->value = apr_pstrmemdup(msr->mp,
                    target + ovector[2 * i], ovector[2 * i + 1] - ovector[2 * i]);
            if (s->value == NULL) return -1;

            s->value_len = (ovector[2 * i + 1] - ovector[2 * i]);

            apr_table_addn(msr->tx_vars, s->name, (void *)s);

            if(((matched == 1) || (matched_bytes == 1)) && (var != NULL) && (var->name != NULL))    {
                qspos = apr_psprintf(msr->mp, "%s", var->name);
                parm = strstr(qspos, ":");
                if (parm != NULL)   {
                    parm++;
                    mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                    if (mparm == NULL)
                        continue;

                    mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                    mparm->pad_1 = rule->actionset->arg_min;
                    mparm->pad_2 = rule->actionset->arg_max;
                    apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
                } else  {
                    mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                    if (mparm == NULL)
                        continue;

                    mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                    apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
                }
            }

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
                        log_escape_nq_ex(msr->mp, s->value, s->value_len));
            }
        }
    }

#ifndef WITH_PCRE
    if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
    if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
        /* We no longer escape the pattern here as it is done when logging */
        char *pattern = apr_pstrdup(msr->mp, log_escape(msr->mp, regex->pattern ? regex->pattern : "<Unknown Match>"));

        /* This message will be logged. */
        if (strlen(pattern) > 252) {
            *error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.",
                    pattern, var->name);
        } else {
            *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
                    pattern, var->name);
        }

        return 1;
    }

    /* No match. */
    return 0;
}

/* pm */

static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    ACMP *p;
    const char *phrase;
    const char *next;
    unsigned short int op_len;

    if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'.");
        return 0; /* ERROR */
    }

    op_len = strlen(rule->op_param);

    p = acmp_create(0, rule->ruleset->mp);
    if (p == NULL) return 0;

    phrase = apr_pstrdup(rule->ruleset->mp, parse_pm_content(rule->op_param, op_len, rule, error_msg));

    if(phrase == NULL)
        phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param);

    /* Loop through phrases */
    /* ENH: Need to allow quoted phrases w/space */
    for (;;) {
        while((apr_isspace(*phrase) != 0) && (*phrase != '\0')) phrase++;
        if (*phrase == '\0') break;
        next = phrase;
        while((apr_isspace(*next) == 0) && (*next != 0)) next++;
        acmp_add_pattern(p, phrase, NULL, NULL, next - phrase);
        phrase = next;
    }
    acmp_prepare(p);
    rule->op_param_data = p;
    return 1;
}

/* pmFromFile */

static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    char errstr[1024];
    char buf[HUGE_STRING_LEN + 1];
    char *fn = NULL;
    char *next = NULL;
    char *start = NULL;
    char *end = NULL;
    const char *rulefile_path;
    char *processed = NULL;
    unsigned short int op_len;
    apr_status_t rc;
    apr_file_t *fd = NULL;
    ACMP *p;

    if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pmFromFile'.");
        return 0; /* ERROR */
    }

    p = acmp_create(0, rule->ruleset->mp);
    if (p == NULL) return 0;

    fn = apr_pstrdup(rule->ruleset->mp, rule->op_param);

    /* Get the path of the rule filename to use as a base */
    rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename)));

    #ifdef DEBUG_CONF
    fprintf(stderr, "Rulefile path: \"%s\"\n", rulefile_path);
    #endif

    /* Loop through filenames */
    /* ENH: Need to allow quoted filenames w/space */
    for (;;) {
        const char *rootpath = NULL;
        const char *filepath = NULL;
        int line = 0;

        /* Trim whitespace */
        while((apr_isspace(*fn) != 0) && (*fn != '\0')) fn++;
        if (*fn == '\0') break;
        next = fn;
        while((apr_isspace(*next) == 0) && (*next != '\0')) next++;
        while((apr_isspace(*next) != 0) && (*next != '\0')) *(next++) = '\0';

        /* Add path of the rule filename for a relative phrase filename */
        filepath = fn;

        if (strlen(fn) > strlen("http://") &&
            strncmp(fn, "http://", strlen("http://")) == 0)
        {
            *error_msg = apr_psprintf(rule->ruleset->mp, "HTTPS address or " \
                "file path are expected for operator pmFromFile \"%s\"", fn);
            return 0;
        }
        else if (strlen(fn) > strlen("https://") &&
            strncmp(fn, "https://", strlen("https://")) == 0)
        {
#ifdef WITH_CURL
            int res = 0;
            char *word = NULL;
            char *brkt = NULL;
            char *sep = "\n";
            struct msc_curl_memory_buffer_t chunk;

            res = msc_remote_download_content(rule->ruleset->mp, fn, NULL,
                    &chunk, error_msg);
            if (res == -2)
            {
                /* If download failed but SecRemoteRulesFailAction is set to Warn. */
                return 1;
            }
            else if (res < 0)
            {
                return 0;
            }

            for (word = strtok_r(chunk.memory, sep, &brkt);
                 word;
                 word = strtok_r(NULL, sep, &brkt))
            {
                /* Ignore empty lines and comments */
                if (*word == '#') continue;

                acmp_add_pattern(p, word, NULL, NULL, strlen(word));
            }
            msc_remote_clean_chunk(&chunk);
#else
            *error_msg = apr_psprintf(rule->ruleset->mp, "ModSecurity was not " \
                "compiled with Curl support, it cannot load: \"%s\"", fn);
            return 0;
#endif
        }
        else
        {
            if (apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, rule->ruleset->mp) != APR_SUCCESS) {
                /* We are not an absolute path.  It could mean an error, but
                 * let that pass through to the open call for a better error */
                apr_filepath_merge(&fn, rulefile_path, fn, APR_FILEPATH_TRUENAME, rule->ruleset->mp);
            }

            /* Open file and read */
            rc = apr_file_open(&fd, fn, APR_READ | APR_BUFFERED | APR_FILE_NOCLEANUP, 0, rule->ruleset->mp);
            if (rc != APR_SUCCESS) {
                *error_msg = apr_psprintf(rule->ruleset->mp, "Could not open phrase file \"%s\": %s", fn, apr_strerror(rc, errstr, 1024));
                return 0;
            }

            #ifdef DEBUG_CONF
            fprintf(stderr, "Loading phrase file: \"%s\"\n", fn);
            #endif

            /* Read one pattern per line skipping empty/commented */
            for(;;) {
                line++;
                rc = apr_file_gets(buf, HUGE_STRING_LEN, fd);
                if (rc == APR_EOF) break;
                if (rc != APR_SUCCESS) {
                    *error_msg = apr_psprintf(rule->ruleset->mp, "Could not read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024));
                    return 0;
                }

                op_len = strlen(buf);
                processed = apr_pstrdup(rule->ruleset->mp, parse_pm_content(buf, op_len, rule, error_msg));

                /* Trim Whitespace */
                if(processed != NULL)
                    start = processed;
                else
                    start = buf;

                while ((apr_isspace(*start) != 0) && (*start != '\0')) start++;
                if(processed != NULL)
                    end = processed + strlen(processed);
                else
                    end = buf + strlen(buf);
                if (end > start) end--;
                while ((end > start) && (apr_isspace(*end) != 0)) end--;
                if (end > start) {
                    *(++end) = '\0';
                }

                /* Ignore empty lines and comments */
                if ((start == end) || (*start == '#')) continue;

                acmp_add_pattern(p, start, NULL, NULL, (end - start));
            }
        }

        fn = next;

        if (fd != NULL) apr_file_close(fd);
    }

    acmp_prepare(p);
    rule->op_param_data = p;
    return 1;
}

static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    const char *match = NULL;
    apr_status_t rc = 0;
    int capture;
    ACMPT pt;

    /* Nothing to read */
    if ((var->value == NULL) || (var->value_len == 0)) return 0;

    /* Are we supposed to capture subexpressions? */
    if (rule->actionset->actions)
        capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
    else capture = 0;

    if (rule->op_param_data == NULL)
    {
        if (msr->txcfg->debuglog_level >= 6)
        {
            msr_log(msr, 1, "ACMPTree is null.");
        }

        return 0;
    }

    pt.parser = (ACMP *)rule->op_param_data;
    pt.ptr = NULL;

    rc = acmp_process_quick(&pt, &match, var->value, var->value_len);
    if (rc) {
        char *match_escaped = log_escape(msr->mp, match ? match : "<Unknown Match>");

        /* This message will be logged. */
        if (strlen(match_escaped) > 252) {
            *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%.252s ...\" at %s.",
                    match_escaped, var->name);
        } else {
            *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%s\" at %s.",
                    match_escaped, var->name);
        }

        /* Handle capture as tx.0=match */
        if (capture) {
            int i;
            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

            if (s == NULL) return -1;

            s->name = "0";
            s->name_len = strlen(s->name);
            s->value = apr_pstrdup(msr->mp, match);
            if (s->value == NULL) return -1;
            s->value_len = strlen(s->value);
            apr_table_setn(msr->tx_vars, s->name, (void *)s);

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Added phrase match to TX.0: %s",
                        log_escape_nq_ex(msr->mp, s->value, s->value_len));
            }

            /* Unset the remaining ones (from previous invocations). */
            for(i = rc; i <= 9; i++) {
                char buf[2];
                apr_snprintf(buf, sizeof(buf), "%d", i);
                apr_table_unset(msr->tx_vars, buf);
            }
        }

        return 1;
    }
    return rc;
}

/* gsbLookup */

/**
 * \brief Reduce /./ to /
 *
 * \param pool Pointer to the memory pool
 * \param domain Input data
 *
 * \retval domain On Failure
 * \retval url On Success
 */
static const char *gsb_replace_tpath(apr_pool_t *pool, const char *domain, int len)    {
    assert(domain != NULL);
    char *pos = NULL, *data = NULL;
    char *url = NULL;
    int match = 0;

    url = apr_palloc(pool, len + 1);
    if (!url) return NULL;
    data = apr_palloc(pool, len + 1);
    if (!data) return NULL;

    url[len] = '\0';

    while(( pos = strstr(url , "/./" )) != NULL) {
        match = 1;
        data[0] = '\0';
        strncat(data, url, pos - url);
        strcat(data , "/");
        strcat(data ,pos + strlen("/./"));
        strncpy(url , data, len);
    }

    if (match == 0) return domain;

    return url;
}

/**
 * \brief Reduce double dot to single dot
 *
 * \param msr ModSecurity transation resource
 * \param domain Input data
 *
 * \retval domain On Failure
 * \retval reduced On Success
 */
static const char *gsb_reduce_char(apr_pool_t *pool, const char *domain) {
    assert(domain != NULL);
    char *ptr = apr_pstrdup(pool, domain);
    char *data = NULL;
    char *reduced = NULL;
    int skip = 0;


    if(ptr == NULL)
        return domain;

    data = apr_pcalloc(pool, strlen(ptr));

    if(data == NULL)
        return domain;

    reduced = data;

    while(*ptr != '\0') {

        switch(*ptr)    {
            case '.':
                ptr++;
                if(*ptr == '.')
                    skip = 1;

                ptr--;
                break;
            case '/':
                ptr++;
                if(*ptr == '/')
                    skip = 1;

                ptr--;
                break;
        }

        if(skip == 0)   {
            *data = *ptr;
            data++;
        }
        ptr++;
        skip = 0;
    }

    *data = '\0'; --data;

    if(*data == '.')
        *data = '\0';
    else
        ++data;

    return reduced;
}


/**
 * \brief Verify function to gsbLookup operator
 *
 * \param msr ModSecurity transaction resource
 * \param match Pointer to input data
 * \param match_length Input size
 *
 * \retval -1 On Failure
 * \retval 1 On Match
 * \retval 0 On No Match
 */
static int verify_gsb(gsb_db *gsb, modsec_rec *msr, const char *match, unsigned int match_length) {
    assert(gsb != NULL);
    assert(msr != NULL);
    assert(match != NULL);
    apr_md5_ctx_t ctx;
    apr_status_t rc;
    unsigned char digest[APR_MD5_DIGESTSIZE];
    const char *hash = NULL;
    const char *search = NULL;

    apr_md5_init(&ctx);

    if ((rc = apr_md5_update(&ctx, match, match_length)) != APR_SUCCESS)
        return -1;

    apr_md5_final(digest, &ctx);

    hash = apr_psprintf(msr->mp, "%s", bytes2hex(msr->mp, digest, APR_MD5_DIGESTSIZE));

    if ((hash != NULL) && (gsb->gsb_table != NULL))   {
        search = apr_hash_get(gsb->gsb_table, hash, APR_HASH_KEY_STRING);

        if (search != NULL)
            return 1;
    }

    return 0;
}

/**
 * \brief Init function to gsbLookup operator
 *
 * \param rule ModSecurity rule struct
 * \param error_msg Error message
 *
 * \retval 1 On Success
 * \retval 0 On Fail
 */
static int msre_op_gsbLookup_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(error_msg != NULL);
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    msc_regex_t *regex;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    /* Compile rule->op_param */
#ifndef WITH_PCRE
    options = PCRE2_DOTALL | PCRE2_MULTILINE;
#else
    options = PCRE_DOTALL | PCRE_MULTILINE;
#endif
    regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);

    if (regex == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
                erroffset, errptr);
        return 0;
    }

    rule->op_param_data = regex;

    return 1; /* OK */
}

/**
 * \brief Execution function to gsbLookup operator
 *
 * \param msr ModSecurity transaction resource
 * \param rule ModSecurity rule struct
 * \param var ModSecurity variable struct
 * \param error_msg Error message
 *
 * \retval -1 On Failure
 * \retval 1 On Match
 * \retval 0 On No Match
 */
static int msre_op_gsbLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    char *my_error_msg = NULL;
    int ovector[33];
    unsigned int offset = 0;
    int options = 0;
    gsb_db *gsb = msr->txcfg->gsb;
    const char *match = NULL;
    unsigned int match_length;
    unsigned int canon_length;
    int rv, i, ret, count_slash;
    unsigned int j = 0;
    unsigned int size = var->value_len;
    char *base = NULL, *domain = NULL, *savedptr = NULL;
    char *str = NULL, *canon = NULL, *dot = NULL;
    char *data = NULL, *ptr = NULL, *url = NULL;
    int capture, domain_len;
    int d_pos = -1;
    int s_pos = -1;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if(regex == NULL)    {
        *error_msg = "Internal Error: regex is null.";
        return 0;
    }

    if(gsb == NULL)    {
        msr_log(msr, 1, "GSB lookup failed without a database.  Set SecGsbLookupDB.");
        return 0;
    }

    data = apr_pcalloc(msr->mp, var->value_len+1);

    if(data == NULL)    {
        *error_msg = "Internal Error: cannot allocate memory for data.";
        return -1;
    }

    capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;

    memcpy(data,var->value,var->value_len);

#ifndef WITH_PCRE
    options = PCRE2_NOTEMPTY;
#else
    options = PCRE_NOTEMPTY;
#endif
    while (offset < size && (rv = msc_regexec_ex(regex, data, size, offset, options, ovector, 30, &my_error_msg)) >= 0)
    {
        for(i = 0; i < rv; ++i)
        {
            match = apr_psprintf(msr->mp, "%.*s", ovector[2*i+1] - ovector[2*i], data + ovector[2*i]);

            if (match == NULL)  {
                *error_msg = "Internal Error: cannot allocate memory for match.";
                return -1;
            }

            match = remove_escape(msr->mp, match, strlen(match));

            match = gsb_replace_tpath(msr->mp, match, strlen(match));

            match = gsb_reduce_char(msr->mp, match);

            match_length = strlen(match);

            strtolower_inplace((unsigned char *)match);

            if((strstr(match,"http") == NULL) && (match_length > 0) && (strchr(match,'.')))    {

                /* full url */
                if (msr->txcfg->debuglog_level >= 4) {
                    msr_log(msr, 4, "GSB: Successfully extracted url: %s", match);
                }

                ret = verify_gsb(gsb, msr, match, match_length);

                if(ret > 0) {
                    set_match_to_tx(msr, capture, match, 0);
                    if (! *error_msg) {
                        *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                log_escape_nq(msr->mp, match));
                    }

                    str = apr_pstrdup(msr->mp,match);

                    base = apr_strtok(str,"/",&savedptr);
                    if(base != NULL)
                        set_match_to_tx(msr, capture, base, 1);

                    return 1;
                }

                /* append / in the end of full url */
                if ((match[match_length -1] != '/') && (strchr(match,'?') == NULL))    {

                    canon = apr_psprintf(msr->mp, "%s/", match);
                    if (canon != NULL)  {

                        canon_length = strlen(canon);
                        ret = verify_gsb(gsb, msr, canon, canon_length);

                        if(ret > 0) {
                            set_match_to_tx(msr, capture, match, 0);
                            if (! *error_msg) {
                                *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                        log_escape_nq(msr->mp, canon));
                            }

                            str = apr_pstrdup(msr->mp,match);

                            base = apr_strtok(str,"/",&savedptr);
                            if(base != NULL)
                                set_match_to_tx(msr, capture, base, 1);

                            return 1;
                        }
                    }
                }

                /* Parsing full url */

                domain = apr_pstrdup(msr->mp, match);

                domain_len = strlen(domain);

                if(*domain != '/')  {

                    if(domain[domain_len-1] == '.')
                        domain[domain_len-1] = '\0';
                    if(domain[domain_len-1] == '/' && domain[domain_len-2] == '.')    {
                        domain[domain_len-2] = '/';
                        domain[domain_len-1] = '\0';
                    }

                    dot = strchr(domain,'.');
                    if(dot != NULL) {
                        canon = apr_pstrdup(msr->mp, domain);

                        ret = verify_gsb(gsb, msr, canon, strlen(canon));

                        if(ret > 0) {
                            set_match_to_tx(msr, capture, canon, 0);
                            if (! *error_msg) {
                                *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                        log_escape_nq(msr->mp, canon));
                            }

                            str = apr_pstrdup(msr->mp,match);

                            base = apr_strtok(str,"/",&savedptr);
                            if(base != NULL)
                                set_match_to_tx(msr, capture, base, 1);

                            return 1;
                        }


                        base = apr_strtok(canon,"?",&savedptr);

                        if(base != NULL)   {
                            ret = verify_gsb(gsb, msr, base, strlen(base));

                            if(ret > 0) {
                                set_match_to_tx(msr, capture, base, 0);
                                if (! *error_msg) {
                                    *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                            log_escape_nq(msr->mp, base));
                                }

                                str = apr_pstrdup(msr->mp,match);

                                base = apr_strtok(str,"/",&savedptr);
                                if(base != NULL)
                                    set_match_to_tx(msr, capture, base, 1);

                                return 1;
                            }

                        }

                        url = apr_palloc(msr->mp, strlen(canon));
                        count_slash = 0;

                        while(*canon != '\0') {
                            switch (*canon)   {
                                case '/':
                                    ptr = apr_psprintf(msr->mp,"%s/",url);
                                    ret = verify_gsb(gsb, msr, ptr, strlen(ptr));
                                    if(ret > 0) {
                                        set_match_to_tx(msr, capture, ptr, 0);
                                        if (! *error_msg) {
                                            *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                                    log_escape_nq(msr->mp, ptr));
                                        }

                                        str = apr_pstrdup(msr->mp,match);

                                        base = apr_strtok(str,"/",&savedptr);
                                        if(base != NULL)
                                            set_match_to_tx(msr, capture, base, 1);
                                        return 1;
                                    }

                                    break;
                            }
                            url[count_slash] = *canon;
                            count_slash++;
                            canon++;
                        }
                    }
                }

                /* Do the same for subdomains */

                for(j=0; j<strlen(match); j++)    {
                    if(match[j] == '/')    {
                        s_pos = j;
                        break;
                    }
                }


                str = apr_pstrdup(msr->mp, match);

                while (*str != '\0')   {

                    switch(*str) {
                        case '.':
                            domain++;
                            domain_len = strlen(domain);

                            d_pos = strchr(domain,'.') - domain;

                            if(s_pos >= 0 && d_pos >= 0 && d_pos > s_pos)
                                break;

                            if(*domain != '/')  {

                                if(domain[domain_len-1] == '.')
                                    domain[domain_len-1] = '\0';
                                if(domain[domain_len-1] == '/' && domain[domain_len-2] == '.')    {
                                    domain[domain_len-2] = '/';
                                    domain[domain_len-1] = '\0';
                                }

                                dot = strchr(domain,'.');
                                if(dot != NULL) {
                                    canon = apr_pstrdup(msr->mp, domain);

                                    ret = verify_gsb(gsb, msr, canon, strlen(canon));

                                    if(ret > 0) {
                                        set_match_to_tx(msr, capture, canon, 0);
                                        if (! *error_msg) {
                                            *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                                    log_escape_nq(msr->mp, canon));
                                        }
                                        str = apr_pstrdup(msr->mp,match);

                                        base = apr_strtok(str,"/",&savedptr);
                                        if(base != NULL)
                                            set_match_to_tx(msr, capture, base, 1);
                                        return 1;
                                    }


                                    base = apr_strtok(canon,"?",&savedptr);

                                    if(base != NULL)   {
                                        ret = verify_gsb(gsb, msr, base, strlen(base));

                                        if(ret > 0) {
                                            set_match_to_tx(msr, capture, base, 0);
                                            if (! *error_msg) {
                                                *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                                        log_escape_nq(msr->mp, base));
                                            }
                                            str = apr_pstrdup(msr->mp,match);

                                            base = apr_strtok(str,"/",&savedptr);
                                            if(base != NULL)
                                                set_match_to_tx(msr, capture, base, 1);
                                            return 1;
                                        }

                                    }

                                    url = apr_palloc(msr->mp, strlen(canon));
                                    count_slash = 0;

                                    while(*canon != '\0') {
                                        switch (*canon)   {
                                            case '/':
                                                ptr = apr_psprintf(msr->mp,"%s/",url);
                                                ret = verify_gsb(gsb, msr, ptr, strlen(ptr));
                                                if(ret > 0) {
                                                    set_match_to_tx(msr, capture, ptr, 0);
                                                    if (! *error_msg) {
                                                        *error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
                                                                log_escape_nq(msr->mp, ptr));
                                                    }
                                                    str = apr_pstrdup(msr->mp,match);

                                                    base = apr_strtok(str,"/",&savedptr);
                                                    if(base != NULL)
                                                        set_match_to_tx(msr, capture, base, 1);
                                                    return 1;
                                                }

                                                break;
                                        }
                                        url[count_slash] = *canon;
                                        count_slash++;
                                        canon++;
                                    }
                                }
                            }
                            break;
                    }

                    domain = str;
                    domain++;
                    str++;
                }

            }
        }

        offset = ovector[1];
    }

    return 0;
}

/* within */

static int msre_op_within_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length = 0;
    unsigned int i, i_max;

    *error_msg = NULL;

    str->value = (char *)rule->op_param;

    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null we give up without a match */
    if (var->value == NULL) {
        /* No match. */
        return 0;
    }

    target = var->value;
    target_length = var->value_len;

    /* The empty string always matches */
    if (target_length == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match within \"\" at %s.",
                var->name);
        return 1;
    }

    /* This is impossible to match */
    if (target_length > match_length) {
        /* No match. */
        return 0;
    }

    /* scan for first character, then compare from there until we
     * have a match or there is no room left in the target
     */
    i_max = match_length - target_length;
    for (i = 0; i <= i_max; i++) {
        if (match[i] == target[0]) {
            if (memcmp((target + 1), (match + i + 1), (target_length - 1)) == 0) {
                /* match. */
                *error_msg = apr_psprintf(msr->mp, "String match within \"%s\" at %s.",
                        log_escape_ex(msr->mp, match, match_length),
                        var->name);
                return 1;
            }
        }
    }

    /* No match. */
    return 0;
}

/* contains */

static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length = 0;
    unsigned int i, i_max;
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (str == NULL) {
        *error_msg = "Internal Error: cannot allocate memory.";
        return -1;
    }

    str->value = (char *)rule->op_param;
    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* The empty string always matches */
    if (match_length == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
        return 1;
    }

    /* This is impossible to match */
    if (match_length > target_length) {
        /* No match. */
        return 0;
    }

    /* scan for first character, then compare from there until we
     * have a match or there is no room left in the target
     */
    i_max = target_length - match_length;
    for (i = 0; i <= i_max; i++) {
        /* First character matched - avoid func call */
        if (target[i] == match[0]) {
            /* See if remaining matches */
            if (   (match_length == 1)
                    || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0))
            {
                /* Match. */
                *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
                        log_escape_ex(msr->mp, match, match_length),
                        var->name);
                return 1;
            }
        }
    }

    /* No match. */
    return 0;
}

/** libinjection evaluate
 *   evaluates the result of a libinjection function
 *   based on injection_result_t enum values
 */
static int libinjection_evaluate(injection_result_t result) {
    return (result != LIBINJECTION_RESULT_FALSE);
}

/** libinjection detectSQLi
 *   links against files in libinjection directory
 *  See www.client9.com/libinjection for details
 */
static int msre_op_detectSQLi_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(rule->actionset->actions != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    char fingerprint[8];
    injection_result_t issqli;
    int capture;

    issqli = libinjection_sqli(var->value ? var->value : "", var->value_len, fingerprint);
    capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;

    if (libinjection_evaluate(issqli)) {
        switch(issqli) {
            case LIBINJECTION_RESULT_TRUE:
                set_match_to_tx(msr, capture, fingerprint, 0);
                *error_msg = apr_psprintf(msr->mp, "detected SQLi using libinjection with fingerprint '%s'",
                                        fingerprint);
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "ISSQL: libinjection fingerprint '%s' matched input '%s'",
                        fingerprint,
                        log_escape_ex(msr->mp, var->value, var->value_len));
                }
                break;
            case LIBINJECTION_RESULT_ERROR:
                set_match_to_tx_safe(msr, capture, var->value ? var->value : "", var->value_len, 0);
                *error_msg = apr_psprintf(msr->mp, "libinjection parser error");
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "ISSQL: libinjection's input '%s' caused a parser error",
                        log_escape_ex(msr->mp, var->value, var->value_len));
                }
                break;
            default:
                set_match_to_tx_safe(msr, capture, var->value ? var->value : "", var->value_len, 0);
                *error_msg = apr_psprintf(msr->mp, "unexpected libinjection result: (%d)", issqli);
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "ISSQL: libinjection's input '%s' caused an unexpected result: (%d)",
                        log_escape_ex(msr->mp, var->value, var->value_len),
                        issqli);
                }
                break;
        }
    } else {
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "ISSQL: not sqli, no libinjection sqli fingerprint matched input '%s'",
                    log_escape_ex(msr->mp, var->value, var->value_len));
        }
    }

    return libinjection_evaluate(issqli);
}

/** libinjection detectXSS
 */
static int msre_op_detectXSS_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(rule->actionset->actions != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    int capture;
    injection_result_t is_xss;

    is_xss = libinjection_xss(var->value ? var->value : "", var->value_len);
    capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;

    if (libinjection_evaluate(is_xss)) {
        set_match_to_tx_safe(msr, capture, var->value ? var->value : "", var->value_len, 0);
        switch(is_xss) {
            case LIBINJECTION_RESULT_TRUE:
                *error_msg = apr_psprintf(msr->mp, "detected XSS using libinjection.");
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "IS_XSS: libinjection detected XSS in input '%s'",
                        log_escape_ex(msr->mp, var->value, var->value_len));
                }
                break;
            case LIBINJECTION_RESULT_ERROR:
                *error_msg = apr_psprintf(msr->mp, "libinjection parser error");
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "IS_XSS: libinjection's input '%s' caused a parser error",
                        log_escape_ex(msr->mp, var->value, var->value_len));
                }
                break;
            default:
                *error_msg = apr_psprintf(msr->mp, "unexpected libinjection result: (%d)", is_xss);
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "IS_XSS: libinjection's input '%s' caused an unexpected result: (%d)",
                        log_escape_ex(msr->mp, var->value, var->value_len),
                        is_xss);
                }
                break;
        }
    } else {
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "IS_XSS: not XSS, libinjection was not able to find any XSS.");
        }
    }

    return libinjection_evaluate(is_xss);
}


/* containsWord */

static int msre_op_containsWord_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length = 0;
    unsigned int i, i_max;
    int rc = 0;
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (str == NULL) {
        *error_msg = "Internal Error: cannot allocate memory.";
        return -1;
    }

    str->value = (char *)rule->op_param;
    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    *error_msg = NULL;

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* The empty string always matches */
    if (match_length == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
        return 1;
    }

    /* This is impossible to match */
    if (match_length > target_length) {
        /* No match. */
        return 0;
    }

    /* scan for first character, then compare from there until we
     * have a match or there is no room left in the target
     */
    i_max = target_length - match_length;
    for (i = 0; i <= i_max; i++) {

        /* Previous char must have been a start or non-word */
        if ((i > 0) && (apr_isalnum(target[i-1])||(target[i-1] == '_')))
            continue;

        /* First character matched - avoid func call */
        if (target[i] == match[0]) {
            /* See if remaining matches */
            if (   (match_length == 1)
                    || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0))
            {
                /* check boundaries */
                if (i == i_max) {
                    /* exact/end word match */
                    rc = 1;
                }
                else if (!(apr_isalnum(target[i + match_length])||(target[i + match_length] == '_'))) {
                    /* start/mid word match */
                    rc = 1;
                }
            }
        }
    }

    if (rc == 1) {
        /* Maybe a match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
                log_escape_ex(msr->mp, match, match_length),
                var->name);
        return 1;
    }

    /* No match. */
    *error_msg = NULL;
    return 0;
}

/* streq */

static int msre_op_streq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (!str) {
        msr_log(msr, 1, "streq: Memory allocation error");
        return -1;
    }
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length;

    str->value = (char *)rule->op_param;
    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    *error_msg = NULL;

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* These are impossible to match */
    if (match_length != target_length) {
        /* No match. */
        return 0;
    }

    if (memcmp(match, target, target_length) == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
                log_escape_ex(msr->mp, match, match_length),
                var->name);
        return 1;
    }

    /* No match. */
    return 0;
}

/* beginsWith */

static int msre_op_beginsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length;
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (str == NULL) {
            *error_msg = "Internal Error: cannot allocate memory.";
            return -1;
        }

    str->value = (char *)rule->op_param;
    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* The empty string always matches */
    if (match_length == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
        return 1;
    }

    /* This is impossible to match */
    if (match_length > target_length) {
        /* No match. */
        return 0;
    }

    if (memcmp(match, target, match_length) == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
                log_escape_ex(msr->mp, match, match_length),
                var->name);
        return 1;
    }

    /* No match. */
    return 0;
}

/* endsWith */

static int msre_op_endsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    // Normally useless code, left to be safe for the moment
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_endsWith_execute: error_msg is NULL");
        return -1;
    }
    const char *match = NULL;
    const char *target;
    unsigned int match_length;
    unsigned int target_length;
    msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    if (str == NULL) {
        *error_msg = "Internal Error: cannot allocate memory.";
        return -1;
    }

    str->value = (char *)rule->op_param;
    if (str->value == NULL) {
        *error_msg = "Internal Error: match string is null.";
        return -1;
    }

    str->value_len = strlen(str->value);

    *error_msg = NULL;

    expand_macros(msr, str, rule, msr->mp);

    match = (const char *)str->value;
    match_length = str->value_len;

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    /* The empty string always matches */
    if (match_length == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
        return 1;
    }

    /* This is impossible to match */
    if (match_length > target_length) {
        /* No match. */
        return 0;
    }

    if (memcmp(match, (target + (target_length - match_length)), match_length) == 0) {
        /* Match. */
        *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
                log_escape_ex(msr->mp, match, match_length),
                var->name);
        return 1;
    }

    /* No match. */
    return 0;
}

/* strmatch */

static int msre_op_strmatch_param_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    // Normally useless code, left to be safe for the moment
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_strmatch_param_init: error_msg is NULL");
        return -1;
    }
    const apr_strmatch_pattern *compiled_pattern;
    char *processed = NULL;
    const char *pattern = rule->op_param;
    unsigned short int op_len;

    *error_msg = NULL;

    op_len = strlen(pattern);

    /* Process pattern */
    processed = parse_pm_content(pattern, op_len, rule, error_msg);
    if (processed == NULL) {
        return 0;
    }

    /* Compile pattern */
    compiled_pattern = apr_strmatch_precompile(rule->ruleset->mp, processed, 1);
    if (compiled_pattern == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern: %s", pattern);
        return 0;
    }

    rule->op_param_data = (void *)compiled_pattern;

    return 1; /* OK */
}

static int msre_op_strmatch_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    // Normally useless code, left to be safe for the moment
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_strmatch_execute: error_msg is NULL");
        return -1;
    }
    apr_strmatch_pattern *compiled_pattern = (apr_strmatch_pattern *)rule->op_param_data;
    const char *target;
    unsigned int target_length;
    const char *rc;

    *error_msg = NULL;

    if (compiled_pattern == NULL) {
        *error_msg = "Internal Error: strnmatch data is null.";
        return -1;
    }

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    rc = apr_strmatch(compiled_pattern, target, target_length);
    if (rc == NULL) {
        /* No match. */
        return 0;
    }

    *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
            log_escape(msr->mp, rule->op_param), var->name);

    /* Match. */
    return 1;
}

/* validateDTD */

static int msre_op_validateDTD_init(msre_rule *rule, char **error_msg) {
    /* ENH Verify here the file actually exists. */
    return 1;
}

static int msre_op_validateDTD_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
        char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    xmlValidCtxtPtr cvp;
    xmlDtdPtr dtd;

    if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
        *error_msg = apr_psprintf(msr->mp,
                "XML document tree could not be found for DTD validation.");
        return -1;
    }

    if (msr->xml->well_formed != 1) {
        *error_msg = apr_psprintf(msr->mp,
                "XML: DTD validation failed because content is not well formed.");
        return 1;
    }

    /* Make sure there were no other generic processing errors */
    if (msr->msc_reqbody_error) {
        *error_msg = apr_psprintf(msr->mp,
                "XML: DTD validation could not proceed due to previous"
                " processing errors.");
        return 1;
    }

    dtd = xmlParseDTD(NULL, (const xmlChar *)rule->op_param); /* EHN support relative filenames */
    if (dtd == NULL) {
        *error_msg = apr_psprintf(msr->mp, "XML: Failed to load DTD: %s", rule->op_param);
        return -1;
    }

    cvp = xmlNewValidCtxt();
    if (cvp == NULL) {
        *error_msg = "XML: Failed to create a validation context.";
        xmlFreeDtd(dtd);
        return -1;
    }

    /* Send validator errors/warnings to msr_log */
    /* NOTE: No xmlDtdSetValidErrors()? */
    cvp->error = (xmlSchemaValidityErrorFunc)msr_log_error;
    cvp->warning = (xmlSchemaValidityErrorFunc)msr_log_warn;
    cvp->userData = msr;

    if (!xmlValidateDtd(cvp, msr->xml->doc, dtd)) {
        *error_msg = "XML: DTD validation failed.";
        xmlFreeValidCtxt(cvp);
        xmlFreeDtd(dtd);
        return 1; /* No match. */
    }

    if (msr->txcfg->debuglog_level >= 4) {
        msr_log(msr, 4, "XML: Successfully validated payload against DTD: %s", rule->op_param);
    }

    xmlFreeValidCtxt(cvp);
    xmlFreeDtd(dtd);

    /* Match. */
    return 0;
}

/* validateSchema */

static int msre_op_validateSchema_init(msre_rule *rule, char **error_msg) {
    /* ENH Verify here the file actually exists. */
    return 1;
}

static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
        char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    xmlSchemaParserCtxtPtr parserCtx;
    xmlSchemaValidCtxtPtr validCtx;
    xmlSchemaPtr schema;
    int rc;

    if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
        *error_msg = apr_psprintf(msr->mp,
                "XML document tree could not be found for schema validation.");
        return -1;
    }

    if (msr->xml->well_formed != 1) {
        *error_msg = apr_psprintf(msr->mp,
                "XML: Schema validation failed because content is not well formed.");
        return 1;
    }

    /* Make sure there were no other generic processing errors */
    if (msr->msc_reqbody_error) {
        *error_msg = apr_psprintf(msr->mp,
                "XML: Schema validation could not proceed due to previous"
                " processing errors.");
        return 1;
    }

    parserCtx = xmlSchemaNewParserCtxt(rule->op_param); /* ENH support relative filenames */
    if (parserCtx == NULL) {
        *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema from file: %s",
                rule->op_param);
        return -1;
    }

    /* Send parser errors/warnings to msr_log */
    xmlSchemaSetParserErrors(parserCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);

    schema = xmlSchemaParse(parserCtx);
    if (schema == NULL) {
        *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema: %s", rule->op_param);
        xmlSchemaFreeParserCtxt(parserCtx);
        return -1;
    }

    validCtx = xmlSchemaNewValidCtxt(schema);
    if (validCtx == NULL) {
        *error_msg = "XML: Failed to create validation context.";
        xmlSchemaFree(schema);
        xmlSchemaFreeParserCtxt(parserCtx);
        return -1;
    }

    /* Send validator errors/warnings to msr_log */
    xmlSchemaSetValidErrors(validCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);

    rc = xmlSchemaValidateDoc(validCtx, msr->xml->doc);
    if (rc != 0) {
        *error_msg = "XML: Schema validation failed.";
        xmlSchemaFree(schema);
        xmlSchemaFreeParserCtxt(parserCtx);
        return 1; /* No match. */
    }

    if (msr->txcfg->debuglog_level >= 4) {
        msr_log(msr, 4, "XML: Successfully validated payload against Schema: %s", rule->op_param);
    }

    xmlSchemaFree(schema);
    xmlSchemaFreeValidCtxt(validCtx);
    xmlSchemaFreeParserCtxt(parserCtx);

    return 0;
}

/* verifyCC */

/**
 * Luhn Mod-10 Method (ISO 2894/ANSI 4.13)
 */
static int luhn_verify(const char *ccnumber, int len) {
    assert(ccnumber != NULL);
    int sum[2] = { 0, 0 };
    int odd = 0;
    int digits = 0;
    int i;

    /* Weighted lookup table which is just a precalculated (i = index):
     *   i*2 + (( (i*2) > 9 ) ? -9 : 0)
     */
    static const int wtable[10] = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9}; /* weight lookup table */

    /* Add up only digits (weighted digits via lookup table)
     * for both odd and even CC numbers to avoid 2 passes.
     */
    for (i = 0; i < len; i++) {
        if (apr_isdigit(ccnumber[i])) {
            sum[0] += (!odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
            sum[1] += (odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
            odd = 1 - odd; /* alternate weights */
            digits++;
        }
    }

    /* No digits extracted */
    if (digits == 0) return 0;

    /* Do a mod 10 on the sum */
    sum[odd] %= 10;

    /* If the result is a zero the card is valid. */
    return sum[odd] ? 0 : 1;
}

static int msre_op_verifyCC_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    msc_regex_t *regex;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

#ifndef WITH_PCRE
    options = PCRE2_DOTALL | PCRE2_MULTILINE;
#else
    options = PCRE_DOTALL | PCRE_MULTILINE;
#endif
    /* Compile rule->op_param */
    regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
    if (regex == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
                erroffset, errptr);
        return 0;
    }

    rule->op_param_data = regex;

    return 1; /* OK */
}

static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    const char *target;
    unsigned int target_length;
    char *my_error_msg = NULL;
    int ovector[33];
    int rc;
    int is_cc = 0;
    int offset;
    int options = 0;
    int matched_bytes = 0;
    char *qspos = NULL;
    const char *parm = NULL;
    msc_parm *mparm = NULL;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int jit = 0;
       #endif
    #endif

    *error_msg = NULL;

    if (regex == NULL) {
        *error_msg = "Internal Error: regex data is null.";
        return -1;
    }

    assert(rule->actionset != NULL);

    memset(ovector, 0, sizeof(ovector));

    #ifdef WITH_PCRE_STUDY
        #ifdef WITH_PCRE_JIT
    if (msr->txcfg->debuglog_level >= 4) {
            #ifndef WITH_PCRE
        msc_op_set_jitrc(regex, &rc, &jit);
            #else
        rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
            #endif
        if ((rc != 0) || (jit != 1)) {
            *error_msg = apr_psprintf(msr->mp,
                    "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                    "Execution error - "
                    "Does not support JIT (%d)",
                    rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                            (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                    rule->filename != NULL ? rule->filename : "-",
                    rule->line_num,rc);
            msr_log(msr, 4, "%s.", *error_msg);
        }
    }
        #endif
    #endif


    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    for (offset = 0; ((unsigned int)offset < target_length) && (is_cc == 0); offset++) {
        if (msr->txcfg->debuglog_level >= 9) {
            if (offset > 0) {
                msr_log(msr, 9, "Continuing CC# search at target offset %d.", offset);
            }
        }

#ifndef WITH_PCRE
        options = PCRE2_NOTEMPTY;
#else
        options = PCRE_NOTEMPTY;
#endif
        rc = msc_regexec_ex(regex, target, target_length, offset, options, ovector, 30, &my_error_msg);

        /* If there was no match, then we are done. */
#ifndef WITH_PCRE
        if (rc == PCRE2_ERROR_NOMATCH) {
#else
        if (rc == PCRE_ERROR_NOMATCH) {
#endif
            break;
        }

        if (rc < -1) {
            *error_msg = apr_psprintf(msr->mp, "CC# regex execution failed: %s", my_error_msg);
            return -1;
        }

        /* Verify a match. */
        if (rc > 0) {
            const char *match = target + ovector[0];
            int length = ovector[1] - ovector[0];
            int i = 0;

            offset = ovector[2*i];

            /* Check the Luhn using the match string */
            is_cc = luhn_verify(match, length);

            /* Not a CC number, then try another match where we left off. */
            if (!is_cc) {
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "CC# Luhn check failed at target offset %d: \"%.*s\"", offset, length, match);
                }

                continue;
            }

            /* We have a potential CC number and need to set any captures
             * and we are done.
             */

            matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
            if(!matched_bytes)
                matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;

            if (apr_table_get(rule->actionset->actions, "capture")) {
                for(; i < rc; i++) {
                    msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
                    if (s == NULL) return -1;
                    s->name = apr_psprintf(msr->mp, "%d", i);
                    if (s->name == NULL) return -1;
                    s->name_len = strlen(s->name);
                    s->value = apr_pstrmemdup(msr->mp, match, length);
                    if (s->value == NULL) return -1;
                    s->value_len = length;

                    apr_table_setn(msr->tx_vars, s->name, (void *)s);

                    if (msr->txcfg->debuglog_level >= 9) {
                        msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
                                log_escape_nq_ex(msr->mp, s->value, s->value_len));
                    }

                    if((matched_bytes == 1) && (var != NULL) && (var->name != NULL))    {
                        qspos = apr_psprintf(msr->mp, "%s", var->name);
                        parm = strstr(qspos, ":");
                        if (parm != NULL)   {
                            parm++;
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                            mparm->pad_1 = rule->actionset->arg_min;
                            mparm->pad_2 = rule->actionset->arg_max;
                            apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
                        } else  {
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                            apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
                        }
                    }

                }
            }

            /* Unset the remaining TX vars (from previous invocations). */
            for(; i <= 9; i++) {
                char buf[24];
                apr_snprintf(buf, sizeof(buf), "%i", i);
                apr_table_unset(msr->tx_vars, buf);
            }

            break;
        }
    }

    if (is_cc) {
        /* Match. */

        /* This message will be logged. */
        *error_msg = apr_psprintf(msr->mp, "CC# match \"%s\" at %s. [offset \"%d\"]",
                regex->pattern, var->name, offset);

        return 1;
    }

    /* No match. */
    return 0;
}

/**
 * \brief Check for a valid CPF
 *
 * \param cpfnumber Pointer to cpf
 * \param len cpf length
 *
 * \retval 0 On Invalid CPF
 * \retval 1 On Valid CPF
 */
static int cpf_verify(const char *cpfnumber, int len) {
    assert(cpfnumber != NULL);
    int factor, part_1, part_2, var_len = len;
    unsigned int sum = 0, i = 0, cpf_len = 11, c;
    int cpf[11];
    char s_cpf[11];
    char bad_cpf[11][11] = { "00000000000",
        "01234567890",
        "11111111111",
        "22222222222",
        "33333333333",
        "44444444444",
        "55555555555",
        "66666666666",
        "77777777777",
        "88888888888",
        "99999999999"};

    while((*cpfnumber != '\0') && ( var_len > 0))  {

        if(*cpfnumber != '-' || *cpfnumber != '.') {
            if(i < cpf_len && isdigit(*cpfnumber))  {
                s_cpf[i] = *cpfnumber;
                cpf[i] = convert_to_int(*cpfnumber);
                i++;
            }
        }
        cpfnumber++;
        var_len--;
    }


    if (i != cpf_len)
        return 0;
    else {
        for(i = 0; i< cpf_len; i++)   {
            if(strncmp(s_cpf,bad_cpf[i],cpf_len) == 0)    {
                return 0;
            }
        }
    }

    part_1 = convert_to_int(s_cpf[cpf_len-2]);
    part_2 = convert_to_int(s_cpf[cpf_len-1]);

    c = cpf_len;

    for(i = 0; i < 9; i++) {
        sum += (cpf[i] * --c);
    }

    factor = (sum % cpf_len);

    if(factor < 2) {
        cpf[9] = 0;
    } else {
        cpf[9] = cpf_len-factor;
    }

    sum = 0;
    c = cpf_len;

    for(i = 0;i < 10; i++)
        sum += (cpf[i] * c--);

    factor = (sum % cpf_len);

    if(factor < 2) {
        cpf[10] = 0;
    } else {
        cpf[10] = cpf_len-factor;
    }

    if(part_1 == cpf[9] && part_2 == cpf[10])
        return 1;

    return 0;
}

/**
 * \brief Init function to CPF operator
 *
 * \param rule ModSecurity rule struct
 * \param error_msg Error message
 *
 * \retval 0 On Failure
 * \retval 1 On Success
 */
static int msre_op_verifyCPF_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_verifyCPF_init: error_msg is NULL");
        return -1;
    }
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    msc_regex_t *regex;

    *error_msg = NULL;

#ifndef WITH_PCRE
    options = PCRE2_DOTALL | PCRE2_MULTILINE;
#else
    options = PCRE_DOTALL | PCRE_MULTILINE;
#endif
    /* Compile rule->op_param */
    regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
    if (regex == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
                erroffset, errptr);
        return 0;
    }

    rule->op_param_data = regex;

    return 1; /* OK */
}

/**
 * \brief Execution function to CPF operator
 *
 * \param msr ModSecurity transaction resource
 * \param rule ModSecurity rule struct
 * \param var ModSecurity variable struct
 * \param error_msg Error message
 *
 * \retval -1 On Failure
 * \retval 1 On Match
 * \retval 0 On No Match
 */
static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    const char *target;
    unsigned int target_length;
    char *my_error_msg = NULL;
    int ovector[33];
    int rc;
    int is_cpf = 0;
    int offset;
    int options = 0;
    int matched_bytes = 0;
    char *qspos = NULL;
    const char *parm = NULL;
    msc_parm *mparm = NULL;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int jit = 0;
       #endif
    #endif

    *error_msg = NULL;

    if (regex == NULL) {
        *error_msg = "Internal Error: regex data is null.";
        return -1;
    }

    assert(rule->actionset != NULL);

    memset(ovector, 0, sizeof(ovector));

    #ifdef WITH_PCRE_STUDY
        #ifdef WITH_PCRE_JIT
    if (msr->txcfg->debuglog_level >= 4) {
            #ifndef WITH_PCRE
        msc_op_set_jitrc(regex, &rc, &jit);
            #else
        rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
            #endif
        if ((rc != 0) || (jit != 1)) {
            *error_msg = apr_psprintf(msr->mp,
                    "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                    "Execution error - "
                    "Does not support JIT (%d)",
                    rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                            (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                    rule->filename != NULL ? rule->filename : "-",
                    rule->line_num,rc);
            msr_log(msr, 4, "%s.", *error_msg);
        }
    }
        #endif
    #endif

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    for (offset = 0; ((unsigned int)offset < target_length) && (is_cpf == 0); offset++) {
        if (msr->txcfg->debuglog_level >= 9) {
            if (offset > 0) {
                msr_log(msr, 9, "Continuing CPF# search at target offset %d.", offset);
            }
        }

#ifndef WITH_PCRE
        options = PCRE2_NOTEMPTY;
#else
        options = PCRE_NOTEMPTY;
#endif
        rc = msc_regexec_ex(regex, target, target_length, offset, options, ovector, 30, &my_error_msg);

        /* If there was no match, then we are done. */
#ifndef WITH_PCRE
        if (rc == PCRE2_ERROR_NOMATCH) {
#else
        if (rc == PCRE_ERROR_NOMATCH) {
#endif
            break;
        }

        if (rc < -1) {
            *error_msg = apr_psprintf(msr->mp, "CPF# regex execution failed: %s", my_error_msg);
            return -1;
        }

        /* Verify a match. */
        if (rc > 0) {
            const char* match = target + ovector[0];
            int length = ovector[1] - ovector[0];
            int i = 0;

            offset = ovector[2 * i];

            /* Check CPF using the match string */
            is_cpf = cpf_verify(match, length);

            /* Not a CPF number, then try another match where we left off. */
            if (!is_cpf) {
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "CPF# check failed at target offset %d: \"%.*s\"", offset, length, match);
                }

                continue;
            }

            /* We have a potential CPF number and need to set any captures
             * and we are done.
             */

            matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
            if (!matched_bytes)
                matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;

            if (apr_table_get(rule->actionset->actions, "capture")) {
                for (; i < rc; i++) {
                    msc_string* s = (msc_string*)apr_pcalloc(msr->mp, sizeof(msc_string));
                    if (s == NULL) return -1;
                    s->name = apr_psprintf(msr->mp, "%d", i);
                    if (s->name == NULL) return -1;
                    s->name_len = strlen(s->name);
                    s->value = apr_pstrmemdup(msr->mp, match, length);
                    if (s->value == NULL) return -1;
                    s->value_len = length;

                    apr_table_setn(msr->tx_vars, s->name, (void*)s);

                    if (msr->txcfg->debuglog_level >= 9) {
                        msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
                            log_escape_nq_ex(msr->mp, s->value, s->value_len));
                    }

                    if ((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) {
                        qspos = apr_psprintf(msr->mp, "%s", var->name);
                        parm = strstr(qspos, ":");
                        if (parm != NULL) {
                            parm++;
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp, s->value, s->value_len);
                            mparm->pad_1 = rule->actionset->arg_min;
                            mparm->pad_2 = rule->actionset->arg_max;
                            apr_table_addn(msr->pattern_to_sanitize, parm, (void*)mparm);
                        }
                        else {
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp, s->value, s->value_len);
                            apr_table_addn(msr->pattern_to_sanitize, qspos, (void*)mparm);
                        }
                    }

                }
            }

            /* Unset the remaining TX vars (from previous invocations). */
            for (; i <= 9; i++) {
                char buf[24];
                apr_snprintf(buf, sizeof(buf), "%i", i);
                apr_table_unset(msr->tx_vars, buf);
            }

            break;
        }
    }

    if (is_cpf) {
        /* Match. */

        /* This message will be logged. */
        *error_msg = apr_psprintf(msr->mp, "CPF# match \"%s\" at %s. [offset \"%d\"]",
            regex->pattern, var->name, offset);

        return 1;
    }

    /* No match. */
    return 0;
}

/**
 * \brief Check for a valid SSN
 *
 * \param msr ModSecurity transaction source
 * \param ssnumber Pointer to ssn
 * \param len ssn length
 *
 * \retval 0 On Invalid SSN
 * \retval 1 On Valid SSN
 */
static int ssn_verify(modsec_rec *msr, const char *ssnumber, int len) {
    assert(msr != NULL);
    assert(ssnumber != NULL);
    int i;
    int num[9];
    int digits = 0;
    int area, serial, grp;
    int sequencial = 0;
    int repetitions = 0;
    char *str_area;
    char *str_grp;
    char *str_serial;

    for (i = 0; i < len; i++) {
        if (apr_isdigit(ssnumber[i])) {
            if (digits < 9)
                num[digits] = convert_to_int(ssnumber[i]);
            digits++;
        }
    }

    /* Not a valid number */
    if (digits != 9)
        goto invalid;

    for (i=0; i < 8; i++)   {
        if (num[i] == (num[i+1]-1))
            sequencial++;

        if (num[i] == num[i+1])
            repetitions++;
    }

    /* We are blocking when all numbers were sequencial or repeated */
    if (sequencial == 8)
        goto invalid;

    if (repetitions == 8)
        goto invalid;

    str_area = apr_psprintf(msr->mp,"%d%d%d",num[0],num[1],num[2]);
    str_grp = apr_psprintf(msr->mp,"%d%d",num[3],num[4]);
    str_serial = apr_psprintf(msr->mp,"%d%d%d%d",num[5],num[6],num[7],num[8]);

    if(str_area == NULL || str_grp == NULL || str_serial == NULL)
        goto invalid;

    area = atoi(str_area);
    grp = atoi(str_grp);
    serial = atoi(str_serial);

    /* Cannot has seroed fields */
    if (area == 0 || serial == 0 || grp == 0)
        goto invalid;

    /* More tests */
    if (area >= 740 || area == 666)
        goto invalid;

    return 1;

invalid:
    return 0;
}

/**
* \brief Init function to SSN operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 0 On Failure
* \retval 1 On Success
*/
static int msre_op_verifySSN_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    const char *errptr = NULL;
    int erroffset;
    int options = 0;
    msc_regex_t *regex;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

#ifndef WITH_PCRE
    options = PCRE2_DOTALL | PCRE2_MULTILINE;
#else
    options = PCRE_DOTALL | PCRE_MULTILINE;
#endif
    /* Compile rule->op_param */
    regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, options, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
    if (regex == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
            erroffset, errptr);
        return 0;
    }

    rule->op_param_data = regex;

    return 1; /* OK */
}

/**
* \brief Execution function to SSN operator
*
* \param msr ModSecurity transaction resource
* \param rule ModSecurity rule struct
* \param var ModSecurity variable struct
* \param error_msg Error message
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_verifySSN_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
    const char *target;
    unsigned int target_length;
    char *my_error_msg = NULL;
    int ovector[33];
    int rc;
    int is_ssn = 0;
    int offset;
    int options = 0;
    int matched_bytes = 0;
    char *qspos = NULL;
    const char *parm = NULL;
    msc_parm *mparm = NULL;
    #ifdef WITH_PCRE_STUDY
       #ifdef WITH_PCRE_JIT
    int jit = 0;
       #endif
    #endif


    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if (regex == NULL) {
        *error_msg = "Internal Error: regex data is null.";
        return -1;
    }

    assert(rule->actionset != NULL);

    memset(ovector, 0, sizeof(ovector));

    #ifdef WITH_PCRE_STUDY
        #ifdef WITH_PCRE_JIT
    if (msr->txcfg->debuglog_level >= 4) {
            #ifndef WITH_PCRE
        msc_op_set_jitrc(regex, &rc, &jit);
            #else
        rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
            #endif
        if ((rc != 0) || (jit != 1)) {
            *error_msg = apr_psprintf(msr->mp,
                    "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
                    "Execution error - "
                    "Does not support JIT (%d)",
                    rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
                            (rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
                    rule->filename != NULL ? rule->filename : "-",
                    rule->line_num,rc);
            msr_log(msr, 4, "%s.", *error_msg);
        }
    }
        #endif
    #endif

    /* If the given target is null run against an empty
     * string. This is a behaviour consistent with previous
     * releases.
     */
    if (var->value == NULL) {
        target = "";
        target_length = 0;
    } else {
        target = var->value;
        target_length = var->value_len;
    }

    for (offset = 0; ((unsigned int)offset < target_length) && (is_ssn == 0); offset++) {
        if (msr->txcfg->debuglog_level >= 9) {
            if (offset > 0) {
                msr_log(msr, 9, "Continuing SSN# search at target offset %d.", offset);
            }
        }

#ifndef WITH_PCRE
        options = PCRE2_NOTEMPTY;
#else
        options = PCRE_NOTEMPTY;
#endif
        rc = msc_regexec_ex(regex, target, target_length, offset, options, ovector, 30, &my_error_msg);

        /* If there was no match, then we are done. */
#ifndef WITH_PCRE
        if (rc == PCRE2_ERROR_NOMATCH) {
#else
        if (rc == PCRE_ERROR_NOMATCH) {
#endif
            break;
        }

        if (rc < -1) {
            *error_msg = apr_psprintf(msr->mp, "SSN# regex execution failed: %s", my_error_msg);
            return -1;
        }

        /* Verify a match. */
        if (rc > 0) {
            const char *match = target + ovector[0];
            int length = ovector[1] - ovector[0];
            int i = 0;

            offset = ovector[2*i];

            /* Check SSN using the match string */
            is_ssn = ssn_verify(msr, match, length);

            /* Not a SSN number, then try another match where we left off. */
            if (!is_ssn) {
                if (msr->txcfg->debuglog_level >= 9) {
                    msr_log(msr, 9, "SSN# check failed at target offset %d: \"%.*s\"", offset, length, match);
                }

                continue;
            }

            /* We have a potential SSN number and need to set any captures
             * and we are done.
             */

            matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
            if(!matched_bytes)
                matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;

            if (apr_table_get(rule->actionset->actions, "capture")) {
                for(; i < rc; i++) {
                    msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
                    if (s == NULL) return -1;
                    s->name = apr_psprintf(msr->mp, "%d", i);
                    if (s->name == NULL) return -1;
                    s->name_len = strlen(s->name);
                    s->value = apr_pstrmemdup(msr->mp, match, length);
                    if (s->value == NULL) return -1;
                    s->value_len = length;

                    apr_table_setn(msr->tx_vars, s->name, (void *)s);

                    if (msr->txcfg->debuglog_level >= 9) {
                        msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
                            log_escape_nq_ex(msr->mp, s->value, s->value_len));
                    }

                    if((matched_bytes == 1) && (var != NULL) && (var->name != NULL))    {
                        qspos = apr_psprintf(msr->mp, "%s", var->name);
                        parm = strstr(qspos, ":");
                        if (parm != NULL)   {
                            parm++;
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                            mparm->pad_1 = rule->actionset->arg_min;
                            mparm->pad_2 = rule->actionset->arg_max;
                            apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
                        } else  {
                            mparm = apr_palloc(msr->mp, sizeof(msc_parm));
                            if (mparm == NULL)
                                continue;

                            mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
                            apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
                        }
                    }

                }
            }

            /* Unset the remaining TX vars (from previous invocations). */
            for(; i <= 9; i++) {
                char buf[24];
                apr_snprintf(buf, sizeof(buf), "%i", i);
                apr_table_unset(msr->tx_vars, buf);
            }

            break;
        }
    }

    if (is_ssn) {
        /* Match. */

        /* This message will be logged. */
        *error_msg = apr_psprintf(msr->mp, "SSN# match \"%s\" at %s. [offset \"%d\"]",
            regex->pattern, var->name, offset);

        return 1;
    }

    /* No match. */
    return 0;
}

/**
 * Perform geograpical lookups on an IP/Host.
 */
static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    geo_rec rec;
    geo_db *geo = msr->txcfg->geo;
    const char *geo_host = var->value;
    msc_string *s = NULL;
    int rc;

    *error_msg = NULL;

    if (geo == NULL) {
        msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database.  Set SecGeoLookupDB.", log_escape(msr->mp, geo_host));
        return 0;
    }


    rc = geo_lookup(msr, &rec, geo_host, error_msg);
    if (rc <= 0) {
        if (! *error_msg) {
            *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed at %s.", log_escape_nq(msr->mp, geo_host), var->name);
        }
        apr_table_clear(msr->geo_vars);
        return rc;
    }
    if (! *error_msg) {
        *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded at %s.",
            log_escape_nq(msr->mp, geo_host), var->name);
    }

    if (msr->txcfg->debuglog_level >= 9) {
        msr_log(msr, 9, "GEO: %s={country_code=%s, country_code3=%s, country_name=%s, country_continent=%s, region=%s, city=%s, postal_code=%s, latitude=%f, longitude=%f, dma_code=%d, area_code=%d}",
                geo_host,
                rec.country_code,
                rec.country_code3,
                rec.country_name,
                rec.country_continent,
                rec.region,
                rec.city,
                rec.postal_code,
                rec.latitude,
                rec.longitude,
                rec.dma_code,
                rec.area_code);
    }

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "COUNTRY_CODE");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.country_code ? rec.country_code : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "COUNTRY_CODE3");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.country_code3 ? rec.country_code3 : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "COUNTRY_NAME");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.country_name ? rec.country_name : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "COUNTRY_CONTINENT");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.country_continent ? rec.country_continent : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "REGION");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.region ? rec.region : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "CITY");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.city ? rec.city : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "POSTAL_CODE");
    s->name_len = strlen(s->name);
    s->value = apr_pstrdup(msr->mp, rec.postal_code ? rec.postal_code : "");
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "LATITUDE");
    s->name_len = strlen(s->name);
    s->value = apr_psprintf(msr->mp, "%f", rec.latitude);
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "LONGITUDE");
    s->name_len = strlen(s->name);
    s->value = apr_psprintf(msr->mp, "%f", rec.longitude);
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "DMA_CODE");
    s->name_len = strlen(s->name);
    s->value = apr_psprintf(msr->mp, "%d", rec.dma_code);
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
    s->name = apr_pstrdup(msr->mp, "AREA_CODE");
    s->name_len = strlen(s->name);
    s->value = apr_psprintf(msr->mp, "%d", rec.area_code);
    s->value_len = strlen(s->value);
    apr_table_setn(msr->geo_vars, s->name, (void *)s);

    return 1;
}

/* rbl */

static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
    assert(msr != NULL);
    assert(rule != NULL);
    assert(rule->actionset != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    unsigned int h0, h1, h2, h3;
    unsigned int high8bits = 0;
    char *name_to_check = NULL;
    char *target = NULL;
    apr_sockaddr_t *sa = NULL;
    apr_status_t rc;
    int capture = 0;

    *error_msg = NULL;

    if (rule->actionset->actions)
        capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
    else capture = 0;

    /* ENH Add IPv6 support. */

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;

    /* Construct the host name we want to resolve. */
    if (sscanf(target, "%d.%d.%d.%d", &h0, &h1, &h2, &h3) == 4) {
        /* IPv4 address */
        /* If we're using the httpBl blocklist, we need to add the key */
        if(strstr(rule->op_param,"httpbl.org"))  {
            if (msr->txcfg->httpBlkey == NULL) {
                if (msr->txcfg->debuglog_level >= 4) {
                    msr_log(msr, 4, "RBL httpBl called but no key defined: set SecHttpBlKey");
                }
                *error_msg = "RBL httpBl called but no key defined: set SecHttpBlKey";
            } else {
                name_to_check = apr_psprintf(msr->mp, "%s.%d.%d.%d.%d.%s", msr->txcfg->httpBlkey, h3, h2, h1, h0, rule->op_param);
            }
        } else {
            /* regular IPv4 RBLs */
            name_to_check = apr_psprintf(msr->mp, "%d.%d.%d.%d.%s", h3, h2, h1, h0, rule->op_param);
        }
    } else {
        /* Assume the input is a domain name. */
        name_to_check = apr_psprintf(msr->mp, "%s.%s", target, rule->op_param);
    }

    if (name_to_check == NULL) return -1;

    rc = apr_sockaddr_info_get(&sa, name_to_check,
        APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp);
    if (rc == APR_SUCCESS) {

        high8bits = sa->sa.sin.sin_addr.s_addr >> 24;

        /* multi.uribl.com */

        if(strstr(rule->op_param,"uribl.com"))  {

            switch(high8bits)   {
                case 2:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (BLACK).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
                case 4:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (GREY).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
                case 8:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (RED).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
                case 14:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (BLACK,GREY,RED).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
                case 255:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (DNS IS BLOCKED).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
                default:
                    *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (WHITE).",
                            log_escape_nq(msr->mp, name_to_check), var->name);
                    break;
            }

            set_match_to_tx(msr, capture, *error_msg, 0);

        } else
            if(strstr(rule->op_param,"spamhaus.org"))  {

                switch(high8bits)   {
                    case 2:
                    case 3:
                        *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Static UBE sources).",
                                log_escape_nq(msr->mp, name_to_check), var->name);
                        break;
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Illegal 3rd party exploits).",
                                log_escape_nq(msr->mp, name_to_check), var->name);
                        break;
                    case 10:
                    case 11:
                        *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Delivering unauthenticated SMTP email).",
                                log_escape_nq(msr->mp, name_to_check), var->name);
                        break;
                    default:
                        *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.",
                                log_escape_nq(msr->mp, name_to_check), var->name);
                        break;
                }

            set_match_to_tx(msr, capture, *error_msg, 0);

            } else
                if(strstr(rule->op_param,"httpbl.org"))  {
                   char *respBl;
                   int first, days, score, type;

                   respBl = inet_ntoa(sa->sa.sin.sin_addr);
                   if (sscanf(respBl, "%d.%d.%d.%d", &first, &days, &score, &type) != 4) {
                       *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s failed: bad response", log_escape_nq(msr->mp, name_to_check));
                   } else {
                       if (first != 127) {
                           *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s failed: bad response", log_escape_nq(msr->mp, name_to_check));
                       }
                       else {
                           char *ptype;
                           switch(type) {
                             case 0:
                               ptype = "Search Engine";
                               break;
                             case 1:
                               ptype = "Suspicious IP";
                               break;
                             case 2:
                               ptype = "Harvester IP";
                               break;
                             case 3:
                               ptype = "Suspicious harvester IP";
                               break;
                             case 4:
                               ptype = "Comment spammer IP";
                               break;
                             case 5:
                               ptype = "Suspicious comment spammer IP";
                               break;
                             case 6:
                               ptype = "Harvester and comment spammer IP";
                               break;
                             case 7:
                               ptype = "Suspicious harvester comment spammer IP";
                               break;
                             default:
                               ptype = " ";
                           }
                           *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s. %s: %d days since last activity, threat score %d",
                               log_escape_nq(msr->mp, name_to_check), var->name,
                               ptype, days, score);
                       }
                   }
                set_match_to_tx(msr, capture, *error_msg, 0);
                /* end of httpBl code */
            } else  {
                *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.",
                        log_escape_nq(msr->mp, name_to_check), var->name);

            set_match_to_tx(msr, capture, *error_msg, 0);

            }

        return 1; /* Match. */
    }

    if (msr->txcfg->debuglog_level >= 5) {
        msr_log(msr, 5, "RBL lookup of %s failed at %s.", log_escape_nq(msr->mp, name_to_check), var->name);
    }

    /* No match. */
    return 0;
}

/* fuzzyHash */
static int msre_op_fuzzy_hash_init(msre_rule *rule, char **error_msg)
{
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_fuzzy_hash_init error_msg is NULL");
        return -1;
    }
#ifdef WITH_SSDEEP
    struct fuzzy_hash_param_data *param_data;
    struct fuzzy_hash_chunk *chunk, *t;
    FILE *fp;
    char *file;
    int param_len,threshold;
    char line[1024];

    char *data = NULL;
    char *threshold_str = NULL;

    param_data = apr_palloc(rule->ruleset->mp,
        sizeof(struct fuzzy_hash_param_data));

    param_data->head = NULL;

    data = apr_pstrdup(rule->ruleset->mp, rule->op_param);
    threshold_str = data;
#endif

    *error_msg = NULL;

#ifdef WITH_SSDEEP
    /* Sanity check */
    param_len = strlen(threshold_str);
    threshold_str = threshold_str + param_len;

    if (param_len < 3)
    {
        goto invalid_parameters;
    }

    while (param_len - 1 > 0 && *threshold_str != ' ')
    {
        param_len--;
        threshold_str--;
    }

    *threshold_str = '\0';
    threshold_str++;
    file = data;
    threshold = atoi(threshold_str);

    if ((file == NULL) || (is_empty_string(file)) || (threshold > 100) ||
        (threshold < 1))
    {
        goto invalid_parameters;
    }

    file = resolve_relative_path(rule->ruleset->mp, rule->filename, file);

    fp = fopen(file, "r");
    if (!fp)
    {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Not able to open file:" \
            " %s.", file);
        return -1;
    }

    while (read_line(line, sizeof(line), fp))
    {
        chunk = apr_palloc(rule->ruleset->mp,
            sizeof(struct fuzzy_hash_chunk));

        chunk->data = apr_pstrdup(rule->ruleset->mp, line);
        chunk->next = NULL;

        if (param_data->head == NULL) {
            param_data->head = chunk;
        } else {
            t = param_data->head;

            while (t->next) {
                t = t->next;
            }

            t->next = chunk;
        }
    }

    fclose(fp);

    param_data->file = file;
    param_data->threshold = threshold;

    rule->op_param_data = param_data;
#else
    rule->op_param_data = NULL;

    return 1;
#endif
    return 1;

invalid_parameters:
    *error_msg = apr_psprintf(rule->ruleset->mp, "Operator @fuzzyHash " \
        "requires valid parameters. File and threshold.");
    return -1;
}

static int msre_op_fuzzy_hash_execute(modsec_rec *msr, msre_rule *rule,
    msre_var *var, char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
#ifdef WITH_SSDEEP
    char result[FUZZY_MAX_RESULT];
    struct fuzzy_hash_param_data *param = rule->op_param_data;
    struct fuzzy_hash_chunk *chunk = param->head;
#endif

    *error_msg = NULL;

#ifdef WITH_SSDEEP
    if (fuzzy_hash_buf(var->value, var->value_len, result))
    {
        *error_msg = apr_psprintf(msr->mp, "Problems generating " \
            "fuzzy hash.");

        return -1;
    }

    while (chunk != NULL)
    {
        int i = fuzzy_compare(chunk->data, result);
        msr_log(msr, 9, "%d (%s)", i, chunk->data);
        if (i >= param->threshold)
        {
            *error_msg = apr_psprintf(msr->mp, "Fuzzy hash of %s matched " \
                "with %s (from: %s). Score: %d.", var->name, chunk->data,
                param->file, i);
            return 1;
        }
        chunk = chunk->next;
    }
#else
    *error_msg = apr_psprintf(msr->mp, "ModSecurity was not " \
        "compiled with ssdeep support.");

    return -1;

#endif

    /* No match. */
    return 0;
}

/* inspectFile */

static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    if (error_msg == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_EMERG, 0, rule->ruleset->mp, "msre_op_inspectFile_init: error_msg is NULL");
        return -1;
    }
    char *filename = (char *)rule->op_param;

    *error_msg = NULL;

    if ((filename == NULL)||(is_empty_string(filename))) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Operator @inspectFile requires parameter.");
        return -1;
    }

    filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename);

#if defined(WITH_LUA)
    /* ENH Write & use string_ends(s, e). */
    if (strlen(rule->op_param) > 4) {
        char *p = filename + strlen(filename) - 4;
        if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a'))
        {
            msc_script *script = NULL;

            /* Compile script. */
            *error_msg = lua_compile(&script, filename, rule->ruleset->mp);
            if (*error_msg != NULL) return -1;

            rule->op_param_data = script;
        }
    }
    #endif

    if (rule->op_param_data == NULL) {
        /* ENH Verify the script exists and that we have
         * the rights to execute it.
         */
    }

    return 1;
}

static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    *error_msg = NULL;

    if (rule->op_param_data == NULL) {
        /* Execute externally, as native binary/shell script. */
        char *script_output = NULL;
        char const *argv[5];
        const char *approver_script = rule->op_param;
        const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len);

        if (msr->txcfg->debuglog_level >= 4) {
            msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file);
        }

        argv[0] = approver_script;
        argv[1] = target_file;
        argv[2] = NULL;

        if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) {
            *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).",
                log_escape(msr->mp, approver_script));
            return -1;
        }

        if (script_output == NULL) {
            *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).",
                log_escape(msr->mp, approver_script));
            return -1;
        }

        if (script_output[0] != '1') {
            *error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s",
                log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script),
                log_escape_nq(msr->mp,  script_output));
            return 1; /* Match. */
        }
    }
    #if defined(WITH_LUA)
    else {
        /* Execute internally, as Lua script. */
        char *target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
        msc_script *script = (msc_script *)rule->op_param_data;
        int rc;

        rc = lua_execute(script, target, msr, rule, error_msg);
        if (rc < 0) {
            /* Error. */
            return -1;
        }

        return rc;
    }
    #endif

    /* No match. */
    return 0;
}

/* validateByteRange */

static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) {
    assert(rule != NULL);
    assert(rule->ruleset != NULL);
    assert(error_msg != NULL);
    char *p = NULL, *saveptr = NULL;
    char *table = NULL, *data = NULL;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if (rule->op_param == NULL) {
        *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for validateByteRange.");
        return -1;
    }

    /* Initialise. */
    data = apr_pstrdup(rule->ruleset->mp, rule->op_param);
    rule->op_param_data = apr_pcalloc(rule->ruleset->mp, 32);
    if ((data == NULL)||(rule->op_param_data == NULL)) return -1;
    table = rule->op_param_data;

    /* Extract parameters and update table. */
    p = apr_strtok(data, ",", &saveptr);
    while(p != NULL) {
        char *s = strstr(p, "-");
        if (s == NULL) {
            /* Single value. */
            int x = atoi(p);
            if ((x < 0)||(x > 255)) {
                *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %d", x);
                return 0;
            }
            table[x>>3] = (table[x>>3] | (1 << (x & 0x7)));
        } else {
            /* Range. */
            int start = atoi(p);
            int end = atoi(s + 1);

            if ((start < 0)||(start > 255)) {
                *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %d",
                    start);
                return 0;
            }
            if ((end < 0)||(end > 255)) {
                *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %d", end);
                return 0;
            }
            if (start > end) {
                *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %d-%d", start, end);
                return 0;
            }

            while(start <= end) {
                table[start >> 3] = (table[start >> 3] | (1 << (start & 0x7)));
                start++;
            }
        }

        p = apr_strtok(NULL, ",", &saveptr);
    }

    return 1;
}

static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    char *table = rule->op_param_data;
    unsigned int i, count;

    *error_msg = NULL;

    if (table == NULL) {
        *error_msg = apr_psprintf(msr->mp, "Internal Error: validateByteRange table not "
            "initialised.");
        return -1;
    }

    /* Check every byte of the target to detect characters that are not allowed. */

    count = 0;
    for(i = 0; i < var->value_len; i++) {
        int x = ((unsigned char *)var->value)[i];
        if (!(table[x >> 3] & (1 << (x & 0x7)))) {
            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Value %d in %s outside range: %s", x, var->name, rule->op_param);
            }
            count++;
        }
    }

    if (count == 0) return 0; /* Valid - no match. */

    *error_msg = apr_psprintf(msr->mp, "Found %d byte(s) in %s outside range: %s.",
        count, var->name, rule->op_param);

    return 1; /* Invalid - match.*/
}

/* validateUrlEncoding */

static int validate_url_encoding(const char *input, long int input_length) {
    int i;

    if ((input == NULL)||(input_length < 0)) return -1;

    i = 0;
    while (i < input_length) {
        if (input[i] == '%') {
            if (i + 2 >= input_length) {
                /* Not enough bytes. */
                return -3;
            }
            else {
                /* Here we only decode a %xx combination if it is valid,
                 * leaving it as is otherwise.
                 */
                char c1 = input[i + 1];
                char c2 = input[i + 2];

                if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F')))
                    && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) )
                {
                    i += 3;
                } else {
                    /* Non-hexadecimal characters used in encoding. */
                    return -2;
                }
            }
        } else {
            i++;
        }
    }

    return 1;
}

static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    int rc = validate_url_encoding(var->value, var->value_len);
    switch(rc) {
        case 1 :
            /* Encoding is valid */
            *error_msg = apr_psprintf(msr->mp, "Valid URL Encoding at %s.", var->name);
            break;
        case -2 :
            *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal "
                "digits used at %s.", var->name);
            return 1; /* Invalid match. */
            break;
        case -3 :
            *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters "
                "at the end of input at %s.", var->name);
            return 1; /* Invalid match. */
            break;
        case -1 :
        default :
            *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %d) at %s", rc, var->name);
            return -1;
            break;

    }

    /* No match. */
    return 0;
}

/* validateUtf8Encoding */

/* NOTE: This is over-commented for ease of verification */
static int detect_utf8_character(const unsigned char *p_read, unsigned int length) {
    int unicode_len = 0;
    unsigned int d = 0;
    unsigned char c;

    if (p_read == NULL) return UNICODE_ERROR_DECODING_ERROR;
    c = *p_read;

    /* If first byte begins with binary 0 it is single byte encoding */
    if ((c & 0x80) == 0) {
        /* single byte unicode (7 bit ASCII equivilent) has no validation */
        return 1;
    }
    /* If first byte begins with binary 110 it is two byte encoding*/
    else if ((c & 0xE0) == 0xC0) {
        /* check we have at least two bytes */
        if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
        /* check second byte starts with binary 10 */
        else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        else {
            unicode_len = 2;
            /* compute character number */
            d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F);
        }
    }
    /* If first byte begins with binary 1110 it is three byte encoding */
    else if ((c & 0xF0) == 0xE0) {
        /* check we have at least three bytes */
        if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
        /* check second byte starts with binary 10 */
        else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        /* check third byte starts with binary 10 */
        else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        else {
            unicode_len = 3;
            /* compute character number */
            d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F);
        }
    }
    /* If first byte begins with binary 11110 it is four byte encoding */
    else if ((c & 0xF8) == 0xF0) {
        /* restrict characters to UTF-8 range (U+0000 - U+10FFFF)*/
        if (c >= 0xF5) {
            return UNICODE_ERROR_RESTRICTED_CHARACTER;
        }
        /* check we have at least four bytes */
        if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
        /* check second byte starts with binary 10 */
        else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        /* check third byte starts with binary 10 */
        else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        /* check forth byte starts with binary 10 */
        else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
        else {
            unicode_len = 4;
            /* compute character number */
            d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) << 6) | (*(p_read + 3) & 0x3F);
        }
    }
    /* any other first byte is invalid (RFC 3629) */
    else {
        return UNICODE_ERROR_INVALID_ENCODING;
    }

    /* invalid UTF-8 character number range (RFC 3629) */
    if ((d >= 0xD800) && (d <= 0xDFFF)) {
        return UNICODE_ERROR_RESTRICTED_CHARACTER;
    }

    /* check for overlong */
    if ((unicode_len == 4) && (d < 0x010000)) {
        /* four byte could be represented with less bytes */
        return UNICODE_ERROR_OVERLONG_CHARACTER;
    }
    else if ((unicode_len == 3) && (d < 0x0800)) {
        /* three byte could be represented with less bytes */
        return UNICODE_ERROR_OVERLONG_CHARACTER;
    }
    else if ((unicode_len == 2) && (d < 0x80)) {
        /* two byte could be represented with less bytes */
        return UNICODE_ERROR_OVERLONG_CHARACTER;
    }

    return unicode_len;
}

static int msre_op_validateUtf8Encoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    unsigned int i, bytes_left;

    bytes_left = var->value_len;

    for(i = 0; i < var->value_len;) {
        int rc = detect_utf8_character((unsigned char *)&var->value[i], bytes_left);

        switch(rc) {
            case UNICODE_ERROR_CHARACTERS_MISSING :
                *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
                    "not enough bytes in character "
                    "at %s. [offset \"%d\"]", var->name, i);
                return 1;
                break;
            case UNICODE_ERROR_INVALID_ENCODING :
                *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
                    "invalid byte value in character "
                    "at %s. [offset \"%d\"]", var->name, i);
                return 1;
                break;
            case UNICODE_ERROR_OVERLONG_CHARACTER :
                *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
                    "overlong character detected "
                    "at %s. [offset \"%d\"]", var->name, i);
                return 1;
                break;
            case UNICODE_ERROR_RESTRICTED_CHARACTER :
                *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
                    "use of restricted character "
                    "at %s. [offset \"%d\"]", var->name, i);
                return 1;
                break;
            case UNICODE_ERROR_DECODING_ERROR :
                *error_msg = apr_psprintf(msr->mp, "Error validating UTF-8 decoding "
                    "at %s. [offset \"%d\"]", var->name, i);
                return 1;
                break;
        }

        if (rc <= 0) {
            *error_msg = apr_psprintf(msr->mp, "Internal error during UTF-8 validation "
                "at %s.", var->name);
            return 1;
        }

        i += rc;
        bytes_left -= rc;
    }

    return 0;
}

/* eq */

static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string str;
    int left, right;
    char *target = NULL;

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    str.value = (char *)rule->op_param;
    str.value_len = strlen(str.value);

    expand_macros(msr, &str, rule, msr->mp);

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;
    left = atoi(target);
    right = atoi(str.value);

    if (left != right) {
        /* No match. */
        return 0;
    }
    else {
        *error_msg = apr_psprintf(msr->mp, "Operator EQ matched %d at %s.", right, var->name);
        /* Match. */
        return 1;
    }
}

/* gt */

static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string str;
    int left, right;
    char *target = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    str.value = (char *)rule->op_param;
    str.value_len = strlen(str.value);

    expand_macros(msr, &str, rule, msr->mp);

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;
    left = atoi(target);
    right = atoi(str.value);

    if (left <= right) {
        /* No match. */
        return 0;
    }
    else {
        *error_msg = apr_psprintf(msr->mp, "Operator GT matched %d at %s.", right, var->name);
        /* Match. */
        return 1;
    }
}

/* lt */

static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string str;
    int left, right;
    char *target = NULL;

    *error_msg = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    str.value = (char *)rule->op_param;
    str.value_len = strlen(str.value);

    expand_macros(msr, &str, rule, msr->mp);

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;
    left = atoi(target);
    right = atoi(str.value);

    if (left >= right) {
        /* No match. */
        return 0;
    }
    else {
        *error_msg = apr_psprintf(msr->mp, "Operator LT matched %d at %s.", right, var->name);
        /* Match. */
        return 1;
    }
}

/* ge */

static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string str;
    int left, right;
    char *target = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    if (error_msg == NULL) return -1;
    *error_msg = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    str.value = (char *)rule->op_param;
    str.value_len = strlen(str.value);

    expand_macros(msr, &str, rule, msr->mp);

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;
    left = atoi(target);
    right = atoi(str.value);

    if (left < right) {
        /* No match. */
        return 0;
    }
    else {
        *error_msg = apr_psprintf(msr->mp, "Operator GE matched %d at %s.", right, var->name);
        /* Match. */
        return 1;
    }
}

/* le */

static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
    char **error_msg)
{
    assert(msr != NULL);
    assert(rule != NULL);
    assert(var != NULL);
    assert(error_msg != NULL);
    msc_string str;
    int left, right;
    char *target = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    *error_msg = NULL;

    if ((var->value == NULL)||(rule->op_param == NULL)) {
        /* NULL values do not match anything. */
        return 0;
    }

    str.value = (char *)rule->op_param;
    str.value_len = strlen(str.value);

    expand_macros(msr, &str, rule, msr->mp);

    target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
    if (target == NULL) return -1;
    left = atoi(target);
    right = atoi(str.value);

    if (left > right) {
        /* No match. */
        return 0;
    }
    else {
        *error_msg = apr_psprintf(msr->mp, "Operator LE matched %d at %s.", right, var->name);
        /* Match. */
        return 1;
    }
}

/* -------------------------------------------------------------------------- */

/**
 *
 */
void msre_engine_register_default_operators(msre_engine *engine) {
    /* unconditionalMatch */
    msre_engine_op_register(engine,
        "unconditionalMatch",
        NULL,
        msre_op_unconditionalmatch_execute
    );

    /* noMatch */
    msre_engine_op_register(engine,
        "noMatch",
        NULL,
        msre_op_nomatch_execute
    );

    /* ipmatch */
    msre_engine_op_register(engine,
        "ipmatch",
        msre_op_ipmatch_param_init,
        msre_op_ipmatch_execute
    );

    /* ipmatchFromFile */
    msre_engine_op_register(engine,
        "ipmatchFromFile",
        msre_op_ipmatchFromFile_param_init,
        msre_op_ipmatchFromFile_execute
    );
    /* ipmatchf */
    msre_engine_op_register(engine,
        "ipmatchf",
        msre_op_ipmatchFromFile_param_init,
        msre_op_ipmatchFromFile_execute
    );

    /* rsub */
#if !defined(MSC_TEST)
    msre_engine_op_register(engine,
        "rsub",
        msre_op_rsub_param_init,
        msre_op_rsub_execute
    );
#endif /* MSC_TEST */

    /* rx */
    msre_engine_op_register(engine,
        "rx",
        msre_op_rx_param_init,
        msre_op_rx_execute
    );

    /* validateEncyption */
    msre_engine_op_register(engine,
        "validateHash",
        msre_op_validateHash_param_init,
        msre_op_validateHash_execute
    );

    /* pm */
    msre_engine_op_register(engine,
        "pm",
        msre_op_pm_param_init,
        msre_op_pm_execute
    );

    /* pmFromFile */
    msre_engine_op_register(engine,
        "pmFromFile",
        msre_op_pmFromFile_param_init,
        msre_op_pm_execute
    );

    /* pmf */
    msre_engine_op_register(engine,
        "pmf",
        msre_op_pmFromFile_param_init,
        msre_op_pm_execute
    );

    /* within */
    msre_engine_op_register(engine,
        "within",
        NULL, /* ENH init function to flag var substitution */
        msre_op_within_execute
    );

    /* contains */
    msre_engine_op_register(engine,
        "contains",
        NULL, /* ENH init function to flag var substitution */
        msre_op_contains_execute
    );

    /* containsWord */
    msre_engine_op_register(engine,
        "containsWord",
        NULL, /* ENH init function to flag var substitution */
        msre_op_containsWord_execute
    );

    /* detectSQLi */
    msre_engine_op_register(engine,
        "detectSQLi",
         NULL,
         msre_op_detectSQLi_execute
    );

    /* detectXSS */
    msre_engine_op_register(engine,
        "detectXSS",
         NULL,
         msre_op_detectXSS_execute
    );

    /* streq */
    msre_engine_op_register(engine,
        "streq",
        NULL, /* ENH init function to flag var substitution */
        msre_op_streq_execute
    );

    /* beginsWith */
    msre_engine_op_register(engine,
        "beginsWith",
        NULL, /* ENH init function to flag var substitution */
        msre_op_beginsWith_execute
    );

    /* endsWith */
    msre_engine_op_register(engine,
        "endsWith",
        NULL, /* ENH init function to flag var substitution */
        msre_op_endsWith_execute
    );

    /* strmatch */
    msre_engine_op_register(engine,
        "strmatch",
        msre_op_strmatch_param_init,
        msre_op_strmatch_execute
    );

    /* validateDTD */
    msre_engine_op_register(engine,
        "validateDTD",
        msre_op_validateDTD_init,
        msre_op_validateDTD_execute
    );

    /* validateSchema */
    msre_engine_op_register(engine,
        "validateSchema",
        msre_op_validateSchema_init,
        msre_op_validateSchema_execute
    );

    /* verifyCC */
    msre_engine_op_register(engine,
        "verifyCC",
        msre_op_verifyCC_init,
        msre_op_verifyCC_execute
    );

    /* verifyCPF */
    msre_engine_op_register(engine,
        "verifyCPF",
        msre_op_verifyCPF_init,
        msre_op_verifyCPF_execute
    );

    /* verifySSN */
    msre_engine_op_register(engine,
        "verifySSN",
        msre_op_verifySSN_init,
        msre_op_verifySSN_execute
    );

    /* geoLookup */
    msre_engine_op_register(engine,
        "geoLookup",
        NULL,
        msre_op_geoLookup_execute
    );

    /* gsbLookup */
    msre_engine_op_register(engine,
        "gsbLookup",
        msre_op_gsbLookup_param_init,
        msre_op_gsbLookup_execute
    );

    /* rbl */
    msre_engine_op_register(engine,
        "rbl",
        NULL, /* ENH init function to validate DNS server */
        msre_op_rbl_execute
    );

    /* inspectFile */
    msre_engine_op_register(engine,
        "inspectFile",
        msre_op_inspectFile_init,
        msre_op_inspectFile_execute
    );

    /* fuzzy_hash */
    msre_engine_op_register(engine,
        "fuzzyHash",
        msre_op_fuzzy_hash_init,
        msre_op_fuzzy_hash_execute
    );

    /* validateByteRange */
    msre_engine_op_register(engine,
        "validateByteRange",
        msre_op_validateByteRange_init,
        msre_op_validateByteRange_execute
    );

    /* validateUrlEncoding */
    msre_engine_op_register(engine,
        "validateUrlEncoding",
        NULL,
        msre_op_validateUrlEncoding_execute
    );

    /* validateUtf8Encoding */
    msre_engine_op_register(engine,
        "validateUtf8Encoding",
        NULL,
        msre_op_validateUtf8Encoding_execute
    );

    /* eq */
    msre_engine_op_register(engine,
        "eq",
        NULL,
        msre_op_eq_execute
    );

    /* gt */
    msre_engine_op_register(engine,
        "gt",
        NULL,
        msre_op_gt_execute
    );

    /* lt */
    msre_engine_op_register(engine,
        "lt",
        NULL,
        msre_op_lt_execute
    );

    /* le */
    msre_engine_op_register(engine,
        "le",
        NULL,
        msre_op_le_execute
    );

    /* ge */
    msre_engine_op_register(engine,
        "ge",
        NULL,
        msre_op_ge_execute
    );
}
