/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 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 <limits.h>

#include "modsecurity.h"
#include "msc_logging.h"
#include "msc_util.h"
#include "http_log.h"
#include "apr_lib.h"
#include "acmp.h"
#include "msc_crypt.h"

#if defined(WITH_LUA)
#include "msc_lua.h"
#endif


/* -- Directory context creation and initialisation -- */

/**
 * Creates a fresh directory configuration.
 */
void *create_directory_config(apr_pool_t *mp, char *path)
{
    directory_config *dcfg = (directory_config *)apr_pcalloc(mp, sizeof(directory_config));
    if (dcfg == NULL) return NULL;

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Created directory config %pp path %s", dcfg, path);
    #endif

    dcfg->mp = mp;
    dcfg->is_enabled = NOT_SET;

    dcfg->reqbody_access = NOT_SET;
    dcfg->reqintercept_oe = NOT_SET;
    dcfg->reqbody_buffering = NOT_SET;
    dcfg->reqbody_inmemory_limit = NOT_SET;
    dcfg->reqbody_limit = NOT_SET;
    dcfg->reqbody_no_files_limit = NOT_SET;
    dcfg->resbody_access = NOT_SET;

    dcfg->debuglog_name = NOT_SET_P;
    dcfg->debuglog_level = NOT_SET;
    dcfg->debuglog_fd = NOT_SET_P;

    dcfg->of_limit = NOT_SET;
    dcfg->if_limit_action = NOT_SET;
    dcfg->of_limit_action = NOT_SET;
    dcfg->of_mime_types = NOT_SET_P;
    dcfg->of_mime_types_cleared = NOT_SET;

    dcfg->cookie_format = NOT_SET;
    dcfg->argument_separator = NOT_SET;
    dcfg->cookiev0_separator = NOT_SET_P;

    dcfg->rule_inheritance = NOT_SET;
    dcfg->rule_exceptions = apr_array_make(mp, 16, sizeof(rule_exception *));
    dcfg->hash_method = apr_array_make(mp, 16, sizeof(hash_method *));

    /* audit log variables */
    dcfg->auditlog_flag = NOT_SET;
    dcfg->auditlog_type = NOT_SET;
    #ifdef WITH_YAJL
    dcfg->auditlog_format = NOT_SET;
    #endif
    dcfg->max_rule_time = NOT_SET;
    dcfg->auditlog_dirperms = NOT_SET;
    dcfg->auditlog_fileperms = NOT_SET;
    dcfg->auditlog_name = NOT_SET_P;
    dcfg->auditlog2_name = NOT_SET_P;
    dcfg->auditlog_fd = NOT_SET_P;
    dcfg->auditlog2_fd = NOT_SET_P;
    dcfg->auditlog_storage_dir = NOT_SET_P;
    dcfg->auditlog_parts = NOT_SET_P;
    dcfg->auditlog_relevant_regex = NOT_SET_P;

    dcfg->ruleset = NULL;

    /* Upload */
    dcfg->tmp_dir = NOT_SET_P;
    dcfg->upload_dir = NOT_SET_P;
    dcfg->upload_keep_files = NOT_SET;
    dcfg->upload_validates_files = NOT_SET;
    dcfg->upload_filemode = NOT_SET;
    dcfg->upload_file_limit = NOT_SET;

    /* These are only used during the configuration process. */
    dcfg->tmp_chain_starter = NULL;
    dcfg->tmp_default_actionset = NULL;
    dcfg->tmp_rule_placeholders = NULL;

    /* Misc */
    dcfg->data_dir = NOT_SET_P;
    dcfg->webappid = NOT_SET_P;
    dcfg->sensor_id = NOT_SET_P;
    dcfg->httpBlkey = NOT_SET_P;

    /* Content injection. */
    dcfg->content_injection_enabled = NOT_SET;

    /* Stream inspection */
    dcfg->stream_inbody_inspection = NOT_SET;
    dcfg->stream_outbody_inspection = NOT_SET;

    /* Geo Lookups */
    dcfg->geo = NOT_SET_P;

    /* Gsb Lookups */
    dcfg->gsb = NOT_SET_P;

    /* Unicode Map */
    dcfg->u_map = NOT_SET_P;

    /* Cache */
    dcfg->cache_trans = NOT_SET;
    dcfg->cache_trans_incremental = NOT_SET;
    dcfg->cache_trans_min = NOT_SET;
    dcfg->cache_trans_max = NOT_SET;
    dcfg->cache_trans_maxitems = NOT_SET;

    /* Rule ids */
    dcfg->rule_id_htab = apr_hash_make(mp);
    dcfg->component_signatures = apr_array_make(mp, 16, sizeof(char *));

    dcfg->request_encoding = NOT_SET_P;
    dcfg->disable_backend_compression = NOT_SET;

    /* Collection timeout */
    dcfg->col_timeout = NOT_SET;
    dcfg->col_gc_freq = NOT_SET;

    dcfg->crypto_key = NOT_SET_P;
    dcfg->crypto_key_len = NOT_SET;
    dcfg->crypto_key_add = NOT_SET;
    dcfg->crypto_param_name = NOT_SET_P;
    dcfg->hash_is_enabled = NOT_SET;
    dcfg->hash_enforcement = NOT_SET;
    dcfg->crypto_hash_href_rx = NOT_SET;
    dcfg->crypto_hash_faction_rx = NOT_SET;
    dcfg->crypto_hash_location_rx = NOT_SET;
    dcfg->crypto_hash_iframesrc_rx = NOT_SET;
    dcfg->crypto_hash_framesrc_rx = NOT_SET;
    dcfg->crypto_hash_href_pm = NOT_SET;
    dcfg->crypto_hash_faction_pm = NOT_SET;
    dcfg->crypto_hash_location_pm = NOT_SET;
    dcfg->crypto_hash_iframesrc_pm = NOT_SET;
    dcfg->crypto_hash_framesrc_pm = NOT_SET;


    /* xml external entity */
    dcfg->xml_external_entity = NOT_SET;

    return dcfg;
}

/**
 * Copies rules between one phase of two configuration contexts,
 * taking exceptions into account.
 */
static void copy_rules_phase(apr_pool_t *mp,
                             apr_array_header_t *parent_phase_arr,
                             apr_array_header_t *child_phase_arr,
                             apr_array_header_t *exceptions_arr)
{
    rule_exception **exceptions;
    msre_rule **rules;
    int i, j;
    int mode = 0;

    rules = (msre_rule **)parent_phase_arr->elts;
    for(i = 0; i < parent_phase_arr->nelts; i++) {
        msre_rule *rule = (msre_rule *)rules[i];
        int copy = 1;

        if (mode == 0) {
            /* First rule in the chain. */
            exceptions = (rule_exception **)exceptions_arr->elts;
            for(j = 0; j < exceptions_arr->nelts; j++) {

                /* Process exceptions. */
                switch(exceptions[j]->type) {
                    case RULE_EXCEPTION_REMOVE_ID :
                        if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
                            int ruleid = atoi(rule->actionset->id);
                            if (rule_id_in_range(ruleid, exceptions[j]->param)) copy--;
                        }
                        break;
                    case RULE_EXCEPTION_REMOVE_MSG :
                        if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
                            char *my_error_msg = NULL;

                            int rc = msc_regexec(exceptions[j]->param_data,
                                    rule->actionset->msg, strlen(rule->actionset->msg),
                                    &my_error_msg);
                            if (rc >= 0) copy--;
                        }
                        break;
                    case RULE_EXCEPTION_REMOVE_TAG :
                        if ((rule->actionset != NULL)&&(apr_is_empty_table(rule->actionset->actions) == 0)) {
                            char *my_error_msg = NULL;
                            const apr_array_header_t *tarr = NULL;
                            const apr_table_entry_t *telts = NULL;
                            int c;

                            tarr = apr_table_elts(rule->actionset->actions);
                            telts = (const apr_table_entry_t*)tarr->elts;

                            for (c = 0; c < tarr->nelts; c++) {
                                msre_action *action = (msre_action *)telts[c].val;
                                if(strcmp("tag", action->metadata->name) == 0)  {

                                    int rc = msc_regexec(exceptions[j]->param_data,
                                            action->param, strlen(action->param),
                                            &my_error_msg);
                                    if (rc >= 0) copy--;
                                }
                            }
                        }
                        break;
                }
            }

            if (copy > 0) {
#ifdef DEBUG_CONF
                ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy rule %pp [id \"%s\"]", rule, rule->actionset->id);
#endif

                /* Copy the rule. */
                *(msre_rule **)apr_array_push(child_phase_arr) = rule;
                if (rule->actionset && rule->actionset->is_chained) mode = 2;
            } else {
                if (rule->actionset && rule->actionset->is_chained) mode = 1;
            }
        } else {
            if (mode == 2) {
#ifdef DEBUG_CONF
                ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy chain %pp for rule %pp [id \"%s\"]", rule, rule->chain_starter, rule->chain_starter->actionset->id);
#endif

                /* Copy the rule (it belongs to the chain we want to include. */
                *(msre_rule **)apr_array_push(child_phase_arr) = rule;
            }

            if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
        }
    }
}

/**
 * @brief Copies rules between one phase of two configuration contexts.
 *
 * Copies rules between one phase of two configuration contexts,
 * taking exceptions into account.
 *
 * @param mp apr pool structure
 * @param parent_ruleset Parent's msre_ruleset
 * @param child_ruleset Child's msre_ruleset
 * @param exceptions_arr Exceptions' apr_array_header_t
 * @retval 0 Everything went well.
 * @retval -1 Something went wrong.
 *
 */
static int copy_rules(apr_pool_t *mp, msre_ruleset *parent_ruleset,
                      msre_ruleset *child_ruleset,
                      apr_array_header_t *exceptions_arr)
{
    int ret = 0;

    if (parent_ruleset == NULL || child_ruleset == NULL ||
            exceptions_arr == NULL) {
        ret = -1;
        goto failed;
    }

    copy_rules_phase(mp, parent_ruleset->phase_request_headers,
        child_ruleset->phase_request_headers, exceptions_arr);
    copy_rules_phase(mp, parent_ruleset->phase_request_body,
        child_ruleset->phase_request_body, exceptions_arr);
    copy_rules_phase(mp, parent_ruleset->phase_response_headers,
        child_ruleset->phase_response_headers, exceptions_arr);
    copy_rules_phase(mp, parent_ruleset->phase_response_body,
        child_ruleset->phase_response_body, exceptions_arr);
    copy_rules_phase(mp, parent_ruleset->phase_logging,
        child_ruleset->phase_logging, exceptions_arr);

failed:
    return ret;
}

/**
 * Merges two directory configurations.
 */
