brectanus 99c41afc3d Added a check that SecServerSignature actually worked (Apache changed some of this code as of 2.2.4 and could potentially change it again and break this).
Cleaned up some configure code.
Cleaned up some extraneous cache logging.
Cleaned up the output from the test script.
2008-01-14 22:32:53 +00:00

2113 lines
71 KiB
C

/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/)
*
* 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 Breach Security, Inc. at support@breach.com.
*
*/
#include <ctype.h>
#include "re.h"
#include "msc_lua.h"
/* -- Actions, variables, functions and operator functions ----------------- */
/**
* Creates msre_var instances (rule variables) out of the
* given text string and places them into the supplied table.
*/
apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
apr_array_header_t *arr, char **error_msg)
{
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
apr_table_t *vartable;
unsigned int count = 0;
apr_status_t rc;
msre_var *var;
int i;
if (text == NULL) return -1;
/* Extract name & value pairs first */
vartable = apr_table_make(ruleset->mp, 10);
if (vartable == NULL) return -1;
rc = msre_parse_generic(ruleset->mp, text, vartable, error_msg);
if (rc < 0) return rc;
/* Loop through the table and create variables */
tarr = apr_table_elts(vartable);
telts = (const apr_table_entry_t*)tarr->elts;
for (i = 0; i < tarr->nelts; i++) {
var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg);
if (var == NULL) return -1;
*(msre_var **)apr_array_push(arr) = var;
count++;
}
return count;
}
/**
* Creates msre_action instances by parsing the given string, placing
* them into the supplied array.
*/
apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset,
const char *text, char **error_msg)
{
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
apr_table_t *vartable;
unsigned int count = 0;
apr_status_t rc;
msre_action *action;
int i;
if (text == NULL) return -1;
/* Extract name & value pairs first */
vartable = apr_table_make(engine->mp, 10);
if (vartable == NULL) return -1;
rc = msre_parse_generic(engine->mp, text, vartable, error_msg);
if (rc < 0) return rc;
/* Loop through the table and create actions */
tarr = apr_table_elts(vartable);
telts = (const apr_table_entry_t*)tarr->elts;
for (i = 0; i < tarr->nelts; i++) {
/* Create action. */
action = msre_create_action(engine, telts[i].key, telts[i].val, error_msg);
if (action == NULL) return -1;
/* Initialise action (option). */
if (action->metadata->init != NULL) {
action->metadata->init(engine, actionset, action);
}
if (action->metadata->cardinality == ACTION_CARDINALITY_ONE) {
/* One action per actionlist. */
apr_table_setn(actionset->actions, action->metadata->name, (void *)action);
} else {
/* Multiple actions per actionlist. */
apr_table_addn(actionset->actions, action->metadata->name, (void *)action);
}
count++;
}
return count;
}
/**
* Locates variable metadata given the variable name.
*/
msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name) {
return (msre_var_metadata *)apr_table_get(engine->variables, name);
}
/**
* Locates action metadata given the action name.
*/
msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name) {
return (msre_action_metadata *)apr_table_get(engine->actions, name);
}
/**
* Creates a new variable instance given the variable name
* and an (optional) parameter.
*/
msre_var *msre_create_var_ex(apr_pool_t *pool, msre_engine *engine, const char *name, const char *param,
modsec_rec *msr, char **error_msg)
{
msre_var *var = apr_pcalloc(pool, sizeof(msre_var));
if (var == NULL) return NULL;
if (error_msg == NULL) return NULL;
*error_msg = NULL;
/* Handle negation and member counting */
if (name[0] == '!') {
var->is_negated = 1;
var->name = name + 1;
}
else
if (name[0] == '&') {
var->is_counting = 1;
var->name = name + 1;
}
else {
var->name = name;
}
/* Resolve variable */
var->metadata = msre_resolve_var(engine, var->name);
if (var->metadata == NULL) {
*error_msg = apr_psprintf(engine->mp, "Unknown variable: %s", name);
return NULL;
}
/* The counting operator "&" can only be used against collections. */
if (var->is_counting) {
if (var->metadata->type == VAR_SIMPLE) {
*error_msg = apr_psprintf(engine->mp, "The & modificator does not apply to "
"non-collection variables.");
return NULL;
}
}
/* Check the parameter. */
if (param == NULL) {
if (var->metadata->argc_min > 0) {
*error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for variable %s.",
name);
return NULL;
}
} else { /* Parameter present */
/* Do we allow a parameter? */
if (var->metadata->argc_max == 0) {
*error_msg = apr_psprintf(engine->mp, "Variable %s does not support parameters.",
name);
return NULL;
}
var->param = param;
}
return var;
}
/**
* Create a new variable object from the provided name and value.
*
* NOTE: this allocates out of the global pool and should not be used
* per-request
*/
msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
modsec_rec *msr, char **error_msg)
{
msre_var *var = msre_create_var_ex(ruleset->engine->mp, ruleset->engine, name, param, msr, error_msg);
if (var == NULL) return NULL;
/* Validate & initialise variable */
if (var->metadata->validate != NULL) {
*error_msg = var->metadata->validate(ruleset, var);
if (*error_msg != NULL) {
/* ENH Shouldn't we log the problem? */
return NULL;
}
}
return var;
}
/**
* Creates a new action instance given its name and an (optional) parameter.
*/
msre_action *msre_create_action(msre_engine *engine, const char *name, const char *param,
char **error_msg)
{
msre_action *action = apr_pcalloc(engine->mp, sizeof(msre_action));
if (action == NULL) return NULL;
if (error_msg == NULL) return NULL;
*error_msg = NULL;
/* Resolve action */
action->metadata = msre_resolve_action(engine, name);
if (action->metadata == NULL) {
*error_msg = apr_psprintf(engine->mp, "Unknown action: %s", name);
return NULL;
}
if (param == NULL) { /* Parameter not present */
if (action->metadata->argc_min > 0) {
*error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for action %s",
name);
return NULL;
}
} else { /* Parameter present */
/* Should we allow the parameter? */
if (action->metadata->argc_max == 0) {
*error_msg = apr_psprintf(engine->mp, "Extra parameter provided to action %s", name);
return NULL;
}
/* Handle +/- modificators */
if ((param[0] == '+')||(param[0] == '-')) {
if (action->metadata->allow_param_plusminus == 0) {
*error_msg = apr_psprintf(engine->mp,
"Action %s does not allow +/- modificators.", name);
return NULL;
}
else { /* Modificators allowed. */
if (param[0] == '+') {
action->param = param + 1;
action->param_plusminus = POSITIVE_VALUE;
} else
if (param[0] == '-') {
action->param = param + 1;
action->param_plusminus = NEGATIVE_VALUE;
}
}
} else {
action->param = param;
}
/* Validate parameter */
if (action->metadata->validate != NULL) {
*error_msg = action->metadata->validate(engine, action);
if (*error_msg != NULL) return NULL;
}
}
return action;
}
/**
* Generic parser that is used as basis for target and action parsing.
* It breaks up the input string into name-parameter pairs and places
* them into the given table.
*/
int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable,
char **error_msg)
{
char *p = (char *)text;
int count = 0;
if (error_msg == NULL) return -1;
*error_msg = NULL;
count = 0;
while(*p != '\0') {
char *name = NULL, *value = NULL;
/* ignore whitespace */
while(isspace(*p)) p++;
if (*p == '\0') return count;
/* we are at the beginning of the name */
name = p;
while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */
/* get the name */
name = apr_pstrmemdup(mp, name, p - name);
if (*p != ':') { /* we don't have a parameter */
/* add to the table with no value */
apr_table_addn(vartable, name, NULL);
count++;
/* go over any whitespace present */
while(isspace(*p)) p++;
/* we're done */
if (*p == '\0') {
return count;
}
/* skip over the separator character and continue */
if ((*p == ',')||(*p == '|')) {
p++;
continue;
}
*error_msg = apr_psprintf(mp, "Unexpected character at position %d: %s",
(int)(p - text), text);
return -1;
}
/* we have a parameter */
p++; /* move over the colon */
/* we'll allow empty values */
if (*p == '\0') {
apr_table_addn(vartable, name, NULL);
count++;
return count;
}
if ((*p == ',')||(*p == '|')) {
apr_table_addn(vartable, name, NULL);
count++;
/* move over the separator char and continue */
p++;
continue;
}
/* we really have a parameter */
if (*p == '\'') { /* quoted value */
char *d = NULL;
p++; /* go over the openning quote */
value = d = strdup(p);
if (d == NULL) return -1;
for(;;) {
if (*p == '\0') {
// TODO better 64-bit support here
*error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s",
(int)(p - text), text);
free(value);
return -1;
} else
if (*p == '\\') {
if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) {
// TODO better 64-bit support here
*error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s",
(int)(p - text), text);
free(value);
return -1;
}
p++;
*d++ = *p++;
} else
if (*p == '\'') {
*d = '\0';
p++;
break;
}
else {
*d++ = *p++;
}
}
d = value;
value = apr_pstrdup(mp, d);
free(d);
} else { /* non-quoted value */
value = p;
while((*p != '\0')&&(*p != ',')&&(*p != '|')&&(!isspace(*p))) p++;
value = apr_pstrmemdup(mp, value, p - value);
}
/* add to table */
apr_table_addn(vartable, name, value);
count++;
/* move to the first character of the next name-value pair */
while(isspace(*p)||(*p == ',')||(*p == '|')) p++;
}
return count;
}
/* -- Actionset functions -------------------------------------------------- */
/**
* Creates an actionset instance and (as an option) populates it by
* parsing the given string which contains a list of actions.
*/
msre_actionset *msre_actionset_create(msre_engine *engine, const char *text,
char **error_msg)
{
msre_actionset *actionset = (msre_actionset *)apr_pcalloc(engine->mp,
sizeof(msre_actionset));
if (actionset == NULL) return NULL;
actionset->actions = apr_table_make(engine->mp, 25);
if (actionset->actions == NULL) return NULL;
/* Metadata */
actionset->id = NOT_SET_P;
actionset->rev = NOT_SET_P;
actionset->msg = NOT_SET_P;
actionset->logdata = NOT_SET_P;
actionset->phase = NOT_SET;
actionset->severity = -1;
actionset->rule = NOT_SET_P;
/* Flow */
actionset->is_chained = NOT_SET;
actionset->skip_count = NOT_SET;
actionset->skip_after = NOT_SET_P;
/* Disruptive */
actionset->intercept_action = NOT_SET;
actionset->intercept_uri = NOT_SET_P;
actionset->intercept_status = NOT_SET;
actionset->intercept_pause = NOT_SET;
/* Other */
actionset->auditlog = NOT_SET;
actionset->log = NOT_SET;
/* Parse the list of actions, if it's present */
if (text != NULL) {
if (msre_parse_actions(engine, actionset, text, error_msg) < 0) {
return NULL;
}
}
return actionset;
}
/**
* Create a (shallow) copy of the supplied actionset.
*/
static msre_actionset *msre_actionset_copy(apr_pool_t *mp, msre_actionset *orig) {
msre_actionset *copy = NULL;
if (orig == NULL) return NULL;
copy = (msre_actionset *)apr_pmemdup(mp, orig, sizeof(msre_actionset));
if (copy == NULL) return NULL;
copy->actions = apr_table_copy(mp, orig->actions);
return copy;
}
/**
* Merges two actionsets into one.
*/
msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent,
msre_actionset *child, int inherit_by_default)
{
msre_actionset *merged = NULL;
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
int i;
if (inherit_by_default == 0) {
/* There is nothing to merge in this case. */
return msre_actionset_copy(engine->mp, child);
}
/* Start with a copy of the parent configuration. */
merged = msre_actionset_copy(engine->mp, parent);
if (merged == NULL) return NULL;
if (child == NULL) {
/* The child actionset does not exist, hence
* go with the parent one.
*/
return merged;
}
/* First merge the hard-coded stuff. */
/* Metadata */
if (child->id != NOT_SET_P) merged->id = child->id;
if (child->rev != NOT_SET_P) merged->rev = child->rev;
if (child->msg != NOT_SET_P) merged->msg = child->msg;
if (child->logdata != NOT_SET_P) merged->logdata = child->logdata;
if (child->severity != NOT_SET) merged->severity = child->severity;
if (child->phase != NOT_SET) merged->phase = child->phase;
if (child->rule != NOT_SET_P) merged->rule = child->rule;
/* Flow */
merged->is_chained = child->is_chained;
if (child->skip_count != NOT_SET) merged->skip_count = child->skip_count;
if (child->skip_after != NOT_SET_P) merged->skip_after = child->skip_after;
/* Disruptive */
if (child->intercept_action != NOT_SET) {
merged->intercept_action = child->intercept_action;
merged->intercept_uri = child->intercept_uri;
}
if (child->intercept_status != NOT_SET) merged->intercept_status = child->intercept_status;
if (child->intercept_pause != NOT_SET) merged->intercept_pause = child->intercept_pause;
/* Other */
if (child->auditlog != NOT_SET) merged->auditlog = child->auditlog;
if (child->log != NOT_SET) merged->log = child->log;
/* Now merge the actions. */
tarr = apr_table_elts(child->actions);
telts = (const apr_table_entry_t*)tarr->elts;
for (i = 0; i < tarr->nelts; i++) {
msre_action *action = (msre_action *)telts[i].val;
if (action->metadata->cardinality == ACTION_CARDINALITY_ONE) {
apr_table_setn(merged->actions, action->metadata->name, (void *)action);
} else {
apr_table_addn(merged->actions, action->metadata->name, (void *)action);
}
}
return merged;
}
/**
* Creates an actionset that contains a default list of actions.
*/
msre_actionset *msre_actionset_create_default(msre_engine *engine) {
char *my_error_msg = NULL;
return msre_actionset_create(engine,
"log,auditlog,deny,status:403,phase:2,t:lowercase,t:replaceNulls,t:compressWhitespace",
&my_error_msg);
}
/**
* Sets the default values for the hard-coded actionset configuration.
*/
static void msre_actionset_set_defaults(msre_actionset *actionset) {
/* Metadata */
if (actionset->id == NOT_SET_P) actionset->id = NULL;
if (actionset->rev == NOT_SET_P) actionset->rev = NULL;
if (actionset->msg == NOT_SET_P) actionset->msg = NULL;
if (actionset->logdata == NOT_SET_P) actionset->logdata = NULL;
if (actionset->phase == NOT_SET) actionset->phase = 2;
if (actionset->severity == -1); /* leave at -1 */
if (actionset->rule == NOT_SET_P) actionset->rule = NULL;
/* Flow */
if (actionset->is_chained == NOT_SET) actionset->is_chained = 0;
if (actionset->skip_count == NOT_SET) actionset->skip_count = 0;
if (actionset->skip_after == NOT_SET_P) actionset->skip_after = NULL;
/* Disruptive */
if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE;
if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL;
if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403;
if (actionset->intercept_pause == NOT_SET) actionset->intercept_pause = 0;
/* Other */
if (actionset->auditlog == NOT_SET) actionset->auditlog = 1;
if (actionset->log == NOT_SET) actionset->log = 1;
}
/* -- Engine functions ----------------------------------------------------- */
/**
* Creates a new engine instance.
*/
msre_engine *msre_engine_create(apr_pool_t *parent_pool) {
msre_engine *engine;
apr_pool_t *mp;
/* Create new memory pool */
if (apr_pool_create(&mp, parent_pool) != APR_SUCCESS) return NULL;
/* Init fields */
engine = apr_pcalloc(mp, sizeof(msre_engine));
if (engine == NULL) return NULL;
engine->mp = mp;
engine->tfns = apr_table_make(mp, 25);
if (engine->tfns == NULL) return NULL;
engine->operators = apr_table_make(mp, 25);
if (engine->operators == NULL) return NULL;
engine->variables = apr_table_make(mp, 25);
if (engine->variables == NULL) return NULL;
engine->actions = apr_table_make(mp, 25);
if (engine->actions == NULL) return NULL;
return engine;
}
/**
* Destroys an engine instance, releasing the consumed memory.
*/
void msre_engine_destroy(msre_engine *engine) {
/* Destroyed automatically by the parent pool.
* apr_pool_destroy(engine->mp);
*/
}
/* -- Recipe functions ----------------------------------------------------- */
#define NEXT_CHAIN 1
#define NEXT_RULE 2
#define SKIP_RULES 3
/**
* Default implementation of the ruleset phase processing; it processes
* the rules in the ruleset attached to the currently active
* transaction phase.
*/
#if defined(PERFORMANCE_MEASUREMENT)
static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr) {
#else
apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
#endif
apr_array_header_t *arr = NULL;
msre_rule **rules;
apr_status_t rc;
const char *skip_after = NULL;
int i, mode, skip;
/* First determine which set of rules we need to use. */
switch (msr->phase) {
case PHASE_REQUEST_HEADERS :
arr = ruleset->phase_request_headers;
break;
case PHASE_REQUEST_BODY :
arr = ruleset->phase_request_body;
break;
case PHASE_RESPONSE_HEADERS :
arr = ruleset->phase_response_headers;
break;
case PHASE_RESPONSE_BODY :
arr = ruleset->phase_response_body;
break;
case PHASE_LOGGING :
arr = ruleset->phase_logging;
break;
default :
msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
return -1;
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "This phase consists of %d rule(s).", arr->nelts);
}
/* Loop through the rules in the selected set. */
skip = 0;
mode = NEXT_RULE;
rules = (msre_rule **)arr->elts;
for (i = 0; i < arr->nelts; i++) {
msre_rule *rule = rules[i];
#if defined(PERFORMANCE_MEASUREMENT)
apr_time_t time1 = 0;
#endif
/* Reset the rule interception flag */
msr->rule_was_intercepted = 0;
/* SKIP_RULES is used to skip all rules until we hit a placeholder
* with the specified rule ID and then resume execution after that.
*/
if (mode == SKIP_RULES) {
/* Go to the next rule if we have not yet hit the skip_after ID */
if ((rule->placeholder == RULE_PH_NONE) || (rule->actionset->id == NULL) || (strcmp(skip_after, rule->actionset->id) != 0)) {
if (msr->txcfg->debuglog_level >= 9) {
if (rule->chain_starter != NULL) {
msr_log(msr, 9, "Skipping chain rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->chain_starter->actionset->id ? rule->chain_starter->actionset->id : "(none)"), skip_after);
}
else {
msr_log(msr, 9, "Skipping rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->actionset->id ? rule->actionset->id : "(none)"), skip_after);
}
}
continue;
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Found rule %pp id=\"%s\".", rule, skip_after);
}
/* Go to the rule *after* this one to continue execution. */
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "Continuing execution after rule id=\"%s\".", skip_after);
}
skip_after = NULL;
mode = NEXT_RULE;
continue;
}
/* Skip any rule marked as a placeholder */
if (rule->placeholder != RULE_PH_NONE) {
continue;
}
/* NEXT_CHAIN is used when one of the rules in a chain
* fails to match and then we need to skip the remaining
* rules in that chain in order to get to the next
* rule that can execute.
*/
if (mode == NEXT_CHAIN) {
if (rule->actionset->is_chained == 0) {
mode = NEXT_RULE;
}
/* Go to the next rule. */
continue;
}
/* If we are here that means the mode is NEXT_RULE, which
* then means we have done processing any chains. However,
* if the "skip" parameter is set we need to skip over.
*/
if ((mode == NEXT_RULE)&&(skip > 0)) {
/* Decrement the skip counter by one. */
skip--;
/* If the current rule is part of a chain then
* we need to skip over the entire chain. Thus
* we change the mode to NEXT_CHAIN. The skip
* counter will not decrement as we are moving
* over the rules belonging to the chain.
*/
if (rule->actionset->is_chained) {
mode = NEXT_CHAIN;
}
/* Go to the next rule. */
continue;
}
/* Check if this rule was removed at runtime */
if ((rule->actionset->id !=NULL) && (! apr_is_empty_array(msr->removed_rules))) {
int j;
int do_process = 1;
const char *range;
for(j = 0; j < msr->removed_rules->nelts; j++) {
range = ((const char**)msr->removed_rules->elts)[j];
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Checking removal of rule id=\"%s\" against: %s", rule->actionset->id, range);
}
if (rule_id_in_range(atoi(rule->actionset->id), range)) {
do_process = 0;
break;
}
}
/* Go to the next rule if this one has been removed. */
if (do_process == 0) {
if (msr->txcfg->debuglog_level >= 5) {
msr_log(msr, 5, "Not processing rule id=\"%s\": removed by ctl action", rule->actionset->id);
}
continue;
}
}
if (msr->txcfg->debuglog_level >= 4) {
apr_pool_t *p = msr->mp;
const char *fn = NULL;
const char *id = NULL;
const char *rev = NULL;
if (rule->filename != NULL) {
fn = apr_psprintf(p, " [file \"%s\"] [line \"%d\"]", rule->filename, rule->line_num);
}
if (rule->actionset != NULL && rule->actionset->id != NULL) {
id = apr_psprintf(p, " [id \"%s\"]", rule->actionset->id);
}
if (rule->actionset != NULL && rule->actionset->rev != NULL) {
rev = apr_psprintf(p, " [rev \"%s\"]", rule->actionset->rev);
}
msr_log(msr, 4, "Recipe: Invoking rule %pp;%s%s%s.",
rule, (fn ? fn : ""), (id ? id : ""), (rev ? rev : ""));
msr_log(msr, 5, "Rule %pp: %s", rule, rule->unparsed);
}
#if defined(PERFORMANCE_MEASUREMENT)
time1 = apr_time_now();
#endif
rc = msre_rule_process(rule, msr);
#if defined(PERFORMANCE_MEASUREMENT)
rule->execution_time += (apr_time_now() - time1);
#endif
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "Rule returned %d.", rc);
}
if (rc == RULE_NO_MATCH) {
if (rule->actionset->is_chained) {
/* If the current rule is part of a chain then
* we need to skip over all the rules in the chain.
*/
mode = NEXT_CHAIN;
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "No match, chained -> mode NEXT_CHAIN.");
}
} else {
/* This rule is not part of a chain so we simply
* move to the next rule.
*/
mode = NEXT_RULE;
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "No match, not chained -> mode NEXT_RULE.");
}
}
}
else
if (rc == RULE_MATCH) {
if (msr->rule_was_intercepted) {
/* If the transaction was intercepted by this rule we will
* go back. Do note that we are relying on the
* rule to know if it is a part of a chain and
* not intercept if it is.
*/
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Match, intercepted -> returning.");
}
return 1;
}
if (rule->actionset->skip_after != NULL) {
skip_after = rule->actionset->skip_after;
mode = SKIP_RULES;
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Skipping after rule %pp id=\"%s\" -> mode SKIP_RULES.", rule, skip_after);
}
continue;
}
/* We had a match but the transaction was not
* intercepted. In that case we proceed with the
* next rule...
*/
mode = NEXT_RULE;
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Match -> mode NEXT_RULE.");
}
/* ...unless we need to skip, in which case we
* determine how many rules/chains we need to
* skip and configure the counter accordingly.
*/
if (rule->actionset->is_chained == 0) {
if (rule->chain_starter != NULL) {
if (rule->chain_starter->actionset->skip_count > 0) {
skip = rule->chain_starter->actionset->skip_count;
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "Skipping %d rules/chains (from a chain).", skip);
}
}
}
else if (rule->actionset->skip_count > 0) {
skip = rule->actionset->skip_count;
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "Skipping %d rules/chains.", skip);
}
}
}
}
else if (rc < 0) {
msr_log(msr, 1, "Rule processing failed.");
return -1;
}
else {
msr_log(msr, 1, "Rule processing failed with unknown return code: %d.", rc);
return -1;
}
}
/* ENH warn if chained rules are missing. */
return 0;
}
#if defined(PERFORMANCE_MEASUREMENT)
apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
apr_array_header_t *arr = NULL;
msre_rule **rules = NULL;
apr_status_t rc;
apr_time_t time1;
int i;
switch (msr->phase) {
case PHASE_REQUEST_HEADERS :
arr = ruleset->phase_request_headers;
break;
case PHASE_REQUEST_BODY :
arr = ruleset->phase_request_body;
break;
case PHASE_RESPONSE_HEADERS :
arr = ruleset->phase_response_headers;
break;
case PHASE_RESPONSE_BODY :
arr = ruleset->phase_response_body;
break;
case PHASE_LOGGING :
arr = ruleset->phase_logging;
break;
default :
msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
return -1;
}
rules = (msre_rule **)arr->elts;
for (i = 0; i < arr->nelts; i++) {
msre_rule *rule = rules[i];
rule->execution_time = 0;
}
time1 = apr_time_now();
for (i = 0; i < 10000; i++) {
rc = msre_ruleset_process_phase_(ruleset, msr);
}
msr_log(msr, 1, "Phase %d: %" APR_TIME_T_FMT " usec", msr->phase, ((apr_time_now() - time1) / 10000));
rules = (msre_rule **)arr->elts;
for (i = 0; i < arr->nelts; i++) {
msre_rule *rule = rules[i];
msr_log(msr, 1, "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"]: %u usec (trans:%u usec, op:%u usec)",
rule,
((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,
(rule->execution_time / 10000),
(rule->trans_time / 10000),
(rule->op_time / 10000));
}
return rc;
}
#endif
/**
* Creates a ruleset that will be handled by the default
* implementation.
*/
msre_ruleset *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp) {
msre_ruleset *ruleset;
ruleset = apr_pcalloc(mp, sizeof(msre_ruleset));
if (ruleset == NULL) return NULL;
ruleset->mp = mp;
ruleset->engine = engine;
ruleset->phase_request_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
ruleset->phase_request_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
ruleset->phase_response_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
ruleset->phase_response_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
ruleset->phase_logging = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
return ruleset;
}
/**
* Adds one rule to the given phase of the ruleset.
*/
int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) {
apr_array_header_t *arr = NULL;
switch (phase) {
case PHASE_REQUEST_HEADERS :
arr = ruleset->phase_request_headers;
break;
case PHASE_REQUEST_BODY :
arr = ruleset->phase_request_body;
break;
case PHASE_RESPONSE_HEADERS :
arr = ruleset->phase_response_headers;
break;
case PHASE_RESPONSE_BODY :
arr = ruleset->phase_response_body;
break;
case PHASE_LOGGING :
arr = ruleset->phase_logging;
break;
default :
return -1;
}
/* ENH verify the rule's use of targets is consistent with
* the phase it selected to run at.
*/
msre_actionset_set_defaults(rule->actionset);
rule->actionset->rule = rule;
*(const msre_rule **)apr_array_push(arr) = rule;
return 1;
}
static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
apr_array_header_t *phase_arr)
{
msre_rule **rules;
int i, j, mode, removed_count;
j = 0;
mode = 0;
removed_count = 0;
rules = (msre_rule **)phase_arr->elts;
for (i = 0; i < phase_arr->nelts; i++) {
msre_rule *rule = (msre_rule *)rules[i];
if (mode == 0) { /* Looking for next rule. */
int remove_rule = 0;
/* Only remove non-placeholder rules */
if (rule->placeholder == RULE_PH_NONE) {
switch(re->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, re->param)) {
remove_rule = 1;
}
}
break;
case RULE_EXCEPTION_REMOVE_MSG :
if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
char *my_error_msg = NULL;
int rc = msc_regexec(re->param_data,
rule->actionset->msg, strlen(rule->actionset->msg),
&my_error_msg);
if (rc >= 0) {
remove_rule = 1;
}
}
break;
}
}
if (remove_rule) {
/* Do not increment j. */
removed_count++;
if (rule->actionset->is_chained) mode = 2; /* Remove rules in this chain. */
} else {
if (rule->actionset->is_chained) mode = 1; /* Keep rules in this chain. */
rules[j++] = rules[i];
}
} else { /* Handling rule that is part of a chain. */
if (mode == 2) { /* We want to remove the rule. */
/* Do not increment j. */
removed_count++;
} else {
rules[j++] = rules[i];
}
if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
}
}
/* Update the number of rules in the array. */
phase_arr->nelts -= removed_count;
return 0;
}
/**
* Removes from the ruleset all rules that match the given exception.
*/
int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re) {
int count = 0;
if (ruleset == NULL) return 0;
count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_headers);
count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body);
count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers);
count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body);
count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_logging);
return count;
}
/* -- Rule functions ------------------------------------------------------- */
/**
* Returns the name of the supplied severity level.
*/
static const char *msre_format_severity(int severity) {
static const char *const severities[] = {
"EMERGENCY",
"ALERT",
"CRITICAL",
"ERROR",
"WARNING",
"NOTICE",
"INFO",
"DEBUG",
NULL,
};
if ((severity >= 0)&&(severity <= 7)) {
return severities[severity];
}
else {
return "(invalid value)";
}
}
/**
* Creates a string containing the metadata of the supplied rule.
*/
char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) {
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
char *id = "";
char *rev = "";
char *msg = "";
char *logdata = "";
char *severity = "";
char *tags = "";
char *fn = "";
int k;
if (actionset == NULL) return "";
if ((actionset->rule != NULL) && (actionset->rule->filename != NULL)) {
fn = apr_psprintf(msr->mp, " [file \"%s\"] [line \"%d\"]",
actionset->rule->filename, actionset->rule->line_num);
}
if (actionset->id != NULL) {
id = apr_psprintf(msr->mp, " [id \"%s\"]",
log_escape(msr->mp, actionset->id));
}
if (actionset->rev != NULL) {
rev = apr_psprintf(msr->mp, " [rev \"%s\"]",
log_escape(msr->mp, actionset->rev));
}
if (actionset->msg != NULL) {
/* Expand variables in the message string. */
msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
var->value = (char *)actionset->msg;
var->value_len = strlen(actionset->msg);
expand_macros(msr, var, NULL, msr->mp);
msg = apr_psprintf(msr->mp, " [msg \"%s\"]",
log_escape_ex(msr->mp, var->value, var->value_len));
}
if (actionset->logdata != NULL) {
//TODO: restrict to 512 bytes
/* Expand variables in the message string. */
msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
var->value = (char *)actionset->logdata;
var->value_len = strlen(actionset->logdata);
expand_macros(msr, var, NULL, msr->mp);
logdata = apr_psprintf(msr->mp, " [data \"%s\"]",
log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len));
}
if ((actionset->severity >= 0)&&(actionset->severity <= 7)) {
severity = apr_psprintf(msr->mp, " [severity \"%s\"]",
msre_format_severity(actionset->severity));
}
/* Extract rule tags from the action list. */
tarr = apr_table_elts(actionset->actions);
telts = (const apr_table_entry_t*)tarr->elts;
for (k = 0; k < tarr->nelts; k++) {
msre_action *action = (msre_action *)telts[k].val;
if (strcmp(telts[k].key, "tag") == 0) {
tags = apr_psprintf(msr->mp, "%s [tag \"%s\"]", tags,
log_escape(msr->mp, action->param));
}
}
return apr_pstrcat(msr->mp, fn, id, rev, msg, logdata, severity, tags, NULL);
}
/**
* Assembles a new rule using the strings that contain a list
* of targets (variables), arguments, and actions.
*/
msre_rule *msre_rule_create(msre_ruleset *ruleset,
const char *fn, int line, const char *targets,
const char *args, const char *actions, char **error_msg)
{
msre_rule *rule;
char *my_error_msg;
const char *argsp;
int rc;
if (error_msg == NULL) return NULL;
*error_msg = NULL;
rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
if (rule == NULL) return NULL;
rule->ruleset = ruleset;
rule->targets = apr_array_make(ruleset->mp, 10, sizeof(const msre_var *));
rule->p1 = apr_pstrdup(ruleset->mp, targets);
rule->filename = apr_pstrdup(ruleset->mp, fn);
rule->line_num = line;
/* Add the unparsed rule */
if ((strcmp(SECACTION_TARGETS, targets) == 0) && (strcmp(SECACTION_ARGS, args) == 0)) {
rule->unparsed = apr_psprintf(ruleset->mp, "SecAction \"%s\"",
log_escape(ruleset->mp, actions));
}
else
if ((strcmp(SECMARKER_TARGETS, targets) == 0)
&& (strcmp(SECMARKER_ARGS, args) == 0)
&& (strncmp(SECMARKER_BASE_ACTIONS, actions, strlen(SECMARKER_BASE_ACTIONS)) == 0))
{
rule->unparsed = apr_psprintf(ruleset->mp, "SecMarker \"%s\"",
log_escape(ruleset->mp, actions + strlen(SECMARKER_BASE_ACTIONS)));
}
else {
if (actions == NULL) {
rule->unparsed = apr_psprintf(ruleset->mp, "SecRule \"%s\" \"%s\"",
log_escape(ruleset->mp, targets), log_escape(ruleset->mp, args));
} else {
rule->unparsed = apr_psprintf(ruleset->mp, "SecRule \"%s\" \"%s\" \"%s\"",
log_escape(ruleset->mp, targets), log_escape(ruleset->mp, args),
log_escape(ruleset->mp, actions));
}
}
/* Parse targets */
rc = msre_parse_targets(ruleset, targets, rule->targets, &my_error_msg);
if (rc < 0) {
*error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
return NULL;
}
/* Parse args */
argsp = args;
/* Is negation used? */
if (*argsp == '!') {
rule->op_negated = 1;
argsp++;
while((isspace(*argsp))&&(*argsp != '\0')) argsp++;
}
/* Is the operator explicitly selected? */
if (*argsp != '@') {
/* Go with a regular expression. */
rule->op_name = "rx";
rule->op_param = argsp;
} else {
/* Explicitly selected operator. */
char *p = (char *)(argsp + 1);
while((!isspace(*p))&&(*p != '\0')) p++;
rule->op_name = apr_pstrmemdup(ruleset->mp, argsp + 1, p - (argsp + 1));
while(isspace(*p)) p++; /* skip over the whitespace at the end*/
rule->op_param = p; /* IMP1 So we always have a parameter even when it's empty? */
}
/* Find the operator. */
rule->op_metadata = msre_engine_op_resolve(ruleset->engine, rule->op_name);
if (rule->op_metadata == NULL) {
*error_msg = apr_psprintf(ruleset->mp,
"Error creating rule: Failed to resolve operator: %s", rule->op_name);
return NULL;
}
/* Initialise & validate parameter */
if (rule->op_metadata->param_init != NULL) {
if (rule->op_metadata->param_init(rule, &my_error_msg) <= 0) {
*error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
return NULL;
}
}
/* Parse actions */
if (actions != NULL) {
/* Create per-rule actionset */
rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
if (rule->actionset == NULL) {
*error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
return NULL;
}
}
return rule;
}
/**
*
*/
msre_rule *msre_rule_lua_create(msre_ruleset *ruleset,
const char *fn, int line, const char *script_filename,
const char *actions, char **error_msg)
{
msre_rule *rule;
char *my_error_msg;
if (error_msg == NULL) return NULL;
*error_msg = NULL;
rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
if (rule == NULL) return NULL;
rule->ruleset = ruleset;
rule->filename = apr_pstrdup(ruleset->mp, fn);
rule->line_num = line;
rule->type = RULE_TYPE_LUA;
if (actions == NULL) {
rule->unparsed = apr_psprintf(ruleset->mp, "SecRuleScript \"%s\"",
script_filename);
} else {
rule->unparsed = apr_psprintf(ruleset->mp, "SecRuleScript \"%s\" \"%s\"",
script_filename, log_escape(ruleset->mp, actions));
}
/* Compile script. */
*error_msg = lua_compile(&rule->script, script_filename, ruleset->mp);
if (*error_msg != NULL) {
return NULL;
}
/* Parse actions */
if (actions != NULL) {
/* Create per-rule actionset */
rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
if (rule->actionset == NULL) {
*error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
return NULL;
}
}
return rule;
}
/**
* Perform non-disruptive actions associated with the provided actionset.
*/
static void msre_perform_nondisruptive_actions(modsec_rec *msr, msre_rule *rule,
msre_actionset *actionset, apr_pool_t *mptmp)
{
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
int i;
tarr = apr_table_elts(actionset->actions);
telts = (const apr_table_entry_t*)tarr->elts;
for (i = 0; i < tarr->nelts; i++) {
msre_action *action = (msre_action *)telts[i].val;
if (action->metadata->type == ACTION_NON_DISRUPTIVE) {
if (action->metadata->execute != NULL) {
action->metadata->execute(msr, mptmp, rule, action);
}
}
}
}
/**
* Perform the disruptive actions associated with the given actionset.
*/
static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule,
msre_actionset *actionset, apr_pool_t *mptmp, const char *message)
{
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
int i;
/* Execute the disruptive actions. Do note that this does
* not mean the request will be interrupted straight away. All
* disruptive actions need to do here is update the information
* that will be used to act later.
*/
tarr = apr_table_elts(actionset->actions);
telts = (const apr_table_entry_t*)tarr->elts;
for (i = 0; i < tarr->nelts; i++) {
msre_action *action = (msre_action *)telts[i].val;
if (action->metadata->type == ACTION_DISRUPTIVE) {
if (action->metadata->execute != NULL) {
action->metadata->execute(msr, mptmp, rule, action);
}
}
}
/* If "noauditlog" was used do not mark the transaction for audit logging. */
if (actionset->auditlog == 1) {
msr->is_relevant++;
}
/* We only do stuff when in ONLINE mode. In all other
* cases we only emit warnings.
*/
if ((msr->phase == PHASE_LOGGING)
|| (msr->txcfg->is_enabled == MODSEC_DETECTION_ONLY)
|| (msr->modsecurity->processing_mode == MODSEC_OFFLINE)
|| (actionset->intercept_action == ACTION_NONE))
{
/* If "nolog" was used log at a higher level. */
msc_alert(msr, (actionset->log == 0 ? 4 : 2), actionset,
"Warning.", message);
return;
}
/* Signal to the engine we need to intercept this
* transaction, and rememer the rule that caused it.
*/
msr->was_intercepted = 1;
msr->rule_was_intercepted = 1;
msr->intercept_phase = msr->phase;
msr->intercept_actionset = actionset;
msr->intercept_message = message;
}
/**
* Invokes the rule operator against the given value.
*/
static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr,
msre_actionset *acting_actionset, apr_pool_t *mptmp)
{
apr_time_t time_before_op = 0;
char *my_error_msg = NULL;
const char *full_varname = NULL;
int rc;
/* determine the full var name if not already resolved
*
* NOTE: this can happen if the var does not match but it is
* being tested for non-existance as in:
* @REQUEST_HEADERS:Foo "@eq 0"
* @REQUEST_HEADERS:Foo "!@eq 1"
*/
if ((var->param != NULL) && (var->name != NULL) && (strchr(var->name,':') == NULL)) {
full_varname = apr_psprintf(mptmp, "%s%s:%s",
(var->is_counting ? "&" : ""),
var->name, var->param);
}
else if ((var->name != NULL) && var->is_counting && (*var->name != '&')) {
full_varname = apr_pstrcat(mptmp, "&", var->name, NULL);
}
else {
full_varname = var->name;
}
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "Executing operator \"%s%s\" with param \"%s\" against %s.",
(rule->op_negated ? "!" : ""), rule->op_name,
log_escape(msr->mp, rule->op_param), full_varname);
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Target value: \"%s\"", log_escape_nq_ex(msr->mp, var->value,
var->value_len));
}
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
time_before_op = apr_time_now();
rc = rule->op_metadata->execute(msr, rule, var, &my_error_msg);
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
{
apr_time_t t1 = apr_time_now();
#if defined(PERFORMANCE_MEASUREMENT)
rule->op_time += (t1 - time_before_op);
#endif
msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.",
(t1 - time_before_op));
}
if (rc < 0) {
msr_log(msr, 4, "Operator error: %s", my_error_msg);
return -1;
}
if (((rc == 0)&&(rule->op_negated == 0))||((rc == 1)&&(rule->op_negated == 1))) {
/* No match, do nothing. */
return RULE_NO_MATCH;
}
else {
/* Match. */
if (rc == 0) {
/* Operator did not match so we need to provide a message. */
my_error_msg = apr_psprintf(msr->mp, "Match of \"%s %s\" against \"%s\" required.",
log_escape(msr->mp, rule->op_name), log_escape(msr->mp, rule->op_param),
log_escape(msr->mp, full_varname));
}
/* Save the rules that match */
*(const msre_rule **)apr_array_push(msr->matched_rules) = rule;
/* Save the last matched var data */
msr->matched_var->name = apr_pstrdup(msr->mp, var->name);
msr->matched_var->name_len = strlen(msr->matched_var->name);
msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len);
msr->matched_var->value_len = var->value_len;
/* Keep track of the highest severity matched so far */
if ((acting_actionset->severity > 0) && (acting_actionset->severity < msr->highest_severity))
{
msr->highest_severity = acting_actionset->severity;
}
/* Perform non-disruptive actions. */
msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp);
/* Perform disruptive actions, but only if
* this rule is not part of a chain.
*/
if (rule->actionset->is_chained == 0) {
msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg);
}
return RULE_MATCH;
}
}
/**
* Executes rule against the given transaction.
*/
static apr_status_t msre_rule_process_normal(msre_rule *rule, modsec_rec *msr) {
const apr_array_header_t *arr = NULL;
const apr_table_entry_t *te = NULL;
msre_actionset *acting_actionset = NULL;
msre_var **targets = NULL;
apr_pool_t *mptmp = msr->msc_rule_mptmp;
apr_table_t *tartab = NULL;
apr_table_t *vartab = NULL;
int i, rc, match_count = 0;
int invocations = 0;
int multi_match = 0;
/* Choose the correct metadata/disruptive action actionset. */
acting_actionset = rule->actionset;
if (rule->chain_starter != NULL) {
acting_actionset = rule->chain_starter->actionset;
}
/* Configure recursive matching. */
if (apr_table_get(rule->actionset->actions, "multiMatch") != NULL) {
multi_match = 1;
}
tartab = apr_table_make(mptmp, 24);
if (tartab == NULL) return -1;
vartab = apr_table_make(mptmp, 24);
if (vartab == NULL) return -1;
/* Expand variables to create a list of targets. */
targets = (msre_var **)rule->targets->elts;
for (i = 0; i < rule->targets->nelts; i++) {
int j, list_count;
apr_table_clear(vartab);
/* ENH Introduce a new variable hook that would allow the code
* behind the variable to return the size of the collection
* without having to generate the variables.
*/
/* Expand individual variables first. */
list_count = targets[i]->metadata->generate(msr, targets[i], rule, vartab, mptmp);
if (targets[i]->is_counting) {
/* Count how many there are and just add the score to the target list. */
msre_var *newvar = (msre_var *)apr_pmemdup(mptmp, targets[i], sizeof(msre_var));
newvar->value = apr_psprintf(mptmp, "%d", list_count);
newvar->value_len = strlen(newvar->value);
apr_table_addn(tartab, newvar->name, (void *)newvar);
} else {
/* And either add them or remove from the final target list. */
arr = apr_table_elts(vartab);
te = (apr_table_entry_t *)arr->elts;
for(j = 0; j < arr->nelts; j++) {
if (targets[i]->is_negated == 0) {
apr_table_addn(tartab, te[j].key, te[j].val);
} else {
apr_table_unset(tartab, te[j].key);
}
}
}
}
/* Log the target variable expansion */
if (msr->txcfg->debuglog_level >= 4) {
const char *expnames = NULL;
arr = apr_table_elts(tartab);
te = (apr_table_entry_t *)arr->elts;
if (arr->nelts > 0) {
expnames = apr_pstrdup(mptmp, ((msre_var *)te[0].val)->name);
for(i = 1; i < arr->nelts; i++) {
expnames = apr_psprintf(mptmp, "%s|%s", expnames, ((msre_var *)te[i].val)->name);
}
if (strcmp(rule->p1, expnames) != 0) {
msr_log(msr, 4, "Expanded \"%s\" to \"%s\".", rule->p1, expnames);
}
}
}
/* Loop through targets on the final target list,
* perform transformations as necessary, and invoke
* the operator.
*/
arr = apr_table_elts(tartab);
te = (apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
int changed;
int usecache = 0;
apr_table_t **carr = NULL;
apr_table_t *cachetab = NULL;
apr_time_t time_before_trans = 0;
/* Take one target. */
msre_var *var = (msre_var *)te[i].val;
/* Is this var cacheable? */
if (msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) {
usecache = 1;
/* check the cache options */
if (var->value_len < msr->txcfg->cache_trans_min) {
msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, smaller than minlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_min);
usecache = 0;
}
if ((msr->txcfg->cache_trans_max != 0) && (var->value_len > msr->txcfg->cache_trans_max)) {
msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, larger than maxlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_max);
usecache = 0;
}
/* if cache is still enabled, check the VAR for cacheablity */
if (usecache) {
if (var->metadata->is_cacheable == VAR_CACHE) {
msr_log(msr, 9, "CACHE: Enabled");
/* Fetch cache table for this target */
carr = (apr_table_t **)apr_hash_get(msr->tcache, var->name, APR_HASH_KEY_STRING);
if (carr != NULL) {
cachetab = carr[msr->phase];
}
else {
/* Create an array of cache tables (one table per phase) */
carr = (apr_table_t **)apr_pcalloc(msr->mp, (sizeof(apr_table_t *) * (PHASE_LAST + 1)));
if (carr == NULL) return -1;
memset(carr, 0, (sizeof(apr_table_t *) * (PHASE_LAST + 1)));
apr_hash_set(msr->tcache, var->name, APR_HASH_KEY_STRING, carr);
}
/* Create an empty cache table if this is the first time */
if (cachetab == NULL) {
cachetab = carr[msr->phase] = apr_table_make(msr->mp, 5);
}
}
else {
usecache = 0;
msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name);
}
}
}
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
time_before_trans = apr_time_now();
/* Transform target. */
{
const apr_array_header_t *tarr;
const apr_table_entry_t *telts;
const char *tfnspath = NULL;
char *tfnskey = NULL;
int tfnscount = 0;
int last_cached_tfn = 0;
msre_cache_rec *crec = NULL;
msre_cache_rec *last_crec = NULL;
int k;
msre_action *action;
msre_tfn_metadata *metadata;
apr_table_t *normtab;
normtab = apr_table_make(mptmp, 10);
if (normtab == NULL) return -1;
tarr = apr_table_elts(rule->actionset->actions);
telts = (const apr_table_entry_t*)tarr->elts;
/* Build the final list of transformation functions. */
for (k = 0; k < tarr->nelts; k++) {
action = (msre_action *)telts[k].val;
if (strcmp(telts[k].key, "t") == 0) {
if (strcmp(action->param, "none") == 0) {
apr_table_clear(normtab);
tfnspath = NULL;
tfnscount = 0;
last_cached_tfn = 0;
continue;
}
if (action->param_plusminus == NEGATIVE_VALUE) {
apr_table_unset(normtab, action->param);
}
else {
tfnscount++;
apr_table_addn(normtab, action->param, (void *)action);
/* Check the cache, saving the 'most complete' as a
* starting point
*/
if (usecache) {
tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param);
tfnskey = apr_psprintf(msr->mp, "%x;%s", tfnscount, tfnspath);
crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey);
if (crec != NULL) {
last_crec = crec;
last_cached_tfn = tfnscount;
}
}
}
}
}
/* If the last cached tfn is the last in the list
* then we can stop here and just execute the action immediatly
*/
if (usecache && !multi_match && (crec != NULL) && (crec == last_crec)) {
crec->hits++;
if (crec->changed) {
var->value = apr_pmemdup(msr->mp, crec->val, crec->val_len);
var->value_len = crec->val_len;
}
msr_log(msr, 9, "T (%d) %s: \"%s\" [cached hits=%d]", crec->changed, crec->path, log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits);
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
{
apr_time_t t1 = apr_time_now();
#if defined(PERFORMANCE_MEASUREMENT)
rule->trans_time += (t1 - time_before_trans);
#endif
msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
(t1 - time_before_trans));
}
rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
if (rc < 0) {
return -1;
}
if (rc == RULE_MATCH) {
/* Return straight away if the transaction
* was intercepted - no need to process the remaining
* targets.
*/
if (msr->rule_was_intercepted) {
return RULE_MATCH;
}
}
continue; /* next target */
}
/* Perform transformations. */
tarr = apr_table_elts(normtab);
/* Make a copy of the variable value so that
* we can change it in-place.
*/
if (tarr->nelts) {
var->value = apr_pstrmemdup(mptmp, var->value, var->value_len);
/* var->value_len remains the same */
}
/* Execute transformations in a loop. */
/* Start after the last known cached transformation if we can */
if (!multi_match && (last_crec != NULL)) {
k = last_cached_tfn;
tfnspath = last_crec->path;
last_crec->hits++;
if ((changed = last_crec->changed) == 1) {
var->value = apr_pmemdup(msr->mp, last_crec->val, last_crec->val_len);
var->value_len = last_crec->val_len;
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "T (%d) %s: \"%s\" [partially cached hits=%d]", last_crec->changed, tfnspath, log_escape_nq_ex(mptmp, var->value, var->value_len), last_crec->hits);
}
}
else {
changed = 1;
tfnspath = NULL;
k = 0;
}
telts = (const apr_table_entry_t*)tarr->elts;
for (; k < tarr->nelts; k++) {
char *rval = NULL;
long int rval_length = -1;
/* In multi-match mode we execute the operator
* once at the beginning and then once every
* time the variable is changed by the transformation
* function.
*/
if (multi_match && changed) {
invocations++;
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
{
apr_time_t t1 = apr_time_now();
#if defined(PERFORMANCE_MEASUREMENT)
rule->trans_time += (t1 - time_before_trans);
#endif
msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
(t1 - time_before_trans));
}
rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
if (rc < 0) {
return -1;
}
if (rc == RULE_MATCH) {
match_count++;
/* Return straight away if the transaction
* was intercepted - no need to process the remaining
* targets.
*/
if (msr->rule_was_intercepted) {
return RULE_MATCH;
}
}
}
/* Perform one transformation. */
action = (msre_action *)telts[k].val;
metadata = (msre_tfn_metadata *)action->param_data;
/* Try to use the cache */
if (usecache) {
/* Generate the cache key */
tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param);
tfnskey = apr_psprintf(msr->mp, "%x;%s", (k + 1), tfnspath);
/* Try to fetch this transformation from cache */
#ifdef CACHE_DEBUG
msr_log(msr, 9, "CACHE: Fetching %s %s ", var->name, tfnskey);
#endif
crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey);
if (crec != NULL) {
crec->hits++;
if ((changed = crec->changed) == 1) {
var->value = apr_pmemdup(msr->mp, crec->val, crec->val_len);
var->value_len = crec->val_len;
}
msr_log(msr, 9, "T (%d) %s: \"%s\" [cached hits=%d]", crec->changed, metadata->name, log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits);
continue;
}
}
rc = metadata->execute(mptmp, (unsigned char *)var->value, var->value_len,
&rval, &rval_length);
if (rc < 0) {
return -1;
}
changed = rc;
var->value = rval;
var->value_len = rval_length;
/* Cache the transformation */
if (usecache) {
/* ENH1: Add flag to vars to tell which ones can change across phases store the rest in a global cache */
crec = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec));
if (crec == NULL) return -1;
crec->hits = 0;
crec->changed = changed;
crec->num = k + 1;
crec->path = tfnspath;
crec->val = changed ? apr_pmemdup(msr->mp, var->value, var->value_len) : NULL;
crec->val_len = changed ? var->value_len : 0;
#ifdef CACHE_DEBUG
msr_log(msr, 9, "CACHE: Caching %s=\"%.*s\"", tfnskey, var->value_len, log_escape_nq_ex(mptmp, var->value, var->value_len));
#endif
apr_table_setn(cachetab, tfnskey, (void *)crec);
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, metadata->name,
log_escape_nq_ex(mptmp, var->value, var->value_len));
}
}
}
/* Execute operator if multi-matching is not enabled,
* or if it is and we need to process the result of the
* last transformation.
*/
if (!multi_match || changed) {
invocations++;
#if !defined(PERFORMANCE_MEASUREMENT)
if (msr->txcfg->debuglog_level >= 4)
#endif
{
apr_time_t t1 = apr_time_now();
#if defined(PERFORMANCE_MEASUREMENT)
rule->trans_time += (t1 - time_before_trans);
#endif
msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
(t1 - time_before_trans));
}
rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
if (rc < 0) {
return -1;
}
if (rc == RULE_MATCH) {
match_count++;
/* Return straight away if the transaction
* was intercepted - no need to process the remaining
* targets.
*/
if (msr->rule_was_intercepted) {
return RULE_MATCH;
}
}
}
}
#ifdef CACHE_DEBUG
if (msr->txcfg->debuglog_level >= 9) {
apr_hash_index_t *hi;
void *dummy;
apr_table_t **tab;
const apr_array_header_t *ctarr;
const apr_table_entry_t *ctelts;
msre_cache_rec *rec;
int cn = 0;
int ti, ri;
for (hi = apr_hash_first(msr->mp, msr->tcache); hi; hi = apr_hash_next(hi)) {
apr_hash_this(hi, NULL, NULL, &dummy);
tab = (apr_table_t **)dummy;
if (tab == NULL) continue;
for (ti = PHASE_FIRST; ti <= PHASE_LAST; ti++) {
if (tab[ti] == NULL) continue;
ctarr = apr_table_elts(tab[ti]);
ctelts = (const apr_table_entry_t*)ctarr->elts;
for (ri = 0; ri < ctarr->nelts; ri++) {
cn++;
rec = (msre_cache_rec *)ctelts[ri].val;
if (rec->changed) {
msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %x;%s=\"%s\"", cn, msr->phase, rec->hits, rec->num, rec->path, log_escape_nq_ex(mptmp, rec->val, rec->val_len));
}
else {
msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %x;%s=<no change>", cn, msr->phase, rec->hits, rec->num, rec->path);
}
}
}
}
}
#endif
return (match_count ? RULE_MATCH : RULE_NO_MATCH);
}
/**
*
*/
static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
msre_actionset *acting_actionset = NULL;
char *my_error_msg = NULL;
int rc;
/* Choose the correct metadata/disruptive action actionset. */
acting_actionset = rule->actionset;
if (rule->chain_starter != NULL) {
acting_actionset = rule->chain_starter->actionset;
}
rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg);
if (rc < 0) {
msr_log(msr, 1, "%s", my_error_msg);
return -1;
}
/* A non-NULL error message means the rule matched. */
if (my_error_msg != NULL) {
/* Perform non-disruptive actions. */
msre_perform_nondisruptive_actions(msr, rule, rule->actionset, msr->msc_rule_mptmp);
/* Perform disruptive actions, but only if
* this rule is not part of a chain.
*/
if (rule->actionset->is_chained == 0) {
msre_perform_disruptive_actions(msr, rule, acting_actionset, msr->msc_rule_mptmp, my_error_msg);
}
}
return rc;
}
/**
*
*/
apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) {
/* Use a fresh memory sub-pool for processing each rule */
if (msr->msc_rule_mptmp == NULL) {
if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) {
return -1;
}
} else {
apr_pool_clear(msr->msc_rule_mptmp);
}
if (rule->type == RULE_TYPE_LUA) {
return msre_rule_process_lua(rule, msr);
}
return msre_rule_process_normal(rule, msr);
}
/**
* Checks whether the given rule ID is in the given range.
*/
int rule_id_in_range(int ruleid, const char *range) {
char *p = NULL, *saveptr = NULL;
char *data = NULL;
if (range == NULL) return 0;
data = strdup(range);
if (data == NULL) return 0;
p = apr_strtok(data, ",", &saveptr);
while(p != NULL) {
char *s = strstr(p, "-");
if (s == NULL) {
if (ruleid == atoi(p)) {
free(data);
return 1;
}
} else {
int start = atoi(p);
int end = atoi(s + 1);
if ((ruleid >= start)&&(ruleid <= end)) {
free(data);
return 1;
}
}
p = apr_strtok(NULL, ",", &saveptr);
}
free(data);
return 0;
}