diff --git a/CHANGES b/CHANGES
index 7472ad66..22ca350f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
02 Jan 2008 - 2.5.0-rc2
-----------------------
+ * Implemented SecRuleUpdateActionById.
+
* Phase 5 rules can now be removed via SecRuleRemoveBy* directives.
* Build is now 'configure' based (autotools).
diff --git a/apache2/Makefile.in b/apache2/Makefile.in
index 68a4233e..51d6a86c 100644
--- a/apache2/Makefile.in
+++ b/apache2/Makefile.in
@@ -98,11 +98,13 @@ test: t/run-tests.pl msc_test
mlogc:
@$(MAKE) -C mlogc-src mlogc \
&& cp -p mlogc-src/mlogc . \
- && echo "mlogc-src/INSTALL" \
+ && echo \
+ && echo "See: mlogc-src/INSTALL" \
&& echo
mlogc-static:
@$(MAKE) -C mlogc-src static \
&& cp -p mlogc-src/mlogc mlogc-static \
- && echo "mlogc-src/INSTALL" \
+ && echo \
+ && echo "See: mlogc-src/INSTALL" \
&& echo
diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c
index b553c9f6..846a0c80 100644
--- a/apache2/apache2_config.c
+++ b/apache2/apache2_config.c
@@ -742,6 +742,120 @@ static const char *add_marker(cmd_parms *cmd, directory_config *dcfg, const char
return NULL;
}
+/**
+ *
+ */
+static const char *update_rule_action(cmd_parms *cmd, directory_config *dcfg,
+ const char *p1, const char *p2)
+{
+ char *my_error_msg = NULL;
+ msre_rule *rule = NULL;
+ msre_actionset *new_actionset = NULL;
+ msre_ruleset *ruleset = dcfg->ruleset;
+ extern msc_engine *modsecurity;
+
+ /* Get the ruleset if one exists */
+ if ((ruleset == NULL)||(ruleset == NOT_SET_P)) {
+ return NULL;
+ }
+
+ #ifdef DEBUG_CONF
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
+ "Looking to update rule id=\"%s\" with \"%s\".", p1, p2);
+ #endif
+
+ /* Fetch the rule */
+ rule = msre_ruleset_fetch_rule(ruleset, p1);
+ if (rule == NULL) {
+ #ifdef DEBUG_CONF
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
+ "Failed to update rule id=\"%s\" with \"%s\": Rule not found.", p1, p2);
+ #endif
+ return NULL;
+ }
+
+ /* Check the rule actionset */
+ /* ENH: Can this happen? */
+ if (rule->actionset == NULL) {
+ return apr_psprintf(cmd->pool, "ModSecurity: Attempt to update action for rule \"%s\" failed: Rule does not have an actionset.", p1);
+ }
+
+ /* Create a new actionset */
+ new_actionset = msre_actionset_create(modsecurity->msre, p2, &my_error_msg);
+ if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR;
+ if (my_error_msg != NULL) return my_error_msg;
+
+ /* Must NOT change an id */
+ if ((new_actionset->id != NOT_SET_P) && (rule->actionset->id != NULL) && (strcmp(rule->actionset->id, new_actionset->id) != 0)) {
+ return apr_psprintf(cmd->pool, "ModSecurity: Rule IDs cannot be updated via SecRuleUpdateActionById.");
+ }
+
+ /* Must NOT alter the phase */
+ if ((new_actionset->phase != NOT_SET) && (rule->actionset->phase != new_actionset->phase)) {
+ return apr_psprintf(cmd->pool, "ModSecurity: Rule phases cannot be updated via SecRuleUpdateActionById.");
+ }
+
+ #ifdef DEBUG_CONF
+ {
+ const apr_array_header_t *tarr = apr_table_elts(rule->actionset->actions);
+ const apr_table_entry_t *telts = (const apr_table_entry_t*)tarr->elts;
+ char *actions = NULL;
+ int i;
+ for (i = 0; i < tarr->nelts; i++) {
+ msre_action *action = (msre_action *)telts[i].val;
+ actions = apr_pstrcat(ruleset->mp,
+ (actions == NULL) ? "" : actions,
+ (actions == NULL) ? "" : ",",
+ action->metadata->name,
+ (action->param == NULL) ? "" : ":'",
+ (action->param == NULL) ? "" : action->param,
+ (action->param == NULL) ? "" : "'",
+ NULL);
+ }
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
+ "Updating rule %pp id=\"%s\" action: \"%s\"",
+ rule,
+ (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
+ actions);
+ }
+ #endif
+
+ /* Merge new actions with the rule */
+ /* ENH: Will this leak the old actionset? */
+ rule->actionset = msre_actionset_merge(modsecurity->msre, rule->actionset,
+ new_actionset, 1);
+ msre_actionset_set_defaults(rule->actionset);
+
+ /* ENH: Change the unparsed string, but may be impossible. */
+
+ #ifdef DEBUG_CONF
+ {
+ const apr_array_header_t *tarr = apr_table_elts(rule->actionset->actions);
+ const apr_table_entry_t *telts = (const apr_table_entry_t*)tarr->elts;
+ char *actions = NULL;
+ int i;
+ for (i = 0; i < tarr->nelts; i++) {
+ msre_action *action = (msre_action *)telts[i].val;
+ actions = apr_pstrcat(ruleset->mp,
+ (actions == NULL) ? "" : actions,
+ (actions == NULL) ? "" : ",",
+ action->metadata->name,
+ (action->param == NULL) ? "" : ":'",
+ (action->param == NULL) ? "" : action->param,
+ (action->param == NULL) ? "" : "'",
+ NULL);
+ }
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
+ "Updated rule %pp id=\"%s\" action: \"%s\"",
+ rule,
+ (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
+ actions);
+ }
+ #endif
+
+ return NULL;
+}
+
/* -- Configuration directives -- */
static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1) {
@@ -1334,6 +1448,12 @@ static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg, const cha
return NULL;
}
+static const char *cmd_rule_update_action_by_id(cmd_parms *cmd, void *_dcfg,
+ const char *p1, const char *p2)
+{
+ return update_rule_action(cmd, (directory_config *)_dcfg, p1, p2);
+}
+
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";
@@ -1916,6 +2036,14 @@ const command_rec module_directives[] = {
"rule message for removal"
),
+ AP_INIT_TAKE2 (
+ "SecRuleUpdateActionById",
+ cmd_rule_update_action_by_id,
+ NULL,
+ CMD_SCOPE_ANY,
+ "updated action list"
+ ),
+
AP_INIT_TAKE1 (
"SecServerSignature",
cmd_server_signature,
diff --git a/apache2/re.c b/apache2/re.c
index e1ec3c57..80f8388d 100644
--- a/apache2/re.c
+++ b/apache2/re.c
@@ -559,7 +559,7 @@ msre_actionset *msre_actionset_create_default(msre_engine *engine) {
/**
* Sets the default values for the hard-coded actionset configuration.
*/
-static void msre_actionset_set_defaults(msre_actionset *actionset) {
+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;
@@ -1027,6 +1027,53 @@ int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) {
return 1;
}
+static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id,
+ const apr_array_header_t *phase_arr)
+{
+ msre_rule **rules = (msre_rule **)phase_arr->elts;
+ int i;
+
+ for (i = 0; i < phase_arr->nelts; i++) {
+ msre_rule *rule = (msre_rule *)rules[i];
+
+ if ( (rule->actionset != NULL)
+ && !rule->actionset->is_chained
+ && (rule->actionset->id != NULL)
+ && (strcmp(rule->actionset->id, id) == 0))
+ {
+ /* Return rule that matched unless it is a placeholder */
+ return (rule->placeholder == RULE_PH_NONE) ? rule : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Fetches rule from the ruleset all rules that match the given exception.
+ */
+msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id) {
+ msre_rule *rule = NULL;
+
+ if (ruleset == NULL) return NULL;
+
+ rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers);
+ if (rule != NULL) return rule;
+
+ rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body);
+ if (rule != NULL) return rule;
+
+ rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers);
+ if (rule != NULL) return rule;
+
+ rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body);
+ if (rule != NULL) return rule;
+
+ rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging);
+
+ return rule;
+}
+
static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
apr_array_header_t *phase_arr)
{
diff --git a/apache2/re.h b/apache2/re.h
index edeffab4..2be763da 100644
--- a/apache2/re.h
+++ b/apache2/re.h
@@ -106,6 +106,8 @@ msre_ruleset DSOLOCAL *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp);
int DSOLOCAL msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase);
+msre_rule DSOLOCAL *msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id);
+
int DSOLOCAL msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re);
/*
@@ -159,8 +161,6 @@ msre_rule DSOLOCAL *msre_rule_lua_create(msre_ruleset *ruleset,
const char *fn, int line, const char *script_filename,
const char *actions, char **error_msg);
-void DSOLOCAL msre_rule_actionset_init(msre_rule *rule);
-
apr_status_t DSOLOCAL msre_rule_process(msre_rule *rule, modsec_rec *msr);
#define VAR_SIMPLE 0 /* REQUEST_URI */
@@ -287,6 +287,8 @@ msre_actionset DSOLOCAL *msre_actionset_merge(msre_engine *engine, msre_actionse
msre_actionset DSOLOCAL *msre_actionset_create_default(msre_engine *engine);
+void DSOLOCAL msre_actionset_set_defaults(msre_actionset *actionset);
+
void DSOLOCAL msre_actionset_init(msre_actionset *actionset, msre_rule *rule);
typedef char *(*fn_action_validate_t)(msre_engine *engine, msre_action *action);
diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml
index eb0c904f..0db38a1d 100644
--- a/doc/modsecurity2-apache-reference.xml
+++ b/doc/modsecurity2-apache-reference.xml
@@ -1897,6 +1897,40 @@ end
+
+ SecRuleUpdateActionById
+
+ Description: Updates the action list of the
+ specified rule.
+
+ Syntax: SecRuleRemoveById RULEID ACTIONLIST
+
+ Example Usage: SecRuleUpdateActionById 12345
+ deny,status:403
+
+ Processing Phase: Any
+
+ Scope: Any
+
+ Dependencies/Notes: This directive merges the
+ specified action list with the rule's action list. There are two
+ limitations. The rule ID cannot be changed, nor can the phase. Further
+ note that actions that may be specified multiple times are appended to
+ the original.
+
+ SecAction \
+ "t:lowercase,phase:2,id:12345,pass,msg:'The Message',log,auditlog"
+SecRuleUpdateActionById 12345 "t:compressWhitespace,deny,status:403,msg:'A new message'
+
+ The example above will cause the rule to be executed as if it was
+ specified as follows:
+
+ SecAction \
+ "t:lowercase,phase:2,id:12345,log,auditlog,t:compressWhitespace,deny,status:403,msg:'A new message'"
+
+