void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child)
{
    directory_config *parent = (directory_config *)_parent;
    directory_config *child = (directory_config *)_child;
    directory_config *merged = create_directory_config(mp, NULL);

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Merge parent %pp child %pp RESULT %pp", _parent, _child, merged);
    #endif

    if (merged == NULL) return NULL;

    /* Use values from the child configuration where possible,
     * otherwise use the parent's.
     */

    merged->is_enabled = (child->is_enabled == NOT_SET
        ? parent->is_enabled : child->is_enabled);

    /* IO parameters */
    merged->reqbody_access = (child->reqbody_access == NOT_SET
        ? parent->reqbody_access : child->reqbody_access);
    merged->reqbody_buffering = (child->reqbody_buffering == NOT_SET
        ? parent->reqbody_buffering : child->reqbody_buffering);
    merged->reqbody_inmemory_limit = (child->reqbody_inmemory_limit == NOT_SET
        ? parent->reqbody_inmemory_limit : child->reqbody_inmemory_limit);
    merged->reqbody_limit = (child->reqbody_limit == NOT_SET
        ? parent->reqbody_limit : child->reqbody_limit);
    merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET
        ? parent->reqbody_no_files_limit : child->reqbody_no_files_limit);
    merged->resbody_access = (child->resbody_access == NOT_SET
        ? parent->resbody_access : child->resbody_access);

    merged->of_limit = (child->of_limit == NOT_SET
        ? parent->of_limit : child->of_limit);
    merged->if_limit_action = (child->if_limit_action == NOT_SET
        ? parent->if_limit_action : child->if_limit_action);
    merged->of_limit_action = (child->of_limit_action == NOT_SET
        ? parent->of_limit_action : child->of_limit_action);
    merged->reqintercept_oe = (child->reqintercept_oe == NOT_SET
        ? parent->reqintercept_oe : child->reqintercept_oe);

    if (child->of_mime_types != NOT_SET_P) {
        /* Child added to the table */

        if (child->of_mime_types_cleared == 1) {
            /* The list of MIME types was cleared in the child,
             * which means the parent's MIME types went away and
             * we should not take them into consideration here.
             */
            merged->of_mime_types = child->of_mime_types;
            merged->of_mime_types_cleared = 1;
        } else {
            /* Add MIME types defined in the child to those
             * defined in the parent context.
             */
            if (parent->of_mime_types == NOT_SET_P) {
                merged->of_mime_types = child->of_mime_types;
                merged->of_mime_types_cleared = NOT_SET;
            } else {
                merged->of_mime_types = apr_table_overlay(mp, parent->of_mime_types,
                    child->of_mime_types);
                if (merged->of_mime_types == NULL) return NULL;
            }
        }
    } else {
        /* Child did not add to the table */

        if (child->of_mime_types_cleared == 1) {
            merged->of_mime_types_cleared = 1;
        } else {
            merged->of_mime_types = parent->of_mime_types;
            merged->of_mime_types_cleared = parent->of_mime_types_cleared;
        }
    }

    /* debug log */
    if (child->debuglog_fd == NOT_SET_P) {
        merged->debuglog_name = parent->debuglog_name;
        merged->debuglog_fd = parent->debuglog_fd;
    } else {
        merged->debuglog_name = child->debuglog_name;
        merged->debuglog_fd = child->debuglog_fd;
    }

    merged->debuglog_level = (child->debuglog_level == NOT_SET
        ? parent->debuglog_level : child->debuglog_level);

    merged->cookie_format = (child->cookie_format == NOT_SET
        ? parent->cookie_format : child->cookie_format);
    merged->argument_separator = (child->argument_separator == NOT_SET
        ? parent->argument_separator : child->argument_separator);
    merged->cookiev0_separator = (child->cookiev0_separator == NOT_SET_P
        ? parent->cookiev0_separator : child->cookiev0_separator);


    /* rule inheritance */
    if ((child->rule_inheritance == NOT_SET)||(child->rule_inheritance == 1)) {
        merged->rule_inheritance = parent->rule_inheritance;
        if ((child->ruleset == NULL)&&(parent->ruleset == NULL)) {
            #ifdef DEBUG_CONF
            ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "No rules in this context.");
            #endif

            /* Do nothing, there are no rules in either context. */
        } else
        if (child->ruleset == NULL) {
            #ifdef DEBUG_CONF
            ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent rules in this context.");
            #endif

            /* Copy the rules from the parent context. */
            merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
            /* TODO: copy_rules return code should be taken into consideration. */
            copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions);
        } else
        if (parent->ruleset == NULL) {
            #ifdef DEBUG_CONF
            ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using child rules in this context.");
            #endif

            /* Copy child rules. */
            merged->ruleset = msre_ruleset_create(child->ruleset->engine, mp);
            merged->ruleset->phase_request_headers = apr_array_copy(mp,
                child->ruleset->phase_request_headers);
            merged->ruleset->phase_request_body = apr_array_copy(mp,
                child->ruleset->phase_request_body);
            merged->ruleset->phase_response_headers = apr_array_copy(mp,
                child->ruleset->phase_response_headers);
            merged->ruleset->phase_response_body = apr_array_copy(mp,
                child->ruleset->phase_response_body);
            merged->ruleset->phase_logging = apr_array_copy(mp,
                child->ruleset->phase_logging);
        } else {
            #ifdef DEBUG_CONF
            ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent then child rules in this context.");
            #endif

            /* Copy parent rules, then add child rules to it. */
            merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
            /* TODO: copy_rules return code should be taken into consideration. */
            copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions);

            apr_array_cat(merged->ruleset->phase_request_headers,
                child->ruleset->phase_request_headers);
            apr_array_cat(merged->ruleset->phase_request_body,
                child->ruleset->phase_request_body);
            apr_array_cat(merged->ruleset->phase_response_headers,
                child->ruleset->phase_response_headers);
            apr_array_cat(merged->ruleset->phase_response_body,
                child->ruleset->phase_response_body);
            apr_array_cat(merged->ruleset->phase_logging,
                child->ruleset->phase_logging);
        }
    } else {
        merged->rule_inheritance = 0;
        if (child->ruleset != NULL) {
            /* Copy child rules. */
            merged->ruleset = msre_ruleset_create(child->ruleset->engine, mp);
            merged->ruleset->phase_request_headers = apr_array_copy(mp,
                child->ruleset->phase_request_headers);
            merged->ruleset->phase_request_body = apr_array_copy(mp,
                child->ruleset->phase_request_body);
            merged->ruleset->phase_response_headers = apr_array_copy(mp,
                child->ruleset->phase_response_headers);
            merged->ruleset->phase_response_body = apr_array_copy(mp,
                child->ruleset->phase_response_body);
            merged->ruleset->phase_logging = apr_array_copy(mp,
                child->ruleset->phase_logging);
        }
    }

    /* Merge rule exceptions. */
    merged->rule_exceptions = apr_array_append(mp, parent->rule_exceptions,
        child->rule_exceptions);

    merged->hash_method = apr_array_append(mp, parent->hash_method,
        child->hash_method);

    /* audit log variables */
    merged->auditlog_flag = (child->auditlog_flag == NOT_SET
        ? parent->auditlog_flag : child->auditlog_flag);
    merged->auditlog_type = (child->auditlog_type == NOT_SET
        ? parent->auditlog_type : child->auditlog_type);
    merged->max_rule_time = (child->max_rule_time == NOT_SET
        ? parent->max_rule_time : child->max_rule_time);
    merged->auditlog_dirperms = (child->auditlog_dirperms == NOT_SET
        ? parent->auditlog_dirperms : child->auditlog_dirperms);
    merged->auditlog_fileperms = (child->auditlog_fileperms == NOT_SET
        ? parent->auditlog_fileperms : child->auditlog_fileperms);
    if (child->auditlog_fd != NOT_SET_P) {
        merged->auditlog_fd = child->auditlog_fd;
        merged->auditlog_name = child->auditlog_name;
    } else {
        merged->auditlog_fd = parent->auditlog_fd;
        merged->auditlog_name = parent->auditlog_name;
    }
    if (child->auditlog2_fd != NOT_SET_P) {
        merged->auditlog2_fd = child->auditlog2_fd;
        merged->auditlog2_name = child->auditlog2_name;
    } else {
        merged->auditlog2_fd = parent->auditlog2_fd;
        merged->auditlog2_name = parent->auditlog2_name;
    }
    #ifdef WITH_YAJL
    merged->auditlog_format = (child->auditlog_format == NOT_SET
        ? parent->auditlog_format : child->auditlog_format);
    #endif
    merged->auditlog_storage_dir = (child->auditlog_storage_dir == NOT_SET_P
        ? parent->auditlog_storage_dir : child->auditlog_storage_dir);
    merged->auditlog_parts = (child->auditlog_parts == NOT_SET_P
        ? parent->auditlog_parts : child->auditlog_parts);
    merged->auditlog_relevant_regex = (child->auditlog_relevant_regex == NOT_SET_P
        ? parent->auditlog_relevant_regex : child->auditlog_relevant_regex);

    /* Upload */
    merged->tmp_dir = (child->tmp_dir == NOT_SET_P
        ? parent->tmp_dir : child->tmp_dir);
    merged->upload_dir = (child->upload_dir == NOT_SET_P
        ? parent->upload_dir : child->upload_dir);
    merged->upload_keep_files = (child->upload_keep_files == NOT_SET
        ? parent->upload_keep_files : child->upload_keep_files);
    merged->upload_validates_files = (child->upload_validates_files == NOT_SET
        ? parent->upload_validates_files : child->upload_validates_files);
    merged->upload_filemode = (child->upload_filemode == NOT_SET
        ? parent->upload_filemode : child->upload_filemode);
    merged->upload_file_limit = (child->upload_file_limit == NOT_SET
        ? parent->upload_file_limit : child->upload_file_limit);

    /* Misc */
    merged->data_dir = (child->data_dir == NOT_SET_P
        ? parent->data_dir : child->data_dir);
    merged->webappid = (child->webappid == NOT_SET_P
        ? parent->webappid : child->webappid);
    merged->sensor_id = (child->sensor_id == NOT_SET_P
        ? parent->sensor_id : child->sensor_id);
    merged->httpBlkey = (child->httpBlkey == NOT_SET_P
        ? parent->httpBlkey : child->httpBlkey);

    /* Content injection. */
    merged->content_injection_enabled = (child->content_injection_enabled == NOT_SET
        ? parent->content_injection_enabled : child->content_injection_enabled);

    /* Stream inspection */
    merged->stream_inbody_inspection = (child->stream_inbody_inspection == NOT_SET
        ? parent->stream_inbody_inspection : child->stream_inbody_inspection);
    merged->stream_outbody_inspection = (child->stream_outbody_inspection == NOT_SET
        ? parent->stream_outbody_inspection : child->stream_outbody_inspection);

    /* Geo Lookup */
    merged->geo = (child->geo == NOT_SET_P
        ? parent->geo : child->geo);

    /* Gsb Lookup */
    merged->gsb = (child->gsb == NOT_SET_P
        ? parent->gsb : child->gsb);

    /* Unicode Map */
    merged->u_map = (child->u_map == NOT_SET_P
        ? parent->u_map : child->u_map);

    /* Cache */
    merged->cache_trans = (child->cache_trans == NOT_SET
        ? parent->cache_trans : child->cache_trans);
    merged->cache_trans_incremental = (child->cache_trans_incremental == NOT_SET
        ? parent->cache_trans_incremental : child->cache_trans_incremental);
    merged->cache_trans_min = (child->cache_trans_min == (apr_size_t)NOT_SET
        ? parent->cache_trans_min : child->cache_trans_min);
    merged->cache_trans_max = (child->cache_trans_max == (apr_size_t)NOT_SET
        ? parent->cache_trans_max : child->cache_trans_max);
    merged->cache_trans_maxitems = (child->cache_trans_maxitems == (apr_size_t)NOT_SET
        ? parent->cache_trans_maxitems : child->cache_trans_maxitems);

    /* Merge component signatures. */
    merged->component_signatures = apr_array_append(mp, parent->component_signatures,
        child->component_signatures);

    merged->request_encoding = (child->request_encoding == NOT_SET_P
        ? parent->request_encoding : child->request_encoding);

    merged->disable_backend_compression = (child->disable_backend_compression == NOT_SET
        ? parent->disable_backend_compression : child->disable_backend_compression);

    merged->col_timeout = (child->col_timeout == NOT_SET
        ? parent->col_timeout : child->col_timeout);

    merged->col_gc_freq = (child->col_gc_freq == NOT_SET)
        ? parent->col_gc_freq : child->col_gc_freq;

    /* Hash */
    merged->crypto_key = (child->crypto_key == NOT_SET_P
        ? parent->crypto_key : child->crypto_key);
    merged->crypto_key_len = (child->crypto_key_len == NOT_SET
        ? parent->crypto_key_len : child->crypto_key_len);
    merged->crypto_key_add = (child->crypto_key_add == NOT_SET
        ? parent->crypto_key_add : child->crypto_key_add);
    merged->crypto_param_name = (child->crypto_param_name == NOT_SET_P
        ? parent->crypto_param_name : child->crypto_param_name);
    merged->hash_is_enabled = (child->hash_is_enabled == NOT_SET
        ? parent->hash_is_enabled : child->hash_is_enabled);
    merged->hash_enforcement = (child->hash_enforcement == NOT_SET
        ? parent->hash_enforcement : child->hash_enforcement);
    merged->crypto_hash_href_rx = (child->crypto_hash_href_rx == NOT_SET
        ? parent->crypto_hash_href_rx : child->crypto_hash_href_rx);
    merged->crypto_hash_faction_rx = (child->crypto_hash_faction_rx == NOT_SET
        ? parent->crypto_hash_faction_rx : child->crypto_hash_faction_rx);
    merged->crypto_hash_location_rx = (child->crypto_hash_location_rx == NOT_SET
        ? parent->crypto_hash_location_rx : child->crypto_hash_location_rx);
    merged->crypto_hash_iframesrc_rx = (child->crypto_hash_iframesrc_rx == NOT_SET
        ? parent->crypto_hash_iframesrc_rx : child->crypto_hash_iframesrc_rx);
    merged->crypto_hash_framesrc_rx = (child->crypto_hash_framesrc_rx == NOT_SET
        ? parent->crypto_hash_framesrc_rx : child->crypto_hash_framesrc_rx);
    merged->crypto_hash_href_pm = (child->crypto_hash_href_pm == NOT_SET
        ? parent->crypto_hash_href_pm : child->crypto_hash_href_pm);
    merged->crypto_hash_faction_pm = (child->crypto_hash_faction_pm == NOT_SET
        ? parent->crypto_hash_faction_pm : child->crypto_hash_faction_pm);
    merged->crypto_hash_location_pm = (child->crypto_hash_location_pm == NOT_SET
        ? parent->crypto_hash_location_pm : child->crypto_hash_location_pm);
    merged->crypto_hash_iframesrc_pm = (child->crypto_hash_iframesrc_pm == NOT_SET
        ? parent->crypto_hash_iframesrc_pm : child->crypto_hash_iframesrc_pm);
    merged->crypto_hash_framesrc_pm = (child->crypto_hash_framesrc_pm == NOT_SET
        ? parent->crypto_hash_framesrc_pm : child->crypto_hash_framesrc_pm);

    /* xml external entity */
    merged->xml_external_entity = (child->xml_external_entity == NOT_SET
        ? parent->xml_external_entity : child->xml_external_entity);

    return merged;
}

/**
 * Initialise directory configuration. This function is *not* meant
 * to be called for directory configuration instances created during
 * the configuration phase. It can only be called on copies of those
 * (created fresh for every transaction).
 */
