/*
 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
 * Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
 *
 * $Id: apache2_config.c,v 1.8 2006/12/28 10:39:13 ivanr Exp $
 *
 * You should have received a copy of the licence along with this
 * program (stored in the file "LICENSE"). If the file is missing,
 * or if you have any other questions related to the licence, please
 * write to Thinking Stone at contact@thinkingstone.com.
 *
 */
#include <limits.h>

#include "modsecurity.h"
#include "msc_logging.h"

#include "http_log.h"

/* #define DEBUG_CONF 1 */

/* -- 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
    fprintf(stderr, "Created directory config %x path %s\n", dcfg, path);
    #endif

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

    dcfg->reqbody_access = NOT_SET;
    dcfg->reqbody_inmemory_limit = NOT_SET;
    dcfg->reqbody_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->of_mime_types = NOT_SET_P;    
    dcfg->of_mime_types_cleared = NOT_SET;

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

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

    /* audit log variables */
    dcfg->auditlog_flag = NOT_SET;
    dcfg->auditlog_type = NOT_SET;
    dcfg->auditlog_name = NOT_SET_P;
    dcfg->auditlog_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;

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

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

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

            if (copy > 0) {
                /* Copy the rule. */
                *(msre_rule **)apr_array_push(child_phase_arr) = rule;
                if (rule->actionset->is_chained) mode = 2;
            } else {
                if (rule->actionset->is_chained) mode = 1;
            }
        } else {
            if (mode == 2) {
                /* 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;
        }
    }
}

/**
 * Copies rules between two configuration contexts,
 * taking exceptions into account.
 */
static int copy_rules(apr_pool_t *mp, msre_ruleset *parent_ruleset, msre_ruleset *child_ruleset,
    apr_array_header_t *exceptions_arr)
{
    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);
    
    return 1;
}

/**
 * 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
    fprintf(stderr, "Merge parent %x child %x RESULT %x\n", _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_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->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);    

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


    /* 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)) {
            /* Do nothing, there are no rules in either context. */
        } else
        if (child->ruleset == NULL) {
            /* Copy the rules from the parent context. */
            merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
            copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions);
        } else
        if (parent->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);
        } else {
            /* Copy parent rules, then add child rules to it. */
            merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
            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);

    /* 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);    
    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;
    }
    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);

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

    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->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->resbody_access == NOT_SET) dcfg->resbody_access = 0;
    if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT;

    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->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;
    if (dcfg->auditlog_fd == NOT_SET_P) dcfg->auditlog_fd = NULL;
    if (dcfg->auditlog_name == NOT_SET_P) dcfg->auditlog_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;

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

/**
 * TODO
 */
static const char *add_rule(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;

    /* 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, p1, p2, p3, &my_error_msg);
    if (rule == NULL) {
        return my_error_msg;
    }

    /* 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;
    }

    /* Merge actions with the parent. */
    rule->actionset = msre_actionset_merge(modsecurity->msre, dcfg->tmp_default_actionset,
        rule->actionset, 1);

    if (dcfg->tmp_chain_starter != NULL) {
        /* This rule is part of a chain. */

        /* 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 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. */
        if ((rule->actionset->id != NOT_SET_P)
            ||(rule->actionset->rev != NOT_SET_P)
            ||(rule->actionset->msg != NOT_SET_P))
        {
            return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg) "
                " 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. ");
        }

        rule->chain_starter = dcfg->tmp_chain_starter;
        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;
        }
    }

    /* Optimisation */
    if (strcasecmp(rule->op_name, "inspectFile") == 0) {
        dcfg->upload_validates_files = 1;
    }

    /* 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.";
    }

    return NULL;    
}

/* -- Configuration directives -- */

static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1) {
    return add_rule(cmd, (directory_config *)_dcfg, "REQUEST_URI", "@unconditionalMatch", p1);
}

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 = ap_server_root_relative(cmd->pool, 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;

        rc = apr_file_open(&dcfg->auditlog_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 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;
}

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

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

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, 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. */
    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. */
    if (dcfg->tmp_default_actionset->phase == NOT_SET) {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a phase.");
    }

    /* Must not use metadata actions. */
    if ((dcfg->tmp_default_actionset->id != NOT_SET_P)
        ||(dcfg->tmp_default_actionset->rev != NOT_SET_P)
        ||(dcfg->tmp_default_actionset->msg != NOT_SET_P))
    {
        return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
            "contain any metadata actions (id, rev, msg).");
    }

    /* 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.");
    }

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

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_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;
}

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_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 "null"

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

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, p1, p2, p3);
}

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;
    else
    return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRuleEngine: %s", p1);

    return NULL;
}

/*
static const char *cmd_rule_import_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_IMPORT_ID;
    // TODO verify p1
    re->param = p1;
    *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;
    
    return NULL;
}

static const char *cmd_rule_import_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_IMPORT_MSG;
    // TODO verify p1
    re->param = p1;
    *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;    

    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_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;
}

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
    fprintf(stderr, "Added exception %x (%i %s) to dcfg %x.\n", re, re->type, re->param, dcfg);
    #endif

    return NULL;
}

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_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_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) {
    directory_config *dcfg = (directory_config *)_dcfg;

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

    return NULL;
}


/* -- Configuration directives definitions -- */

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

const command_rec module_directives[] = {

    AP_INIT_TAKE1 (
        "SecAction",
        cmd_action,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),
    
    AP_INIT_TAKE1 (
        "SecArgumentSeparator",
        cmd_argument_separator,
        NULL,
        CMD_SCOPE_MAIN,
        "character that will be used as separator when parsing application/x-www-form-urlencoded 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,
        "The filename of the 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)"
    ),

    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 (
        "SecChrootDir",
        cmd_chroot_dir,
        NULL,
        CMD_SCOPE_MAIN,
        "Path of the directory to which server will be chrooted"
    ),

    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,
        "" // 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 (
        "SecDefaultAction",
        cmd_default_action,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

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

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

    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 is allowed to access."
    ),

    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,
        "" // TODO
    ),

    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"
    ),

    AP_INIT_TAKE23 (
        "SecRule",
        cmd_rule,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

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

    /*
    AP_INIT_TAKE1 (
        "SecRuleImportById",
        cmd_rule_import_by_id,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    AP_INIT_TAKE1 (
        "SecRuleImportByMsg",
        cmd_rule_import_by_msg,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),
    */

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

    AP_INIT_ITERATE (
        "SecRuleRemoveById",
        cmd_rule_remove_by_id,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    AP_INIT_ITERATE (
        "SecRuleRemoveByMsg",
        cmd_rule_remove_by_msg,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    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,
        "" // TODO
    ),

    AP_INIT_TAKE1 (
        "SecUploadDir",
        cmd_upload_dir,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    AP_INIT_TAKE1 (
        "SecUploadKeepFiles",
        cmd_upload_keep_files,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    AP_INIT_TAKE1 (
        "SecWebAppId",
        cmd_web_app_id,
        NULL,
        CMD_SCOPE_ANY,
        "" // TODO
    ),

    { NULL }
};