void init_directory_config(directory_config *dcfg)
{
    if (dcfg == NULL) return;

    if (dcfg->is_enabled == NOT_SET) dcfg->is_enabled = 0;

    if (dcfg->reqbody_access == NOT_SET) dcfg->reqbody_access = 0;
    if (dcfg->reqintercept_oe == NOT_SET) dcfg->reqintercept_oe = 0;
    if (dcfg->reqbody_buffering == NOT_SET) dcfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_OFF;
    if (dcfg->reqbody_inmemory_limit == NOT_SET)
        dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT;
    if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT;
    if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT;
    if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0;
    if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT;
    if (dcfg->if_limit_action == NOT_SET) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT;
    if (dcfg->of_limit_action == NOT_SET) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;

    if (dcfg->of_mime_types == NOT_SET_P) {
        dcfg->of_mime_types = apr_table_make(dcfg->mp, 3);
        if (dcfg->of_mime_types_cleared != 1) {
            apr_table_setn(dcfg->of_mime_types, "text/plain", "1");
            apr_table_setn(dcfg->of_mime_types, "text/html", "1");
        }
    }

    if (dcfg->debuglog_fd == NOT_SET_P) dcfg->debuglog_fd = NULL;
    if (dcfg->debuglog_name == NOT_SET_P) dcfg->debuglog_name = NULL;
    if (dcfg->debuglog_level == NOT_SET) dcfg->debuglog_level = 0;

    if (dcfg->cookie_format == NOT_SET) dcfg->cookie_format = 0;
    if (dcfg->argument_separator == NOT_SET) dcfg->argument_separator = '&';
    if (dcfg->cookiev0_separator == NOT_SET_P) dcfg->cookiev0_separator = NULL;

    if (dcfg->rule_inheritance == NOT_SET) dcfg->rule_inheritance = 1;

    /* audit log variables */
    if (dcfg->auditlog_flag == NOT_SET) dcfg->auditlog_flag = 0;
    if (dcfg->auditlog_type == NOT_SET) dcfg->auditlog_type = AUDITLOG_SERIAL;
    #ifdef WITH_YAJL
    if (dcfg->auditlog_format == NOT_SET) dcfg->auditlog_format = AUDITLOGFORMAT_NATIVE;
    #endif
    if (dcfg->max_rule_time == NOT_SET) dcfg->max_rule_time = 0;
    if (dcfg->auditlog_dirperms == NOT_SET) dcfg->auditlog_dirperms = CREATEMODE_DIR;
    if (dcfg->auditlog_fileperms == NOT_SET) dcfg->auditlog_fileperms = CREATEMODE;
    if (dcfg->auditlog_fd == NOT_SET_P) dcfg->auditlog_fd = NULL;
    if (dcfg->auditlog2_fd == NOT_SET_P) dcfg->auditlog2_fd = NULL;
    if (dcfg->auditlog_name == NOT_SET_P) dcfg->auditlog_name = NULL;
    if (dcfg->auditlog2_name == NOT_SET_P) dcfg->auditlog2_name = NULL;
    if (dcfg->auditlog_storage_dir == NOT_SET_P) dcfg->auditlog_storage_dir = NULL;
    if (dcfg->auditlog_parts == NOT_SET_P) dcfg->auditlog_parts = "ABCFHZ";
    if (dcfg->auditlog_relevant_regex == NOT_SET_P) dcfg->auditlog_relevant_regex = NULL;

    /* Upload */
    if (dcfg->tmp_dir == NOT_SET_P) dcfg->tmp_dir = guess_tmp_dir(dcfg->mp);
    if (dcfg->upload_dir == NOT_SET_P) dcfg->upload_dir = NULL;
    if (dcfg->upload_keep_files == NOT_SET) dcfg->upload_keep_files = KEEP_FILES_OFF;
    if (dcfg->upload_validates_files == NOT_SET) dcfg->upload_validates_files = 0;
    if (dcfg->upload_filemode == NOT_SET) dcfg->upload_filemode = 0600;
    if (dcfg->upload_file_limit == NOT_SET) dcfg->upload_file_limit = 100;

    /* Misc */
    if (dcfg->data_dir == NOT_SET_P) dcfg->data_dir = NULL;
    if (dcfg->webappid == NOT_SET_P) dcfg->webappid = "default";
    if (dcfg->sensor_id == NOT_SET_P) dcfg->sensor_id = "default";
    if (dcfg->httpBlkey == NOT_SET_P) dcfg->httpBlkey = NULL;

    /* Content injection. */
    if (dcfg->content_injection_enabled == NOT_SET) dcfg->content_injection_enabled = 0;

    /* Stream inspection */
    if (dcfg->stream_inbody_inspection == NOT_SET) dcfg->stream_inbody_inspection = 0;
    if (dcfg->stream_outbody_inspection == NOT_SET) dcfg->stream_outbody_inspection = 0;

    /* Geo Lookup */
    if (dcfg->geo == NOT_SET_P) dcfg->geo = NULL;

    /* Gsb Lookup */
    if (dcfg->gsb == NOT_SET_P) dcfg->gsb = NULL;

    /* Unicode Map */
    if (dcfg->u_map == NOT_SET_P) dcfg->u_map = NULL;

    /* Cache */
    if (dcfg->cache_trans == NOT_SET) dcfg->cache_trans = MODSEC_CACHE_DISABLED;
    if (dcfg->cache_trans_incremental == NOT_SET) dcfg->cache_trans_incremental = 0;
    if (dcfg->cache_trans_min == (apr_size_t)NOT_SET) dcfg->cache_trans_min = 32;
    if (dcfg->cache_trans_max == (apr_size_t)NOT_SET) dcfg->cache_trans_max = 1024;
    if (dcfg->cache_trans_maxitems == (apr_size_t)NOT_SET) dcfg->cache_trans_maxitems = 512;

    if (dcfg->request_encoding == NOT_SET_P) dcfg->request_encoding = NULL;

    if (dcfg->disable_backend_compression == NOT_SET) dcfg->disable_backend_compression = 0;

    if (dcfg->col_timeout == NOT_SET) dcfg->col_timeout = 3600;
    if (dcfg->col_gc_freq == NOT_SET) dcfg->col_gc_freq = .01;

    /* Hash */
    if (dcfg->hash_is_enabled == HASH_ENABLED) {
        if (dcfg->crypto_key == NOT_SET_P) dcfg->crypto_key = getkey(dcfg->mp);
        if (dcfg->crypto_key_len == NOT_SET) dcfg->crypto_key_len = strlen(dcfg->crypto_key);
    } else {
        if (dcfg->crypto_key == NOT_SET_P) dcfg->crypto_key = "";
        if (dcfg->crypto_key_len == NOT_SET) dcfg->crypto_key_len = 0;
    }
    if (dcfg->crypto_key_add == NOT_SET) dcfg->crypto_key_add = HASH_KEYONLY;
    if (dcfg->crypto_param_name == NOT_SET_P) dcfg->crypto_param_name = "crypt";
    if (dcfg->hash_is_enabled == NOT_SET) dcfg->hash_is_enabled = HASH_DISABLED;
    if (dcfg->hash_enforcement == NOT_SET) dcfg->hash_enforcement = HASH_DISABLED;
    if (dcfg->crypto_hash_href_rx == NOT_SET) dcfg->crypto_hash_href_rx = 0;
    if (dcfg->crypto_hash_faction_rx == NOT_SET) dcfg->crypto_hash_faction_rx = 0;
    if (dcfg->crypto_hash_location_rx == NOT_SET) dcfg->crypto_hash_location_rx = 0;
    if (dcfg->crypto_hash_iframesrc_rx == NOT_SET) dcfg->crypto_hash_iframesrc_rx = 0;
    if (dcfg->crypto_hash_framesrc_rx == NOT_SET) dcfg->crypto_hash_framesrc_rx = 0;
    if (dcfg->crypto_hash_href_pm == NOT_SET) dcfg->crypto_hash_href_pm = 0;
    if (dcfg->crypto_hash_faction_pm == NOT_SET) dcfg->crypto_hash_faction_pm = 0;
    if (dcfg->crypto_hash_location_pm == NOT_SET) dcfg->crypto_hash_location_pm = 0;
    if (dcfg->crypto_hash_iframesrc_pm == NOT_SET) dcfg->crypto_hash_iframesrc_pm = 0;
    if (dcfg->crypto_hash_framesrc_pm == NOT_SET) dcfg->crypto_hash_framesrc_pm = 0;

    /* xml external entity */
    if (dcfg->xml_external_entity == NOT_SET) dcfg->xml_external_entity = 0;

}

/**
 *
 */
static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
                            const char *p1, const char *p2, const char *p3)
{
    char *my_error_msg = NULL;
    //msre_rule *rule = NULL, *tmp_rule = NULL;
    char *rid = NULL;
    msre_rule *rule = NULL;
    extern msc_engine *modsecurity;
    int type_with_lua = 1;
    int type_rule;
    int rule_actionset;
    int offset = 0;

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Rule: type=%d p1='%s' p2='%s' p3='%s'", type, p1, p2, p3);
    #endif

    /* Create a ruleset if one does not exist. */
    if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) {
        dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool);
        if (dcfg->ruleset == NULL) return FATAL_ERROR;
    }

    /* Create the rule now. */
    switch(type) {
        #if defined(WITH_LUA)
        case RULE_TYPE_LUA :
            rule = msre_rule_lua_create(dcfg->ruleset, cmd->directive->filename,
                cmd->directive->line_num, p1, p2, &my_error_msg);
            break;
        #endif
        default :
            rule = msre_rule_create(dcfg->ruleset, type, cmd->directive->filename,
                cmd->directive->line_num, p1, p2, p3, &my_error_msg);
            break;
    }

    if (rule == NULL) {
        return my_error_msg;
    }

#ifndef ALLOW_ID_NOT_UNIQUE
	/* Rules must have uniq ID */
    type_rule = (dcfg->tmp_chain_starter == NULL);
#if defined(WITH_LUA)
            type_rule = (type != RULE_TYPE_LUA && type_rule);
#endif
            if (type_rule)
                if(rule->actionset == NULL)
                    return "ModSecurity: Rules must have at least id action";

    if(rule->actionset != NULL && (dcfg->tmp_chain_starter == NULL))    {
        rule_actionset = (rule->actionset->id == NOT_SET_P);
#if defined(WITH_LUA)
        rule_actionset = (rule_actionset && (type != RULE_TYPE_LUA));
#endif
        if (rule_actionset)
          return "ModSecurity: No action id present within the rule";
#if defined(WITH_LUA)
        type_with_lua = (type != RULE_TYPE_LUA);
#endif
        if (type_with_lua){
            rid = apr_hash_get(dcfg->rule_id_htab, rule->actionset->id, APR_HASH_KEY_STRING);
            if(rid != NULL) {
                return "ModSecurity: Found another rule with the same id";
            } else    {
                apr_hash_set(dcfg->rule_id_htab, apr_pstrdup(dcfg->mp, rule->actionset->id), APR_HASH_KEY_STRING, apr_pstrdup(dcfg->mp, "1"));
            }

            //tmp_rule = msre_ruleset_fetch_rule(dcfg->ruleset, rule->actionset->id, offset);
            //if(tmp_rule != NULL)
            //    return "ModSecurity: Found another rule with the same id";
        }
    }
#endif

    /* Create default actionset if one does not already exist. */
    if (dcfg->tmp_default_actionset == NULL) {
        dcfg->tmp_default_actionset = msre_actionset_create_default(modsecurity->msre);
        if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR;
    }

    /* Check some cases prior to merging so we know where it came from */

    /* Check syntax for chained rules */
    if ((rule->actionset != NULL) && (dcfg->tmp_chain_starter != NULL)) {
        /* Must NOT specify a disruptive action. */
        if (rule->actionset->intercept_action != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions can only "
                "be specified by chain starter rules.");
        }

        /* Must NOT specify a skipafter action. */
        if (rule->actionset->skip_after != NOT_SET_P) {
            return apr_psprintf(cmd->pool, "ModSecurity: SkipAfter actions can only "
                "be specified by chain starter rules.");
        }

        /* Must NOT specify a phase. */
        if (rule->actionset->phase != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be "
                "specified by chain starter rules.");
        }

        /* Must NOT use metadata actions. */
        /* ENH: loop through to check for tags */
        if ((rule->actionset->id != NOT_SET_P)
            ||(rule->actionset->rev != NOT_SET_P)
            ||(rule->actionset->msg != NOT_SET_P)
            ||(rule->actionset->severity != NOT_SET)
            ||(rule->actionset->version != NOT_SET_P)
            ||(rule->actionset->accuracy != NOT_SET)
            ||(rule->actionset->maturity != NOT_SET)
            ||(rule->actionset->logdata != NOT_SET_P))
        {
            return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg, tag, severity, ver, accuracy, maturity, logdata) "
                " can only be specified by chain starter rules.");
        }

        /* Must NOT use skip. */
        if (rule->actionset->skip_count != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: The skip action can only be used "
                " by chain starter rules. ");
        }
    }

    /* Merge actions with the parent.
     *
     * ENH Probably do not want this done fully for chained rules.
     */
    rule->actionset = msre_actionset_merge(modsecurity->msre, cmd->pool, dcfg->tmp_default_actionset,
        rule->actionset, 1);

    /* Keep track of the parent action for "block" */
    if (rule->actionset) {
        rule->actionset->parent_intercept_action_rec = dcfg->tmp_default_actionset->intercept_action_rec;
        rule->actionset->parent_intercept_action = dcfg->tmp_default_actionset->intercept_action;
    }

    /* Must NOT specify a disruptive action in logging phase. */
    if ((rule->actionset != NULL)
        && (rule->actionset->phase == PHASE_LOGGING)
        && (rule->actionset->intercept_action != ACTION_ALLOW)
        && (rule->actionset->intercept_action != ACTION_ALLOW_REQUEST)
        && (rule->actionset->intercept_action != ACTION_NONE)
    ) {
        return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions "
            "cannot be specified in the logging phase.");
    }

    if (dcfg->tmp_chain_starter != NULL) {
        rule->chain_starter = dcfg->tmp_chain_starter;
        if (rule->actionset) {
            rule->actionset->phase = rule->chain_starter->actionset->phase;
        }
    }

    if (rule->actionset->is_chained != 1) {
        /* If this rule is part of the chain but does
         * not want more rules to follow in the chain
         * then cut it (the chain).
         */
        dcfg->tmp_chain_starter = NULL;
    } else {
        /* On the other hand, if this rule wants other
         * rules to follow it, then start a new chain
         * if there isn't one already.
         */
        if (dcfg->tmp_chain_starter == NULL) {
            dcfg->tmp_chain_starter = rule;
        }
    }

    /* Create skip table if one does not already exist. */
    if (dcfg->tmp_rule_placeholders == NULL) {
        dcfg->tmp_rule_placeholders = apr_table_make(cmd->pool, 10);
        if (dcfg->tmp_rule_placeholders == NULL) return FATAL_ERROR;
    }

    /* Keep track of any rule IDs we need to skip after */
    if (rule->actionset->skip_after != NOT_SET_P) {
        char *tmp_id = apr_pstrdup(cmd->pool, rule->actionset->skip_after);
        apr_table_setn(dcfg->tmp_rule_placeholders, tmp_id, tmp_id);

        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Watching for skipafter target rule id=\"%s\".", tmp_id);
        #endif

    }

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Adding rule %pp phase=%d id=\"%s\".", rule, rule->actionset->phase, (rule->actionset->id == NOT_SET_P
        ? "(none)" : rule->actionset->id));
    #endif

    /* Add rule to the recipe. */
    if (msre_ruleset_rule_add(dcfg->ruleset, rule, rule->actionset->phase) < 0) {
        return "Internal Error: Failed to add rule to the ruleset.";
    }

    /* Add an additional placeholder if this rule ID is on the list */
    if ((rule->actionset->id != NULL) && apr_table_get(dcfg->tmp_rule_placeholders, rule->actionset->id)) {
        msre_rule *phrule = apr_palloc(rule->ruleset->mp, sizeof(msre_rule));
        if (phrule == NULL) {
            return FATAL_ERROR;
        }

        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Adding placeholder %pp for rule %pp id=\"%s\".", phrule, rule, rule->actionset->id);
        #endif

        /* shallow copy of original rule with placeholder marked as target */
        memcpy(phrule, rule, sizeof(msre_rule));
        phrule->placeholder = RULE_PH_SKIPAFTER;

        /* Add placeholder. */
        if (msre_ruleset_rule_add(dcfg->ruleset, phrule, phrule->actionset->phase) < 0) {
            return "Internal Error: Failed to add placeholder to the ruleset.";
        }

        /* No longer need to search for the ID */
        apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id);
    }

    /* Update the unparsed rule */
    rule->unparsed = msre_rule_generate_unparsed(dcfg->ruleset->mp, rule, NULL, NULL, NULL);

    return NULL;
}

/**
 *
 */
static const char *add_marker(cmd_parms *cmd, directory_config *dcfg,
                              const char *p1, const char *p2, const char *p3)
{
    char *my_error_msg = NULL;
    msre_rule *rule = NULL;
    extern msc_engine *modsecurity;
    int p;

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Rule: type=%d p1='%s' p2='%s' p3='%s'", RULE_TYPE_MARKER, p1, p2, p3);
    #endif

    /* Create a ruleset if one does not exist. */
    if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) {
        dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool);
        if (dcfg->ruleset == NULL) return FATAL_ERROR;
    }

    /* Create the rule now. */
    rule = msre_rule_create(dcfg->ruleset, RULE_TYPE_MARKER, cmd->directive->filename, cmd->directive->line_num, p1, p2, p3, &my_error_msg);
    if (rule == NULL) {
        return my_error_msg;
    }

    /* This is a marker */
    rule->placeholder = RULE_PH_MARKER;

    /* Add placeholder to each phase */
    for (p = PHASE_FIRST; p <= PHASE_LAST; p++) {
        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Adding marker %pp phase=%d id=\"%s\".", rule, p, (rule->actionset->id == NOT_SET_P
            ? "(none)" : rule->actionset->id));
        #endif

        if (msre_ruleset_rule_add(dcfg->ruleset, rule, p) < 0) {
            return "Internal Error: Failed to add marker to the ruleset.";
        }
    }

    /* No longer need to search for the ID */
    if (dcfg->tmp_rule_placeholders != NULL) {
        apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id);
    }

    return NULL;
}

/**
 *
 */
static const char *update_rule_action(cmd_parms *cmd, directory_config *dcfg,
                                      const char *p1, const char *p2, int offset)
{
    char *my_error_msg = NULL;
    msre_rule *rule = NULL;
    msre_actionset *new_actionset = NULL;
    msre_ruleset *ruleset = dcfg->ruleset;
    extern msc_engine *modsecurity;

    /* Get the ruleset if one exists */
    if ((ruleset == NULL)||(ruleset == NOT_SET_P)) {
        return NULL;
    }

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Update rule id=\"%s\" with action \"%s\".", p1, p2);
    #endif

    /* Fetch the rule */
    rule = msre_ruleset_fetch_rule(ruleset, p1, offset);
    if (rule == NULL) {
        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Update rule id=\"%s\" with action \"%s\" failed: Rule not found.", p1, p2);
        #endif
        return NULL;
    }

    /* Check the rule actionset */
    /* ENH: Can this happen? */
    if (rule->actionset == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Attempt to update action for rule \"%s\" failed: Rule does not have an actionset.", p1);
    }

    /* Create a new actionset */
    new_actionset = msre_actionset_create(modsecurity->msre, cmd->pool, p2, &my_error_msg);
    if (new_actionset == NULL) return FATAL_ERROR;
    if (my_error_msg != NULL) return my_error_msg;

    /* Must NOT change an id */
    if ((new_actionset->id != NOT_SET_P) && (rule->actionset->id != NULL) && (strcmp(rule->actionset->id, new_actionset->id) != 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Rule IDs cannot be updated via SecRuleUpdateActionById.");
    }

    /* Must NOT alter the phase */
    if ((new_actionset->phase != NOT_SET) && (rule->actionset->phase != new_actionset->phase)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Rule phases cannot be updated via SecRuleUpdateActionById.");
    }

    #ifdef DEBUG_CONF
    {
        char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset);
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Update rule %pp id=\"%s\" old action: \"%s\"",
            rule,
            (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
            actions);
    }
    #endif

    /* Merge new actions with the rule */
    /* ENH: Will this leak the old actionset? */
    rule->actionset = msre_actionset_merge(modsecurity->msre, cmd->pool, rule->actionset,
        new_actionset, 1);
    msre_actionset_set_defaults(rule->actionset);

    /* Update the unparsed rule */
    rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, NULL, NULL);

    #ifdef DEBUG_CONF
    {
        char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset);
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Update rule %pp id=\"%s\" new action: \"%s\"",
            rule,
            (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
            actions);
    }
    #endif

    return NULL;
}

/* -- Configuration directives -- */

static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_ACTION, SECACTION_TARGETS, SECACTION_ARGS, p1);
}

static const char *cmd_marker(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    const char *action = apr_pstrcat(dcfg->mp, SECMARKER_BASE_ACTIONS, p1, NULL);
    return add_marker(cmd, (directory_config *)_dcfg, SECMARKER_TARGETS, SECMARKER_ARGS, action);
}

static const char *cmd_cookiev0_separator(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (strlen(p1) != 1) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid cookie v0 separator: %s", p1);
    }

    dcfg->cookiev0_separator = p1;

    return NULL;
}

static const char *cmd_argument_separator(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (strlen(p1) != 1) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid argument separator: %s", p1);
    }

    dcfg->argument_separator = p1[0];

    return NULL;
}

static const char *cmd_audit_engine(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = _dcfg;

    if (strcasecmp(p1, "On") == 0) dcfg->auditlog_flag = AUDITLOG_ON;
    else
        if (strcasecmp(p1, "Off") == 0) dcfg->auditlog_flag = AUDITLOG_OFF;
        else
            if (strcasecmp(p1, "RelevantOnly") == 0) dcfg->auditlog_flag = AUDITLOG_RELEVANT;
            else
                return (const char *)apr_psprintf(cmd->pool,
                        "ModSecurity: Unrecognised parameter value for SecAuditEngine: %s", p1);

    return NULL;
}

static const char *cmd_audit_log(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = _dcfg;

    dcfg->auditlog_name = (char *)p1;

    if (dcfg->auditlog_name[0] == '|') {
        const char *pipe_name = dcfg->auditlog_name + 1;
        piped_log *pipe_log;

        pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
        if (pipe_log == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the audit log pipe: %s",
                    pipe_name);
        }
        dcfg->auditlog_fd = ap_piped_log_write_fd(pipe_log);
    }
    else {
        const char *file_name = ap_server_root_relative(cmd->pool, dcfg->auditlog_name);
        apr_status_t rc;

        if (dcfg->auditlog_fileperms == NOT_SET) {
            dcfg->auditlog_fileperms = CREATEMODE;
        }
        rc = apr_file_open(&dcfg->auditlog_fd, file_name,
                APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
                dcfg->auditlog_fileperms, cmd->pool);

        if (rc != APR_SUCCESS) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the audit log file: %s",
                    file_name);
        }
    }

    return NULL;
}

static const char *cmd_audit_log2(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = _dcfg;

    if (dcfg->auditlog_name == NOT_SET_P) {
        return apr_psprintf(cmd->pool, "ModSecurity: Cannot configure a secondary audit log without a primary defined: %s", p1);
    }

    dcfg->auditlog2_name = (char *)p1;

    if (dcfg->auditlog2_name[0] == '|') {
        const char *pipe_name = ap_server_root_relative(cmd->pool, dcfg->auditlog2_name + 1);
        piped_log *pipe_log;

        pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
        if (pipe_log == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the secondary audit log pipe: %s",
                    pipe_name);
        }
        dcfg->auditlog2_fd = ap_piped_log_write_fd(pipe_log);
    }
    else {
        const char *file_name = ap_server_root_relative(cmd->pool, dcfg->auditlog2_name);
        apr_status_t rc;

        if (dcfg->auditlog_fileperms == NOT_SET) {
            dcfg->auditlog_fileperms = CREATEMODE;
        }
        rc = apr_file_open(&dcfg->auditlog2_fd, file_name,
                APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
                dcfg->auditlog_fileperms, cmd->pool);

        if (rc != APR_SUCCESS) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the secondary audit log file: %s",
                    file_name);
        }
    }

    return NULL;
}

static const char *cmd_audit_log_parts(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = _dcfg;

    if (is_valid_parts_specification((char *)p1) != 1) {
        return apr_psprintf(cmd->pool, "Invalid parts specification for SecAuditLogParts: %s", p1);
    }

    dcfg->auditlog_parts = (char *)p1;
    return NULL;
}

static const char *cmd_audit_log_relevant_status(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = _dcfg;

    dcfg->auditlog_relevant_regex = msc_pregcomp(cmd->pool, p1, PCRE_DOTALL, NULL, NULL);
    if (dcfg->auditlog_relevant_regex == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
    }

    return NULL;
}

static const char *cmd_audit_log_type(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = _dcfg;

    if (strcasecmp(p1, "Serial") == 0) dcfg->auditlog_type = AUDITLOG_SERIAL;
    else
        if (strcasecmp(p1, "Concurrent") == 0) dcfg->auditlog_type = AUDITLOG_CONCURRENT;
        else
            return (const char *)apr_psprintf(cmd->pool,
                    "ModSecurity: Unrecognised parameter value for SecAuditLogType: %s", p1);

    return NULL;
}

#ifdef WITH_YAJL
static const char *cmd_audit_log_mode(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = _dcfg;

    if (strcasecmp(p1, "JSON") == 0) dcfg->auditlog_format = AUDITLOGFORMAT_JSON;
    else
        if (strcasecmp(p1, "Native") == 0) dcfg->auditlog_format = AUDITLOGFORMAT_NATIVE;
        else
            return (const char *)apr_psprintf(cmd->pool,
                    "ModSecurity: Unrecognised parameter value for SecAuditLogFormat: %s", p1);

    return NULL;
}
#endif

static const char *cmd_audit_log_dirmode(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "default") == 0) {
        dcfg->auditlog_dirperms = NOT_SET;
    }
    else {
        long int mode = strtol(p1, NULL, 8); /* expects octal mode */
        if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecAuditLogDirMode: %s", p1);
        }

        dcfg->auditlog_dirperms = mode2fileperms(mode);
    }

    return NULL;
}

static const char *cmd_audit_log_filemode(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "default") == 0) {
        dcfg->auditlog_fileperms = NOT_SET;
    }
    else {
        long int mode = strtol(p1, NULL, 8); /* expects octal mode */
        if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecAuditLogFileMode: %s", p1);
        }

        dcfg->auditlog_fileperms = mode2fileperms(mode);
    }

    return NULL;
}

static const char *cmd_audit_log_storage_dir(cmd_parms *cmd, void *_dcfg,
                                             const char *p1)
{
    directory_config *dcfg = _dcfg;

    dcfg->auditlog_storage_dir = ap_server_root_relative(cmd->pool, p1);

    return NULL;
}

static const char *cmd_cookie_format(cmd_parms *cmd, void *_dcfg,
                                     const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (strcmp(p1, "0") == 0) dcfg->cookie_format = COOKIES_V0;
    else
    if (strcmp(p1, "1") == 0) dcfg->cookie_format = COOKIES_V1;
    else {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid cookie format: %s", p1);
    }

    return NULL;
}

static const char *cmd_chroot_dir(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    char cwd[1025] = "";

    if (cmd->server->is_virtual) {
        return "ModSecurity: SecChrootDir not allowed in VirtualHost";
    }

    chroot_dir = (char *)p1;

    if (getcwd(cwd, 1024) == NULL) {
        return "ModSecurity: Failed to get the current working directory";
    }

    if (chdir(chroot_dir) < 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Failed to chdir to %s, errno=%d (%s)",
            chroot_dir, errno, strerror(errno));
    }

    if (chdir(cwd) < 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Failed to chdir to %s, errno=%d (%s)",
            cwd, errno, strerror(errno));
    }

    return NULL;
}

/**
 * Adds component signature to the list of signatures kept in configuration.
 */
static const char *cmd_component_signature(cmd_parms *cmd, void *_dcfg,
                                           const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    /* ENH Enforce "Name/VersionX.Y.Z (comment)" format. */
    *(char **)apr_array_push(dcfg->component_signatures) = (char *)p1;

    return NULL;
}

static const char *cmd_content_injection(cmd_parms *cmd, void *_dcfg, int flag)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    dcfg->content_injection_enabled = flag;
    return NULL;
}

static const char *cmd_data_dir(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (cmd->server->is_virtual) {
        return "ModSecurity: SecDataDir not allowed in VirtualHost.";
    }

    dcfg->data_dir = ap_server_root_relative(cmd->pool, p1);

    return NULL;
}

static const char *cmd_debug_log(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    apr_status_t rc;

    dcfg->debuglog_name = ap_server_root_relative(cmd->pool, p1);

    rc = apr_file_open(&dcfg->debuglog_fd, dcfg->debuglog_name,
                   APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
                   CREATEMODE, cmd->pool);

    if (rc != APR_SUCCESS) {
        return apr_psprintf(cmd->pool, "ModSecurity: Failed to open debug log file: %s",
            dcfg->debuglog_name);
    }

    return NULL;
}

/**
* \brief Add SecCollectionTimeout configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_collection_timeout(cmd_parms *cmd, void *_dcfg,
                                       const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    dcfg->col_timeout = atoi(p1);
    /* max 30 days */
    if ((dcfg->col_timeout >= 0)&&(dcfg->col_timeout <= 2592000)) return NULL;

    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecCollectionTimeout: %s", p1);
}

static const char *cmd_collection_gc_freq(cmd_parms *cmd, void *_dcfg,
                                        const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    float freq;
    
    if (sscanf(p1, "%f", &freq) != 1) {
      return "ModSecurity: Invalid value for SecCollectionGCFrequency. Value must be a float.";
    }
    
    if (freq <= 0) {
        // <= 0 would mean GC would never run. 
        return "ModSecurity: Invalid value for SecCollectionGCFrequency. Value must be greater than 0.";
    }
    
    if (freq > 100) {
        // >100 would make no sense.
        return "ModSecurity: Invalid value for SecCollectionGCFrequency. Value must be less than 100.";
    }
    
    // Convert to percentage
    dcfg->col_gc_freq = freq / 100;

    return NULL;
}

static const char *cmd_debug_log_level(cmd_parms *cmd, void *_dcfg,
                                       const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    dcfg->debuglog_level = atoi(p1);
    if ((dcfg->debuglog_level >= 0)&&(dcfg->debuglog_level <= 9)) return NULL;

    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecDebugLogLevel: %s", p1);
}

static const char *cmd_default_action(cmd_parms *cmd, void *_dcfg,
                                      const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    extern msc_engine *modsecurity;
    char *my_error_msg = NULL;

    dcfg->tmp_default_actionset = msre_actionset_create(modsecurity->msre, cmd->pool, p1, &my_error_msg);
    if (dcfg->tmp_default_actionset == NULL) {
        if (my_error_msg != NULL) return my_error_msg;
        else return FATAL_ERROR;
    }

    /* Must specify a disruptive action. */
    /* ENH: Remove this requirement? */
    if (dcfg->tmp_default_actionset->intercept_action == NOT_SET) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a disruptive action.");
    }

    /* Must specify a phase. */
    /* ENH: Remove this requirement? */
    if (dcfg->tmp_default_actionset->phase == NOT_SET) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a phase.");
    }

    /* Must not use metadata actions. */
    /* ENH: loop through to check for tags */
    if ((dcfg->tmp_default_actionset->id != NOT_SET_P)
        ||(dcfg->tmp_default_actionset->rev != NOT_SET_P)
        ||(dcfg->tmp_default_actionset->version != NOT_SET_P)
        ||(dcfg->tmp_default_actionset->maturity != NOT_SET)
        ||(dcfg->tmp_default_actionset->accuracy != NOT_SET)
        ||(dcfg->tmp_default_actionset->msg != NOT_SET_P))
    {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
            "contain any metadata actions (id, rev, msg, tag, severity, ver, accuracy, maturity, logdata).");
    }
    /* These are just a warning for now. */
    if ((dcfg->tmp_default_actionset->severity != NOT_SET)
        ||(dcfg->tmp_default_actionset->logdata != NOT_SET_P))
    {
        ap_log_perror(APLOG_MARK,
            APLOG_STARTUP|APLOG_WARNING|APLOG_NOERRNO, 0, cmd->pool,
            "ModSecurity: WARNING Using \"severity\" or \"logdata\" in "
            "SecDefaultAction is deprecated (%s:%d).",
            cmd->directive->filename, cmd->directive->line_num);
    }

    if (apr_table_get(dcfg->tmp_default_actionset->actions, "t")) {
        ap_log_perror(APLOG_MARK,
            APLOG_STARTUP|APLOG_WARNING|APLOG_NOERRNO, 0, cmd->pool,
            "ModSecurity: WARNING Using transformations in "
            "SecDefaultAction is deprecated (%s:%d).",
            cmd->directive->filename, cmd->directive->line_num);
    }

    /* Must not use chain. */
    if (dcfg->tmp_default_actionset->is_chained != NOT_SET) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
            "contain a chain action.");
    }

    /* Must not use skip. */
    if (dcfg->tmp_default_actionset->skip_count != NOT_SET) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
            "contain a skip action.");
    }

    /* Must not use skipAfter. */
    if (dcfg->tmp_default_actionset->skip_after != NOT_SET_P) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
            "contain a skipAfter action.");
    }

    return NULL;
}

static const char *cmd_disable_backend_compression(cmd_parms *cmd, void *_dcfg, int flag)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    dcfg->disable_backend_compression = flag;
    return NULL;
}

static const char *cmd_guardian_log(cmd_parms *cmd, void *_dcfg,
                                    const char *p1, const char *p2)
{
    extern char *guardianlog_name;
    extern apr_file_t *guardianlog_fd;
    extern char *guardianlog_condition;

    if (cmd->server->is_virtual) {
        return "ModSecurity: SecGuardianLog not allowed in VirtualHost";
    }

    if (p2 != NULL) {
        if (strncmp(p2, "env=", 4) != 0) {
            return "ModSecurity: Error in condition clause";
        }
        if ( (p2[4] == '\0') || ((p2[4] == '!')&&(p2[5] == '\0')) ) {
            return "ModSecurity: Missing variable name";
        }
        guardianlog_condition = apr_pstrdup(cmd->pool, p2 + 4);
    }

    guardianlog_name = (char *)p1;

    if (guardianlog_name[0] == '|') {
        const char *pipe_name = ap_server_root_relative(cmd->pool, guardianlog_name + 1);
        piped_log *pipe_log;

        pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
        if (pipe_log == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the guardian log pipe: %s",
                pipe_name);
        }
        guardianlog_fd = ap_piped_log_write_fd(pipe_log);
    }
    else {
        const char *file_name = ap_server_root_relative(cmd->pool, guardianlog_name);
        apr_status_t rc;

        rc = apr_file_open(&guardianlog_fd, file_name,
            APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
            CREATEMODE, cmd->pool);

        if (rc != APR_SUCCESS) {
            return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the guardian log file: %s",
                file_name);
        }
    }

    return NULL;
}

/**
* \brief Add SecStreamInBodyInspection configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_stream_inbody_inspection(cmd_parms *cmd, void *_dcfg, int flag)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    dcfg->stream_inbody_inspection = flag;
    return NULL;
}


/**
* \brief Add SecStreamOutBodyInspection configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_stream_outbody_inspection(cmd_parms *cmd, void *_dcfg, int flag)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    dcfg->stream_outbody_inspection = flag;
    return NULL;
}
/**
* \brief Add SecRulePerfTime configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_rule_perf_time(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRulePerfTime: %s", p1);
    }

    dcfg->max_rule_time = limit;

    return NULL;
}

char *parser_conn_limits_operator(apr_pool_t *mp, const char *p2,
    TreeRoot **whitelist, TreeRoot **suspicious_list,
    const char *filename)
{
    int res = 0;
    char *config_orig_path;
    char *param = strchr(p2, ' ');
    char *file = NULL;
    char *error_msg = NULL;
    param++;

    config_orig_path = apr_pstrndup(mp, filename,
        strlen(filename) - strlen(apr_filepath_name_get(filename)));

    apr_filepath_merge(&file, config_orig_path, param, APR_FILEPATH_TRUENAME,
        mp);

    if ((strncasecmp(p2, "!@ipMatchFromFile", strlen("!@ipMatchFromFile")) == 0) ||
        (strncasecmp(p2, "!@ipMatchF", strlen("!@ipMatchF")) == 0)) {

        res = ip_tree_from_file(whitelist, file, mp, &error_msg);
    }
    else if (strncasecmp(p2, "!@ipMatch", strlen("!@ipMatch")) == 0) {
        res = ip_tree_from_param(mp, param, whitelist, &error_msg);
    }
    else if ((strncasecmp(p2, "@ipMatchFromFile", strlen("@ipMatchFromFile")) == 0) ||
        (strncasecmp(p2, "@ipMatchF", strlen("@ipMatchF")) == 0)) {

        res = ip_tree_from_file(suspicious_list, file, mp, &error_msg);
    }
    else if (strncasecmp(p2, "@ipMatch", strlen("@ipMatch")) == 0) {
        res = ip_tree_from_param(mp, param, suspicious_list, &error_msg);
    }
    else {
        return apr_psprintf(mp, "ModSecurity: Invalid operator for " \
           "SecConnReadStateLimit: %s, expected operators: @ipMatch, @ipMatchF " \
           "or @ipMatchFromFile with or without !", p2);
    }

    if (res) {
        char *error;
        error = apr_psprintf(mp, "ModSecurity: failed to load IPs " \
            "from: %s", param);

        if (*error_msg) {
            error = apr_psprintf(mp, "%s %s", error, error_msg);
        }

        return error;
    }

    return NULL;
}


/**
* \brief Add SecConnReadStateLimit configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
* \param p2 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_conn_read_state_limit(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX) || (limit == LONG_MIN) || (limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
            "SecConnReadStateLimit: %s", p1);
    }

    if (p2 != NULL) {
        char *param = parser_conn_limits_operator(cmd->pool, p2,
            &conn_read_state_whitelist, &conn_read_state_suspicious_list,
            cmd->directive->filename);

        if (param)
            return param;
    }

    conn_read_state_limit = limit;

    return NULL;
}

static const char *cmd_read_state_limit(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "SecReadStateLimit is depricated, use SecConnReadStateLimit " \
            "instead.");

    return cmd_conn_read_state_limit(cmd, _dcfg, p1, p2);
}

/**
* \brief Add SecConnWriteStateLimit configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
* \param p2 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_conn_write_state_limit(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX) || (limit == LONG_MIN) || (limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
            "SecConnWriteStateLimit: %s", p1);
    }

    if (p2 != NULL) {
        char *param = parser_conn_limits_operator(cmd->pool, p2,
            &conn_write_state_whitelist, &conn_write_state_suspicious_list,
            cmd->directive->filename);

        if (param)
            return param;
    }

    conn_write_state_limit = limit;

    return NULL;
}
static const char *cmd_write_state_limit(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "SecWriteStateLimit is depricated, use SecConnWriteStateLimit " \
            "instead.");

    return cmd_conn_write_state_limit(cmd, _dcfg, p1, p2);
}



static const char *cmd_request_body_inmemory_limit(cmd_parms *cmd, void *_dcfg,
                                                   const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyInMemoryLimit: %s", p1);
    }

    dcfg->reqbody_inmemory_limit = limit;

    return NULL;
}

static const char *cmd_request_body_limit(cmd_parms *cmd, void *_dcfg,
                                          const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyLimit: %s", p1);
    }

    dcfg->reqbody_limit = limit;

    return NULL;
}

static const char *cmd_request_body_no_files_limit(cmd_parms *cmd, void *_dcfg,
                                                   const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    if (dcfg == NULL) return NULL;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyNoFilesLimit: %s", p1);
    }

    dcfg->reqbody_no_files_limit = limit;

    return NULL;
}

static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg,
                                           const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0) dcfg->reqbody_access = 1;
    else
    if (strcasecmp(p1, "off") == 0) dcfg->reqbody_access = 0;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyAccess: %s", p1);

    return NULL;
}

/**
* \brief Add SecInterceptOnError configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On success
*/
static const char *cmd_request_intercept_on_error(cmd_parms *cmd, void *_dcfg,
                                           const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0) dcfg->reqintercept_oe = 1;
    else
    if (strcasecmp(p1, "off") == 0) dcfg->reqintercept_oe = 0;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecInterceptOnError: %s", p1);

    return NULL;
}


static const char *cmd_request_encoding(cmd_parms *cmd, void *_dcfg,
                                        const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    /* ENH Validate encoding */

    dcfg->request_encoding = p1;

    return NULL;
}

static const char *cmd_response_body_access(cmd_parms *cmd, void *_dcfg,
                                            const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0) dcfg->resbody_access = 1;
    else
    if (strcasecmp(p1, "off") == 0) dcfg->resbody_access = 0;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyAccess: %s", p1);

    return NULL;
}

static const char *cmd_response_body_limit(cmd_parms *cmd, void *_dcfg,
                                           const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int limit;

    limit = strtol(p1, NULL, 10);
    if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyLimit: %s", p1);
    }

    if (limit > RESPONSE_BODY_HARD_LIMIT) {
        return apr_psprintf(cmd->pool, "ModSecurity: Response size limit can not exceed the hard limit: %li", RESPONSE_BODY_HARD_LIMIT);
    }

    dcfg->of_limit = limit;

    return NULL;
}

static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg,
                                                  const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (dcfg->is_enabled == MODSEC_DETECTION_ONLY)  {
        dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL;
        return NULL;
    }

    if (strcasecmp(p1, "ProcessPartial") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL;
    else
    if (strcasecmp(p1, "Reject") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyLimitAction: %s", p1);

    return NULL;
}

/**
* \brief Add SecRequestBodyLimitAction configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On success
*/
static const char *cmd_resquest_body_limit_action(cmd_parms *cmd, void *_dcfg,
                                                  const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (dcfg->is_enabled == MODSEC_DETECTION_ONLY)  {
        dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_PARTIAL;
        return NULL;
    }

    if (strcasecmp(p1, "ProcessPartial") == 0) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_PARTIAL;
    else
    if (strcasecmp(p1, "Reject") == 0) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyLimitAction: %s", p1);

    return NULL;
}

static const char *cmd_response_body_mime_type(cmd_parms *cmd, void *_dcfg,
                                               const char *_p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    char *p1 = apr_pstrdup(cmd->pool, _p1);

    /* TODO check whether the parameter is a valid MIME type of "???" */

    if ((dcfg->of_mime_types == NULL)||(dcfg->of_mime_types == NOT_SET_P)) {
        dcfg->of_mime_types = apr_table_make(cmd->pool, 10);
    }

    strtolower_inplace((unsigned char *)p1);
    apr_table_setn(dcfg->of_mime_types, p1, "1");

    return NULL;
}

static const char *cmd_response_body_mime_types_clear(cmd_parms *cmd,
                                                      void *_dcfg)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    dcfg->of_mime_types_cleared = 1;

    if ((dcfg->of_mime_types != NULL)&&(dcfg->of_mime_types != NOT_SET_P)) {
        apr_table_clear(dcfg->of_mime_types);
    }

    return NULL;
}

/**
 * \brief Add SecRuleUpdateTargetById
 *
 * \param cmd Pointer to configuration data
 * \param _dcfg Pointer to directory configuration
 * \param p1 Pointer to configuration option
 * \param p2 Pointer to configuration option
 * \param p3 Pointer to configuration option
 *
 * \retval NULL On failure|Success
 */
static const char *cmd_rule_update_target_by_id(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2, const char *p3)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    if(p1 == NULL)  {
        return apr_psprintf(cmd->pool, "Updating target by ID with no ID");
    }

    re->type = RULE_EXCEPTION_REMOVE_ID;
    /* TODO: Validate the range here, while we can still tell the user if it's invalid */
    re->param = p1;

    if(dcfg->ruleset == NULL) {
        return apr_psprintf(cmd->pool, "Updating target by ID with no ruleset in this context");
    }

    return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3);
}

/**
 * \brief Add SecRuleUpdateTargetByTag  configuration option
 *
 * \param cmd Pointer to configuration data
 * \param _dcfg Pointer to directory configuration
 * \param p1 Pointer to configuration option RULETAG
 * \param p2 Pointer to configuration option TARGET
 * \param p3 Pointer to configuration option REPLACED_TARGET
 * \todo Finish documenting
 *
 * \retval NULL On success
 * \retval apr_psprintf On failure
 *
 * \todo Figure out error checking
 */
static const char *cmd_rule_update_target_by_tag(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2, const char *p3)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    if(p1 == NULL)  {
        return apr_psprintf(cmd->pool, "Updating target by tag with no tag");
    }

    re->type = RULE_EXCEPTION_REMOVE_TAG;
    re->param = p1;
    re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL);
    if (re->param_data == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
    }

    return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3);
}
/**
 * \brief Add SecRuleUpdateTargetByMsg configuration option
 *
 * \param cmd Pointer to configuration data
 * \param _dcfg Pointer to directory configuration
 * \param p1 Pointer to configuration option RULEMSG
 * \param p2 Pointer to configuration option TARGET
 * \param p3 Pointer to configuration option REPLACED_TARGET
 * \todo Finish documenting
 *
 * \retval NULL On success
 * \retval apr_psprintf On failure
 *
 * \todo Figure out error checking
 */
static const char *cmd_rule_update_target_by_msg(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2, const char *p3)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    if(p1 == NULL)  {
        return apr_psprintf(cmd->pool, "Updating target by message with no message");
    }

    re->type = RULE_EXCEPTION_REMOVE_MSG;
    re->param = p1;
    re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL);
    if (re->param_data == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
    }

    return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3);
}


static const char *cmd_rule(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2, const char *p3)
{
    return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_NORMAL, p1, p2, p3);
}

static const char *cmd_sever_conn_filters_engine(cmd_parms *cmd, void *_dcfg,
    const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)
    {
        conn_limits_filter_state = MODSEC_ENABLED;
    }
    else if (strcasecmp(p1, "off") == 0)
    {
        conn_limits_filter_state = MODSEC_DISABLED;
    }
    else if (strcasecmp(p1, "detectiononly") == 0)
    {
        conn_limits_filter_state = MODSEC_DETECTION_ONLY;
    }
    else
    {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
                "SecConnEngine: %s", p1);
    }

    return NULL;
}

static const char *cmd_rule_engine(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)
    {
        dcfg->is_enabled = MODSEC_ENABLED;
    }
    else if (strcasecmp(p1, "off") == 0)
    {
        dcfg->is_enabled = MODSEC_DISABLED;
    }
    else if (strcasecmp(p1, "detectiononly") == 0)
    {
        dcfg->is_enabled = MODSEC_DETECTION_ONLY;
        dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL;
        dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_PARTIAL;
    }
    else
    {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
                "SecRuleEngine: %s", p1);
    }

    return NULL;
}

static const char *cmd_remote_rules_fail(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    if (strncasecmp(p1, "warn", 4) == 0)
    {
        remote_rules_fail_action = REMOTE_RULES_WARN_ON_FAIL;
    }
    else if (strncasecmp(p1, "abort", 5) == 0)
    {
        remote_rules_fail_action = REMOTE_RULES_ABORT_ON_FAIL;
    }
    else
    {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
                "SecRemoteRulesFailAction, expected: Abort or Warn.");
    }

    return NULL;
}

static const char *cmd_remote_rules(cmd_parms *cmd, void *_dcfg, const char *p1,
        const char *p2, const char *p3)
{
    char *error_msg = NULL;
    directory_config *dcfg = (directory_config *)_dcfg;
#ifdef WITH_REMOTE_RULES
    int crypto = 0;
    const char *uri = p2;
    const char *key = p1;
#endif

    if (dcfg == NULL) return NULL;

#ifdef WITH_REMOTE_RULES
    if (strncasecmp(p1, "crypto", 6) == 0)
    {
#ifdef WITH_APU_CRYPTO
        uri = p3;
        key = p2;
        crypto = 1;
#else
        return apr_psprintf(cmd->pool, "ModSecurity: SecRemoteRule using " \
                "`crypto' but ModSecurity was not compiled with crypto " \
                "support.");
#endif
    }

    if (uri == NULL || key == NULL)
    {
        return apr_psprintf(cmd->pool, "ModSecurity: Use SecRemoteRule with " \
                "Key and URI");
    }

    if (strncasecmp(uri, "https", 5) != 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid URI:" \
                " '%s'. Expected HTTPS.", uri);
    }

    // FIXME: Should we handle more then one server at once?
    if (remote_rules_server != NULL)
    {
        return apr_psprintf(cmd->pool, "ModSecurity: " \
                "SecRemoteRules cannot be used more than once.");
    }

    remote_rules_server = apr_pcalloc(cmd->pool, sizeof(msc_remote_rules_server));
    if (remote_rules_server == NULL)
    {
        return apr_psprintf(cmd->pool, "ModSecurity: " \
                "SecRemoteRules: Internal failure. Not enougth memory.");
    }

    remote_rules_server->context = dcfg;
    remote_rules_server->context_label = apr_pstrdup(cmd->pool, "Unkwon context");
    remote_rules_server->key = key;
    remote_rules_server->uri = uri;
    remote_rules_server->amount_of_rules = 0;
    remote_rules_server->crypto = crypto;

    msc_remote_add_rules_from_uri(cmd, remote_rules_server, &error_msg);
    if (error_msg != NULL)
    {
        return error_msg;
    }
#else
    return apr_psprintf(cmd->pool, "ModSecurity: SecRemoteRules: " \
            "ModSecurity was not compiled with SecRemoteRules support.");
#endif

    return NULL;
}


static const char *cmd_remote_timeout(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    long int timeout;

    if (dcfg == NULL) return NULL;

    timeout = strtol(p1, NULL, 10);
    if ((timeout == LONG_MAX)||(timeout == LONG_MIN)||(timeout < 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRemoteTimeout: %s", p1);
    }

    remote_rules_timeout = timeout;

    return NULL;
}


static const char *cmd_status_engine(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    if (strcasecmp(p1, "on") == 0) {
        status_engine_state = STATUS_ENGINE_ENABLED;
    }
    else if (strcasecmp(p1, "off") == 0) {
        status_engine_state = STATUS_ENGINE_DISABLED;
    }
    else {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
                "SecStatusEngine: %s", p1);
    }

    return NULL;
}


static const char *cmd_rule_inheritance(cmd_parms *cmd, void *_dcfg, int flag)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;
    dcfg->rule_inheritance = flag;
    return NULL;
}

static const char *cmd_rule_script(cmd_parms *cmd, void *_dcfg,
                                   const char *p1, const char *p2)
{
    #if defined(WITH_LUA)
    const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
    return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_LUA, filename, p2, NULL);
    #else
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Ignoring SecRuleScript \"%s\" directive (%s:%d): No Lua scripting support.", p1, cmd->directive->filename, cmd->directive->line_num);
    return NULL;
    #endif
}

static const char *cmd_rule_remove_by_id(cmd_parms *cmd, void *_dcfg,
                                         const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    re->type = RULE_EXCEPTION_REMOVE_ID;
    re->param = p1;
    *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;

    /* Remove the corresponding rules from the context straight away. */
    msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re);

    return NULL;
}

/**
* \brief Add SecRuleRemoveByTag  configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On success
*/
static const char *cmd_rule_remove_by_tag(cmd_parms *cmd, void *_dcfg,
                                          const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    re->type = RULE_EXCEPTION_REMOVE_TAG;
    re->param = p1;
    re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL);
    if (re->param_data == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
    }
    *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;

    /* Remove the corresponding rules from the context straight away. */
    msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re);

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Added exception %pp (%d %s) to dcfg %pp.", re, re->type, re->param, dcfg);
    #endif

    return NULL;
}

static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg,
                                          const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
    if (dcfg == NULL) return NULL;

    re->type = RULE_EXCEPTION_REMOVE_MSG;
    re->param = p1;
    re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL);
    if (re->param_data == NULL) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
    }
    *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;

    /* Remove the corresponding rules from the context straight away. */
    msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re);

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Added exception %pp (%d %s) to dcfg %pp.", re, re->type, re->param, dcfg);
    #endif

    return NULL;
}

static const char *cmd_rule_update_action_by_id(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    int offset = 0, rule_id = atoi(p1);
    char *opt = strchr(p1,':');
    char *savedptr = NULL;
    char *param = apr_pstrdup(cmd->pool, p1);

    if ((rule_id == LONG_MAX)||(rule_id == LONG_MIN)||(rule_id <= 0)) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for ID for update action: %s", p1);
    }

    if(opt != NULL) {
        opt++;
        offset = atoi(opt);
        opt = apr_strtok(param,":", &savedptr);
        return update_rule_action(cmd, (directory_config *)_dcfg, (const char *)opt, p2, offset);
    }

    return update_rule_action(cmd, (directory_config *)_dcfg, p1, p2, offset);
}

static const char *cmd_server_signature(cmd_parms *cmd, void *_dcfg,
                                        const char *p1)
{
    if (cmd->server->is_virtual) {
        return "ModSecurity: SecServerSignature not allowed in VirtualHost";
    }
    new_server_signature = (char *)p1;
    return NULL;
}

static const char *cmd_tmp_dir(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "none") == 0) dcfg->tmp_dir = NULL;
    else dcfg->tmp_dir = ap_server_root_relative(cmd->pool, p1);

    return NULL;
}

static const char *cmd_upload_dir(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "none") == 0) dcfg->upload_dir = NULL;
    else dcfg->upload_dir = ap_server_root_relative(cmd->pool, p1);

    return NULL;
}

static const char *cmd_upload_file_limit(cmd_parms *cmd, void *_dcfg,
                                         const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "default") == 0) {
        dcfg->upload_file_limit = NOT_SET;
    }
    else {
        dcfg->upload_file_limit = atoi(p1);
    }

    return NULL;
}

static const char *cmd_upload_filemode(cmd_parms *cmd, void *_dcfg,
                                       const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "default") == 0) {
        dcfg->upload_filemode = NOT_SET;
    }
    else {
        long int mode = strtol(p1, NULL, 8); /* expects octal mode */
        if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecUploadFileMode: %s", p1);
        }

        dcfg->upload_filemode = (int)mode;
    }

    return NULL;
}

static const char *cmd_upload_keep_files(cmd_parms *cmd, void *_dcfg,
                                         const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0) {
        dcfg->upload_keep_files = KEEP_FILES_ON;
    } else
    if (strcasecmp(p1, "off") == 0) {
        dcfg->upload_keep_files = KEEP_FILES_OFF;
    } else
    if (strcasecmp(p1, "relevantonly") == 0) {
        dcfg->upload_keep_files = KEEP_FILES_RELEVANT_ONLY;
    } else {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for SecUploadKeepFiles: %s",
            p1);
    }
    return NULL;
}

static const char *cmd_upload_save_tmp_files(cmd_parms *cmd, void *_dcfg,
    const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)
    {
        dcfg->upload_validates_files = 1;
    }
    else if (strcasecmp(p1, "off") == 0)
    {
        dcfg->upload_validates_files = 0;
    }
    else
    {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for SecTmpSaveUploadedFiles: %s",
            p1);
    }

    return NULL;
}

static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    /* ENH enforce format (letters, digits, ., _, -) */
    dcfg->webappid = p1;

    return NULL;
}

static const char *cmd_sensor_id(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    /* ENH enforce format (letters, digits, ., _, -) */
    dcfg->sensor_id = p1;

    return NULL;
}

/**
* \brief Add SecXmlExternalEntity configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_xml_external_entity(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)  {
        dcfg->xml_external_entity = 1;
    }
    else if (strcasecmp(p1, "off") == 0)    {
        dcfg->xml_external_entity = 0;
    }
    else return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecXmlExternalEntity: %s", p1);

    return NULL;
}


/**
* \brief Add SecHashEngine configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_hash_engine(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)  {
        dcfg->hash_is_enabled = HASH_ENABLED;
        dcfg->hash_enforcement = HASH_ENABLED;
    }
    else if (strcasecmp(p1, "off") == 0)    {
        dcfg->hash_is_enabled = HASH_DISABLED;
        dcfg->hash_enforcement = HASH_DISABLED;
    }
    else return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecHashEngine: %s", p1);

    return NULL;
}

/**
* \brief Add SecHashPram configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_hash_param(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (p1 == NULL) return NULL;
    dcfg->crypto_param_name = p1;

    return NULL;
}

/**
* \brief Add SecHashKey configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param _p1 Pointer to configuration option
* \param _p2 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_hash_key(cmd_parms *cmd, void *_dcfg, const char *_p1, const char *_p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    char *p1 = NULL;

    if (dcfg == NULL) return NULL;
    if (_p1 == NULL) return NULL;

    if (strcasecmp(_p1, "Rand") == 0)    {
        p1 = apr_pstrdup(cmd->pool, getkey(cmd->pool));
        dcfg->crypto_key = p1;
        dcfg->crypto_key_len = strlen(dcfg->crypto_key);
    } else    {
        p1 = apr_pstrdup(cmd->pool, _p1);
        dcfg->crypto_key = p1;
        dcfg->crypto_key_len = strlen(p1);
    }

    if(_p2 == NULL)  {
        return NULL;
    } else    {
        if (strcasecmp(_p2, "KeyOnly") == 0)
            dcfg->crypto_key_add = HASH_KEYONLY;
        else if (strcasecmp(_p2, "SessionID") == 0)
            dcfg->crypto_key_add = HASH_SESSIONID;
        else if (strcasecmp(_p2, "RemoteIP") == 0)
            dcfg->crypto_key_add = HASH_REMOTEIP;
    }
    return NULL;
}

/**
* \brief Add SecHashMethodPm configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
* \param p2 Pointer to configuration option
*
* \retval NULL On failure
* \retval apr_psprintf On Success
*/
static const char *cmd_hash_method_pm(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(hash_method));
    const char *_p2 = apr_pstrdup(cmd->pool, p2);
    ACMP *p = NULL;
    const char *phrase = NULL;
    const char *next = NULL;

    if (dcfg == NULL) return NULL;

    p = acmp_create(0, cmd->pool);
    if (p == NULL) return NULL;

    if(phrase == NULL)
        phrase = apr_pstrdup(cmd->pool, _p2);

    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);

    if (strcasecmp(p1, "HashHref") == 0) {
        re->type = HASH_URL_HREF_HASH_PM;
        re->param = _p2;
        re->param_data = (void *)p;
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2);
        }
        dcfg->crypto_hash_href_pm = 1;
    }
    else if (strcasecmp(p1, "HashFormAction") == 0) {
        re->type = HASH_URL_FACTION_HASH_PM;
        re->param = _p2;
        re->param_data = (void *)p;
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2);
        }
        dcfg->crypto_hash_faction_pm = 1;
    }
    else if (strcasecmp(p1, "HashLocation") == 0) {
        re->type = HASH_URL_LOCATION_HASH_PM;
        re->param = _p2;
        re->param_data = (void *)p;
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2);
        }
        dcfg->crypto_hash_location_pm = 1;
    }
    else if (strcasecmp(p1, "HashIframeSrc") == 0) {
        re->type = HASH_URL_IFRAMESRC_HASH_PM;
        re->param = _p2;
        re->param_data = (void *)p;
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2);
        }
        dcfg->crypto_hash_iframesrc_pm = 1;
    }
    else if (strcasecmp(p1, "HashFrameSrc") == 0) {
        re->type = HASH_URL_FRAMESRC_HASH_PM;
        re->param = _p2;
        re->param_data = (void *)p;
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2);
        }
        dcfg->crypto_hash_framesrc_pm = 1;
    }

    *(hash_method **)apr_array_push(dcfg->hash_method) = re;

    return NULL;
}

/**
 * \brief Add SecHashMethodRx configuration option
 *
 * \param cmd Pointer to configuration data
 * \param _dcfg Pointer to directory configuration
 * \param p1 Pointer to configuration option
 * \param p2 Pointer to configuration option
 *
 * \retval NULL On failure
 * \retval apr_psprintf On Success
 */
static const char *cmd_hash_method_rx(cmd_parms *cmd, void *_dcfg,
        const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;
    rule_exception *re = apr_pcalloc(cmd->pool, sizeof(hash_method));
    const char *_p2 = apr_pstrdup(cmd->pool, p2);
    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "HashHref") == 0) {
        re->type = HASH_URL_HREF_HASH_RX;
        re->param = _p2;
        re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL);
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2);
        }
        dcfg->crypto_hash_href_rx = 1;
    }
    else if (strcasecmp(p1, "HashFormAction") == 0) {
        re->type = HASH_URL_FACTION_HASH_RX;
        re->param = _p2;
        re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL);
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2);
        }
        dcfg->crypto_hash_faction_rx = 1;
    }
    else if (strcasecmp(p1, "HashLocation") == 0) {
        re->type = HASH_URL_LOCATION_HASH_RX;
        re->param = _p2;
        re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL);
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2);
        }
        dcfg->crypto_hash_location_rx = 1;
    }
    else if (strcasecmp(p1, "HashIframeSrc") == 0) {
        re->type = HASH_URL_IFRAMESRC_HASH_RX;
        re->param = _p2;
        re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL);
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2);
        }
        dcfg->crypto_hash_iframesrc_rx = 1;
    }
    else if (strcasecmp(p1, "HashFrameSrc") == 0) {
        re->type = HASH_URL_FRAMESRC_HASH_RX;
        re->param = _p2;
        re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL);
        if (re->param_data == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2);
        }
        dcfg->crypto_hash_framesrc_rx = 1;
    }

    *(hash_method **)apr_array_push(dcfg->hash_method) = re;

    return NULL;
}

/**
* \brief Add SecHttpBlKey configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_httpBl_key(cmd_parms *cmd, void *_dcfg, const char *p1)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (p1 == NULL) return NULL;
    dcfg->httpBlkey = p1;

    return NULL;
}

/* PCRE Limits */

static const char *cmd_pcre_match_limit(cmd_parms *cmd,
        void *_dcfg, const char *p1)
{
    long val;

    if (cmd->server->is_virtual) {
        return "ModSecurity: SecPcreMatchLimit not allowed in VirtualHost";
    }

    val = atol(p1);
    if (val <= 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for "
                "SecPcreMatchLimit: %s", p1);
    }
    msc_pcre_match_limit = (unsigned long int)val;

    return NULL;
}

static const char *cmd_pcre_match_limit_recursion(cmd_parms *cmd,
        void *_dcfg, const char *p1)
{
    long val;

    if (cmd->server->is_virtual) {
        return "ModSecurity: SecPcreMatchLimitRecursion not allowed in VirtualHost";
    }

    val = atol(p1);
    if (val <= 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for "
                "SecPcreMatchLimitRecursion: %s", p1);
    }
    msc_pcre_match_limit_recursion = (unsigned long int)val;

    return NULL;
}


/* -- Geo Lookup configuration -- */

static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg,
        const char *p1)
{
    const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
    char *error_msg;
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (geo_init(dcfg, filename, &error_msg) <= 0) {
        return error_msg;
    }

    return NULL;
}

/**
* \brief Add SecUnicodeCodePage configuration option
*
* Depcrecated
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_unicode_codepage(cmd_parms *cmd,
                                        void *_dcfg, const char *p1)
{
    long val;

    val = atol(p1);
    if (val <= 0) {
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for "
                                       "SecUnicodeCodePage: %s", p1);
    }

    unicode_codepage = (unsigned long int)val;

    return NULL;
}

/**
* \brief Add SecUnicodeMapFile configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_unicode_map(cmd_parms *cmd, void *_dcfg,
                                     const char *p1, const char *p2)
{
    const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
    char *error_msg;
    long val = 0;
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if(p2 != NULL)  {
        val = atol(p2);
        if (val <= 0) {
            return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for "
                    "SecUnicodeMapFile: %s", p2);
        }

        unicode_codepage = (unsigned long int)val;
    }

    if (unicode_map_init(dcfg, filename, &error_msg) <= 0) {
        return error_msg;
    }

    return NULL;
}

/**
* \brief Add SecGsbLookupDb configuration option
*
* \param cmd Pointer to configuration data
* \param _dcfg Pointer to directory configuration
* \param p1 Pointer to configuration option
*
* \retval NULL On success
*/
static const char *cmd_gsb_lookup_db(cmd_parms *cmd, void *_dcfg,
                                     const char *p1)
{
    const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
    char *error_msg;
    directory_config *dcfg = (directory_config *)_dcfg;
    if (dcfg == NULL) return NULL;

    if (gsb_db_init(dcfg, filename, &error_msg) <= 0) {
        return error_msg;
    }

    return NULL;
}

/* -- Cache -- */

static const char *cmd_cache_transformations(cmd_parms *cmd, void *_dcfg,
                                             const char *p1, const char *p2)
{
    directory_config *dcfg = (directory_config *)_dcfg;

    if (dcfg == NULL) return NULL;

    if (strcasecmp(p1, "on") == 0)
        dcfg->cache_trans = MODSEC_CACHE_ENABLED;
    else if (strcasecmp(p1, "off") == 0)
        dcfg->cache_trans = MODSEC_CACHE_DISABLED;
    else
        return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecCacheTransformations: %s", p1);

    /* Process options */
    if (p2 != NULL) {
        apr_table_t *vartable = apr_table_make(cmd->pool, 4);
        apr_status_t rc;
        char *error_msg = NULL;
        const char *charval = NULL;
        apr_int64_t intval = 0;

        if (vartable == NULL) {
            return apr_psprintf(cmd->pool, "ModSecurity: Unable to process options for SecCacheTransformations");
        }
        rc = msre_parse_generic(cmd->pool, p2, vartable, &error_msg);
        if (rc < 0) {
            return apr_psprintf(cmd->pool, "ModSecurity: Unable to parse options for SecCacheTransformations: %s", error_msg);
        }

        /* incremental */
        charval = apr_table_get(vartable, "incremental");
        if (charval != NULL) {
            if (strcasecmp(charval, "on") == 0)
                dcfg->cache_trans_incremental = 1;
            else if (strcasecmp(charval, "off") == 0)
                dcfg->cache_trans_incremental = 0;
            else
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations invalid incremental value: %s", charval);
        }

        /* minlen */
        charval = apr_table_get(vartable, "minlen");
        if (charval != NULL) {
            intval = apr_atoi64(charval);
            if (errno == ERANGE) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen out of range: %s", charval);
            }
            if (intval < 0) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be positive: %s", charval);
            }

            /* The NOT_SET indicator is -1, a signed long, and therfore
             * we cannot be >= the unsigned value of NOT_SET.
             */
            if ((unsigned long)intval >= (unsigned long)NOT_SET) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be less than: %lu", (unsigned long)NOT_SET);
            }
            dcfg->cache_trans_min = (apr_size_t)intval;
        }

        /* maxlen */
        charval = apr_table_get(vartable, "maxlen");
        if (charval != NULL) {
            intval = apr_atoi64(charval);
            if (errno == ERANGE) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen out of range: %s", charval);
            }
            if (intval < 0) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be positive: %s", charval);
            }

            /* The NOT_SET indicator is -1, a signed long, and therfore
             * we cannot be >= the unsigned value of NOT_SET.
             */
            if ((unsigned long)intval >= (unsigned long)NOT_SET) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be less than: %lu", (unsigned long)NOT_SET);
            }
            if ((intval != 0) && ((apr_size_t)intval < dcfg->cache_trans_min)) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must not be less than minlen: %lu < %" APR_SIZE_T_FMT, (unsigned long)intval, dcfg->cache_trans_min);
            }
            dcfg->cache_trans_max = (apr_size_t)intval;

        }

        /* maxitems */
        charval = apr_table_get(vartable, "maxitems");
        if (charval != NULL) {
            intval = apr_atoi64(charval);
            if (errno == ERANGE) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxitems out of range: %s", charval);
            }
            if (intval < 0) {
                return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxitems must be positive: %s", charval);
            }
            dcfg->cache_trans_maxitems = (apr_size_t)intval;
        }
    }

    return NULL;
}


/* -- Configuration directives definitions -- */

#define CMD_SCOPE_MAIN  (RSRC_CONF)
#define CMD_SCOPE_ANY   (RSRC_CONF | ACCESS_CONF)

#if defined(HTACCESS_CONFIG)
#define CMD_SCOPE_HTACCESS  (OR_OPTIONS)
#endif

const command_rec module_directives[] = {

#ifdef HTACCESS_CONFIG
    AP_INIT_TAKE1 (
        "SecAction",
        cmd_action,
        NULL,
        CMD_SCOPE_HTACCESS,
        "an action list"
    ),
#else
    AP_INIT_TAKE1 (
        "SecAction",
        cmd_action,
        NULL,
        CMD_SCOPE_ANY,
        "an action list"
    ),
#endif

    AP_INIT_TAKE1 (
        "SecArgumentSeparator",
        cmd_argument_separator,
        NULL,
        CMD_SCOPE_ANY,
        "character that will be used as separator when parsing application/x-www-form-urlencoded content."
    ),

    AP_INIT_TAKE1 (
        "SecCookiev0Separator",
        cmd_cookiev0_separator,
        NULL,
        CMD_SCOPE_ANY,
        "character that will be used as separator when parsing cookie v0 content."
    ),

    AP_INIT_TAKE1 (
        "SecAuditEngine",
        cmd_audit_engine,
        NULL,
        CMD_SCOPE_ANY,
        "On, Off or RelevantOnly to determine the level of audit logging"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLog",
        cmd_audit_log,
        NULL,
        CMD_SCOPE_ANY,
        "filename of the primary audit log file"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLog2",
        cmd_audit_log2,
        NULL,
        CMD_SCOPE_ANY,
        "filename of the secondary audit log file"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLogParts",
        cmd_audit_log_parts,
        NULL,
        CMD_SCOPE_ANY,
        "list of audit log parts that go into the log."
    ),

    AP_INIT_TAKE1 (
        "SecAuditLogRelevantStatus",
        cmd_audit_log_relevant_status,
        NULL,
        CMD_SCOPE_ANY,
        "regular expression that will be used to determine if the response status is relevant for audit logging"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLogType",
        cmd_audit_log_type,
        NULL,
        CMD_SCOPE_ANY,
        "whether to use the old audit log format (Serial) or new (Concurrent)"
    ),

#ifdef WITH_YAJL
    AP_INIT_TAKE1 (
        "SecAuditLogFormat",
        cmd_audit_log_mode,
        NULL,
        CMD_SCOPE_ANY,
        "whether to emit audit log data in native format or JSON"
    ),
#endif

    AP_INIT_TAKE1 (
        "SecAuditLogStorageDir",
        cmd_audit_log_storage_dir,
        NULL,
        CMD_SCOPE_ANY,
        "path to the audit log storage area; absolute, or relative to the root of the server"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLogDirMode",
        cmd_audit_log_dirmode,
        NULL,
        CMD_SCOPE_ANY,
        "octal permissions mode for concurrent audit log directories"
    ),

    AP_INIT_TAKE1 (
        "SecAuditLogFileMode",
        cmd_audit_log_filemode,
        NULL,
        CMD_SCOPE_ANY,
        "octal permissions mode for concurrent audit log files"
    ),

    AP_INIT_TAKE12 (
        "SecCacheTransformations",
        cmd_cache_transformations,
        NULL,
        CMD_SCOPE_ANY,
        "whether or not to cache transformations. Defaults to true."
    ),

    AP_INIT_TAKE1 (
        "SecChrootDir",
        cmd_chroot_dir,
        NULL,
        CMD_SCOPE_MAIN,
        "path of the directory to which server will be chrooted"
    ),

    AP_INIT_TAKE1 (
        "SecComponentSignature",
        cmd_component_signature,
        NULL,
        CMD_SCOPE_MAIN,
        "component signature to add to ModSecurity signature."
    ),

    AP_INIT_FLAG (
        "SecContentInjection",
        cmd_content_injection,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_FLAG (
        "SecStreamOutBodyInspection",
        cmd_stream_outbody_inspection,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_FLAG (
        "SecStreamInBodyInspection",
        cmd_stream_inbody_inspection,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecCookieFormat",
        cmd_cookie_format,
        NULL,
        CMD_SCOPE_ANY,
        "version of the Cookie specification to use for parsing. Possible values are 0 and 1."
    ),

    AP_INIT_TAKE1 (
        "SecDataDir",
        cmd_data_dir,
        NULL,
        CMD_SCOPE_MAIN,
        "path to the persistent data storage area" // TODO
    ),

    AP_INIT_TAKE1 (
        "SecDebugLog",
        cmd_debug_log,
        NULL,
        CMD_SCOPE_ANY,
        "path to the debug log file"
    ),

    AP_INIT_TAKE1 (
        "SecDebugLogLevel",
        cmd_debug_log_level,
        NULL,
        CMD_SCOPE_ANY,
        "debug log level, which controls the verbosity of logging."
        " Use values from 0 (no logging) to 9 (a *lot* of logging)."
    ),

    AP_INIT_TAKE1 (
        "SecCollectionTimeout",
        cmd_collection_timeout,
        NULL,
        CMD_SCOPE_ANY,
        "set default collections timeout. default it 3600"
    ),

    AP_INIT_TAKE12 (
        "SecCollectionGCFrequency",
        cmd_collection_gc_freq,
        NULL,
        CMD_SCOPE_ANY,
        "set garbage collection frequency. default is to run during roughly 1% of requests."
    ),

    AP_INIT_TAKE1 (
        "SecDefaultAction",
        cmd_default_action,
        NULL,
        CMD_SCOPE_ANY,
        "default action list"
    ),

    AP_INIT_FLAG (
        "SecDisableBackendCompression",
        cmd_disable_backend_compression,
        NULL,
        CMD_SCOPE_ANY,
        "When set to On, removes the compression headers from the backend requests."
    ),

    AP_INIT_TAKE1 (
        "SecGsbLookupDB",
        cmd_gsb_lookup_db,
        NULL,
        RSRC_CONF,
        "database google safe browsing"
    ),

    AP_INIT_TAKE1 (
        "SecUnicodeCodePage",
        cmd_unicode_codepage,
        NULL,
        CMD_SCOPE_MAIN,
        "Unicode CodePage"
    ),

    AP_INIT_TAKE12 (
        "SecUnicodeMapFile",
        cmd_unicode_map,
        NULL,
        CMD_SCOPE_MAIN,
        "Unicode Map file"
    ),

    AP_INIT_TAKE1 (
        "SecGeoLookupDB",
        cmd_geo_lookup_db,
        NULL,
        RSRC_CONF,
        "database for geographical lookups module."
    ),

    AP_INIT_TAKE12 (
        "SecGuardianLog",
        cmd_guardian_log,
        NULL,
        CMD_SCOPE_MAIN,
        "The filename of the filter debugging log file"
    ),

    AP_INIT_TAKE1 (
        "SecMarker",
        cmd_marker,
        NULL,
        CMD_SCOPE_ANY,
        "marker for a skipAfter target"
    ),

    AP_INIT_TAKE1 (
        "SecPcreMatchLimit",
        cmd_pcre_match_limit,
        NULL,
        CMD_SCOPE_MAIN,
        "PCRE match limit"
    ),

    AP_INIT_TAKE1 (
        "SecPcreMatchLimitRecursion",
        cmd_pcre_match_limit_recursion,
        NULL,
        CMD_SCOPE_MAIN,
        "PCRE match limit recursion"
    ),

    AP_INIT_TAKE1 (
        "SecRequestBodyAccess",
        cmd_request_body_access,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecInterceptOnError",
        cmd_request_intercept_on_error,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecRulePerfTime",
        cmd_rule_perf_time,
        NULL,
        CMD_SCOPE_ANY,
        "Threshold to log slow rules in usecs."
    ),

    AP_INIT_TAKE12 (
        "SecConnReadStateLimit",
        cmd_conn_read_state_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum number of threads in READ_BUSY state per ip address"
    ),

    AP_INIT_TAKE12 (
        "SecReadStateLimit",
        cmd_read_state_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum number of threads in READ_BUSY state per ip address"
    ),

    AP_INIT_TAKE12 (
        "SecConnWriteStateLimit",
        cmd_conn_write_state_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum number of threads in WRITE_BUSY state per ip address"
    ),

    AP_INIT_TAKE12 (
        "SecWriteStateLimit",
        cmd_write_state_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum number of threads in WRITE_BUSY state per ip address"
    ),

    AP_INIT_TAKE1 (
        "SecRequestBodyInMemoryLimit",
        cmd_request_body_inmemory_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum request body size that will be placed in memory (except for POST urlencoded requests)."
    ),

    AP_INIT_TAKE1 (
        "SecRequestBodyLimit",
        cmd_request_body_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum request body size ModSecurity will accept."
    ),

    AP_INIT_TAKE1 (
        "SecRequestBodyNoFilesLimit",
        cmd_request_body_no_files_limit,
        NULL,
        CMD_SCOPE_ANY,
        "maximum request body size ModSecurity will accept, but excluding the size of uploaded files."
    ),

    AP_INIT_TAKE1 (
        "SecRequestEncoding",
        cmd_request_encoding,
        NULL,
        CMD_SCOPE_ANY,
        "character encoding used in request."
    ),

    AP_INIT_TAKE1 (
        "SecResponseBodyAccess",
        cmd_response_body_access,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecResponseBodyLimit",
        cmd_response_body_limit,
        NULL,
        CMD_SCOPE_ANY,
        "byte limit for response body"
    ),

    AP_INIT_TAKE1 (
        "SecResponseBodyLimitAction",
        cmd_response_body_limit_action,
        NULL,
        CMD_SCOPE_ANY,
        "what happens when the response body limit is reached"
    ),

    AP_INIT_TAKE1 (
        "SecRequestBodyLimitAction",
        cmd_resquest_body_limit_action,
        NULL,
        CMD_SCOPE_ANY,
        "what happens when the request body limit is reached"
    ),

    AP_INIT_ITERATE (
        "SecResponseBodyMimeType",
        cmd_response_body_mime_type,
        NULL,
        CMD_SCOPE_ANY,
        "adds given MIME types to the list of types that will be buffered on output"
    ),

    AP_INIT_NO_ARGS (
        "SecResponseBodyMimeTypesClear",
        cmd_response_body_mime_types_clear,
        NULL,
        CMD_SCOPE_ANY,
        "clears the list of MIME types that will be buffered on output"
    ),

#ifdef HTACCESS_CONFIG
    AP_INIT_TAKE23 (
        "SecRule",
        cmd_rule,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule target, operator and optional action list"
    ),
#else
    AP_INIT_TAKE23 (
        "SecRule",
        cmd_rule,
        NULL,
        CMD_SCOPE_ANY,
        "rule target, operator and optional action list"
    ),
#endif

    AP_INIT_TAKE1 (
        "SecRuleEngine",
        cmd_rule_engine,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecStatusEngine",
        cmd_status_engine,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecConnEngine",
        cmd_sever_conn_filters_engine,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE23 (
        "SecRemoteRules",
        cmd_remote_rules,
        NULL,
        CMD_SCOPE_ANY,
        "key and URI to the remote rules"
    ),

    AP_INIT_TAKE1 (
        "SecRemoteRulesFailAction",
        cmd_remote_rules_fail,
        NULL,
        CMD_SCOPE_ANY,
        "Abort or Warn"
    ),

    AP_INIT_TAKE1 (
        "SecRemoteTimeout",
        cmd_remote_timeout,
        NULL,
        CMD_SCOPE_ANY,
        "timeout in seconds"
    ),


    AP_INIT_TAKE1 (
        "SecXmlExternalEntity",
        cmd_xml_external_entity,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_FLAG (
        "SecRuleInheritance",
        cmd_rule_inheritance,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE12 (
        "SecRuleScript",
        cmd_rule_script,
        NULL,
        CMD_SCOPE_ANY,
        "rule script and optional actionlist"
    ),

#ifdef HTACCESS_CONFIG
    AP_INIT_ITERATE (
        "SecRuleRemoveById",
        cmd_rule_remove_by_id,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule ID for removal"
    ),

    AP_INIT_ITERATE (
        "SecRuleRemoveByTag",
        cmd_rule_remove_by_tag,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule tag for removal"
    ),

    AP_INIT_ITERATE (
        "SecRuleRemoveByMsg",
        cmd_rule_remove_by_msg,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule message for removal"
    ),
#else
    AP_INIT_ITERATE (
        "SecRuleRemoveById",
        cmd_rule_remove_by_id,
        NULL,
        CMD_SCOPE_ANY,
        "rule ID for removal"
    ),

    AP_INIT_ITERATE (
        "SecRuleRemoveByTag",
        cmd_rule_remove_by_tag,
        NULL,
        CMD_SCOPE_ANY,
        "rule tag for removal"
    ),

    AP_INIT_ITERATE (
        "SecRuleRemoveByMsg",
        cmd_rule_remove_by_msg,
        NULL,
        CMD_SCOPE_ANY,
        "rule message for removal"
    ),
#endif

    AP_INIT_TAKE2 (
        "SecHashMethodPm",
        cmd_hash_method_pm,
        NULL,
        CMD_SCOPE_ANY,
        "Hash method and pattern"
    ),

    AP_INIT_TAKE2 (
        "SecHashMethodRx",
        cmd_hash_method_rx,
        NULL,
        CMD_SCOPE_ANY,
        "Hash method and regex"
    ),

#ifdef HTACCESS_CONFIG
    AP_INIT_TAKE2 (
        "SecRuleUpdateActionById",
        cmd_rule_update_action_by_id,
        NULL,
        CMD_SCOPE_HTACCESS,
        "updated action list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetById",
        cmd_rule_update_target_by_id,
        NULL,
        CMD_SCOPE_HTACCESS,
        "updated target list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetByTag",
        cmd_rule_update_target_by_tag,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule tag pattern and updated target list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetByMsg",
        cmd_rule_update_target_by_msg,
        NULL,
        CMD_SCOPE_HTACCESS,
        "rule message pattern and updated target list"
    ),
#else
    AP_INIT_TAKE2 (
        "SecRuleUpdateActionById",
        cmd_rule_update_action_by_id,
        NULL,
        CMD_SCOPE_ANY,
        "updated action list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetById",
        cmd_rule_update_target_by_id,
        NULL,
        CMD_SCOPE_ANY,
        "updated target list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetByTag",
        cmd_rule_update_target_by_tag,
        NULL,
        CMD_SCOPE_ANY,
        "rule tag pattern and updated target list"
    ),

    AP_INIT_TAKE23 (
        "SecRuleUpdateTargetByMsg",
        cmd_rule_update_target_by_msg,
        NULL,
        CMD_SCOPE_ANY,
        "rule message pattern and updated target list"
    ),
#endif

    AP_INIT_TAKE1 (
        "SecServerSignature",
        cmd_server_signature,
        NULL,
        CMD_SCOPE_MAIN,
        "the new signature of the server"
    ),

    AP_INIT_TAKE1 (
        "SecTmpDir",
        cmd_tmp_dir,
        NULL,
        CMD_SCOPE_ANY,
        "path to the temporary storage area"
    ),

    AP_INIT_TAKE1 (
        "SecUploadDir",
        cmd_upload_dir,
        NULL,
        CMD_SCOPE_ANY,
        "path to the file upload area"
    ),

    AP_INIT_TAKE1 (
        "SecUploadFileLimit",
        cmd_upload_file_limit,
        NULL,
        CMD_SCOPE_ANY,
        "limit the number of uploaded files processed"
    ),

    AP_INIT_TAKE1 (
        "SecUploadFileMode",
        cmd_upload_filemode,
        NULL,
        CMD_SCOPE_ANY,
        "octal permissions mode for uploaded files"
    ),

    AP_INIT_TAKE1 (
        "SecUploadKeepFiles",
        cmd_upload_keep_files,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecTmpSaveUploadedFiles",
        cmd_upload_save_tmp_files,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE1 (
        "SecWebAppId",
        cmd_web_app_id,
        NULL,
        CMD_SCOPE_ANY,
        "id"
    ),

    AP_INIT_TAKE1 (
        "SecSensorId",
        cmd_sensor_id,
        NULL,
        CMD_SCOPE_MAIN,
        "sensor id"
    ),

    AP_INIT_TAKE1 (
        "SecHttpBlKey",
        cmd_httpBl_key,
        NULL,
        CMD_SCOPE_ANY,
        "httpBl access key"
    ),

    AP_INIT_TAKE1 (
        "SecHashEngine",
        cmd_hash_engine,
        NULL,
        CMD_SCOPE_ANY,
        "On or Off"
    ),

    AP_INIT_TAKE2 (
        "SecHashKey",
        cmd_hash_key,
        NULL,
        CMD_SCOPE_ANY,
        "Set Hash key"
    ),

    AP_INIT_TAKE1 (
        "SecHashParam",
        cmd_hash_param,
        NULL,
        CMD_SCOPE_ANY,
        "Set Hash parameter"
    ),

    { NULL }
};
