Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / re.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
5  * This product is released under the terms of the General Public Licence,
6  * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7  * distribution) which contains the complete text of the licence.
8  *
9  * There are special exceptions to the terms and conditions of the GPL
10  * as it is applied to this software. View the full text of the exception in
11  * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
12  * distribution.
13  *
14  * If any of the files related to licensing are missing or if you have any
15  * other questions related to licensing please contact Breach Security, Inc.
16  * directly using the email address support@breach.com.
17  *
18  */
19 #include <ctype.h>
20
21 #include "re.h"
22
23 #if defined(WITH_LUA)
24 #include "msc_lua.h"
25 #endif
26
27 static const char *const severities[] = {
28     "EMERGENCY",
29     "ALERT",
30     "CRITICAL",
31     "ERROR",
32     "WARNING",
33     "NOTICE",
34     "INFO",
35     "DEBUG",
36     NULL,
37 };
38
39 /* -- Actions, variables, functions and operator functions ----------------- */
40
41 /**
42  * Remove actions with the same cardinality group from the actionset.
43  */
44 static void msre_actionset_cardinality_fixup(msre_actionset *actionset, msre_action *action) {
45     const apr_array_header_t *tarr = NULL;
46     const apr_table_entry_t *telts = NULL;
47     int i;
48
49     if ((actionset == NULL) || (action == NULL)) return;
50
51     tarr = apr_table_elts(actionset->actions);
52     telts = (const apr_table_entry_t*)tarr->elts;
53
54     for (i = 0; i < tarr->nelts; i++) {
55         msre_action *target = (msre_action *)telts[i].val;
56         if (target->metadata->cardinality_group == action->metadata->cardinality_group) {
57
58             apr_table_unset(actionset->actions, target->metadata->name);
59         }
60     }
61 }
62
63 /**
64  * Generate an action string from an actionset.
65  */
66 char *msre_actionset_generate_action_string(apr_pool_t *pool, const msre_actionset *actionset)
67 {
68     const apr_array_header_t *tarr = NULL;
69     const apr_table_entry_t *telts = NULL;
70     char *actions = NULL;
71     int chain;
72     int i;
73
74     if (actionset == NULL) return NULL;
75
76     chain = ((actionset->rule != NOT_SET_P) && actionset->rule->chain_starter) ? 1 : 0;
77
78     tarr = apr_table_elts(actionset->actions);
79     telts = (const apr_table_entry_t*)tarr->elts;
80
81     for (i = 0; i < tarr->nelts; i++) {
82         msre_action *action = (msre_action *)telts[i].val;
83         int use_quotes = 0;
84
85         if (chain) {
86             /* Skip some actions that are not used in a chain. */
87             if (   (action->metadata->type == ACTION_DISRUPTIVE)
88                 || (action->metadata->type == ACTION_METADATA)
89                 || (strcmp("log", action->metadata->name) == 0)
90                 || (strcmp("auditlog", action->metadata->name) == 0)
91                 || (strcmp("nolog", action->metadata->name) == 0)
92                 || (strcmp("noauditlog", action->metadata->name) == 0)
93                 || (strcmp("severity", action->metadata->name) == 0)
94                 || (strcmp("tag", action->metadata->name) == 0)
95                 || (strcmp("phase", action->metadata->name) == 0)) 
96             {
97                 continue;
98             }
99         }
100
101         /* Check if we need any quotes */
102         if (action->param != NULL) {
103             int j;
104             for(j = 0; action->param[j] != '\0'; j++) {
105                 if (isspace(action->param[j])) {
106                     use_quotes = 1;
107                     break;
108                 }
109             }
110             if (j == 0) use_quotes = 1;
111         }
112
113         actions = apr_pstrcat(pool,
114             (actions == NULL) ? "" : actions,
115             (actions == NULL) ? "" : ",",
116             action->metadata->name,
117             (action->param == NULL) ? "" : ":",
118             (use_quotes) ? "'" : "",
119             (action->param == NULL) ? "" : action->param,
120             (use_quotes) ? "'" : "",
121             NULL);
122     }
123
124     return actions;
125 }
126
127 /**
128  * Add an action to an actionset.
129  */
130 static void msre_actionset_action_add(msre_actionset *actionset, msre_action *action)
131 {
132     msre_action *add_action = action;
133
134     if ((actionset == NULL)) return;
135
136     /**
137      * The "block" action is just a placeholder for the parent action.
138      */
139     if ((actionset->parent_intercept_action_rec != NULL) && (actionset->parent_intercept_action_rec != NOT_SET_P) && (strcmp("block", action->metadata->name) == 0) && (strcmp("block", action->metadata->name) == 0)) {
140         /* revert back to parent */
141         actionset->intercept_action = actionset->parent_intercept_action;
142         add_action = actionset->parent_intercept_action_rec;
143     }
144
145     if ((add_action == NULL)) return;
146
147     if (add_action->metadata->cardinality_group != ACTION_CGROUP_NONE) {
148         msre_actionset_cardinality_fixup(actionset, add_action);
149     }
150
151     if (add_action->metadata->cardinality == ACTION_CARDINALITY_ONE) {
152         /* One action per actionlist. */
153         apr_table_setn(actionset->actions, add_action->metadata->name, (void *)add_action);
154     } else {
155         /* Multiple actions per actionlist. */
156         apr_table_addn(actionset->actions, add_action->metadata->name, (void *)add_action);
157     }
158 }
159
160 /**
161  * Creates msre_var instances (rule variables) out of the
162  * given text string and places them into the supplied table.
163  */
164 apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
165     apr_array_header_t *arr, char **error_msg)
166 {
167     const apr_array_header_t *tarr;
168     const apr_table_entry_t *telts;
169     apr_table_t *vartable;
170     unsigned int count = 0;
171     apr_status_t rc;
172     msre_var *var;
173     int i;
174
175     if (text == NULL) return -1;
176
177     /* Extract name & value pairs first */
178     vartable = apr_table_make(ruleset->mp, 10);
179     if (vartable == NULL) return -1;
180     rc = msre_parse_generic(ruleset->mp, text, vartable, error_msg);
181     if (rc < 0) return rc;
182
183     /* Loop through the table and create variables */
184     tarr = apr_table_elts(vartable);
185     telts = (const apr_table_entry_t*)tarr->elts;
186     for (i = 0; i < tarr->nelts; i++) {
187         var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg);
188         if (var == NULL) return -1;
189         *(msre_var **)apr_array_push(arr) = var;
190         count++;
191     }
192
193     return count;
194 }
195
196 /**
197  * Creates msre_action instances by parsing the given string, placing
198  * them into the supplied array.
199  */
200 apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset,
201     const char *text, char **error_msg)
202 {
203     const apr_array_header_t *tarr;
204     const apr_table_entry_t *telts;
205     apr_table_t *vartable;
206     unsigned int count = 0;
207     apr_status_t rc;
208     msre_action *action;
209     int i;
210
211     if (text == NULL) return -1;
212
213     /* Extract name & value pairs first */
214     vartable = apr_table_make(engine->mp, 10);
215     if (vartable == NULL) return -1;
216     rc = msre_parse_generic(engine->mp, text, vartable, error_msg);
217     if (rc < 0) return rc;
218
219     /* Loop through the table and create actions */
220     tarr = apr_table_elts(vartable);
221     telts = (const apr_table_entry_t*)tarr->elts;
222     for (i = 0; i < tarr->nelts; i++) {
223         /* Create action. */
224         action = msre_create_action(engine, telts[i].key, telts[i].val, error_msg);
225         if (action == NULL) return -1;
226
227         /* Initialise action (option). */
228         if (action->metadata->init != NULL) {
229             action->metadata->init(engine, actionset, action);
230         }
231
232         msre_actionset_action_add(actionset, action);
233
234         count++;
235     }
236
237     return count;
238 }
239
240 /**
241  * Locates variable metadata given the variable name.
242  */
243 msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name) {
244     return (msre_var_metadata *)apr_table_get(engine->variables, name);
245 }
246
247 /**
248  * Locates action metadata given the action name.
249  */
250 msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name) {
251     return (msre_action_metadata *)apr_table_get(engine->actions, name);
252 }
253
254 /**
255  * Creates a new variable instance given the variable name
256  * and an (optional) parameter.
257  */
258 msre_var *msre_create_var_ex(apr_pool_t *pool, msre_engine *engine, const char *name, const char *param,
259     modsec_rec *msr, char **error_msg)
260 {
261     const char *varparam = param;
262     msre_var *var = apr_pcalloc(pool, sizeof(msre_var));
263     if (var == NULL) return NULL;
264
265     if (error_msg == NULL) return NULL;
266     *error_msg = NULL;
267
268     /* Handle negation and member counting */
269     if (name[0] == '!') {
270         var->is_negated = 1;
271         var->name = name + 1;
272     }
273     else
274     if (name[0] == '&') {
275         var->is_counting = 1;
276         var->name = name + 1;
277     }
278     else {
279         var->name = name;
280     }
281
282     /* Treat HTTP_* targets as an alias for REQUEST_HEADERS:* */
283     if (   (var->name != NULL)
284         && (strlen(var->name) > 5)
285         && (strncmp("HTTP_", var->name, 5) == 0))
286     {
287         const char *oldname = var->name;
288         var->name = apr_pstrdup(pool, "REQUEST_HEADERS");
289         varparam = apr_pstrdup(pool, oldname + 5);
290     }
291
292
293     /* Resolve variable */
294     var->metadata = msre_resolve_var(engine, var->name);
295     if (var->metadata == NULL) {
296         *error_msg = apr_psprintf(engine->mp, "Unknown variable: %s", name);
297         return NULL;
298     }
299
300     /* The counting operator "&" can only be used against collections. */
301     if (var->is_counting) {
302         if (var->metadata->type == VAR_SIMPLE) {
303             *error_msg = apr_psprintf(engine->mp, "The & modificator does not apply to "
304                 "non-collection variables.");
305             return NULL;
306         }
307     }
308
309     /* Check the parameter. */
310     if (varparam == NULL) {
311         if (var->metadata->argc_min > 0) {
312             *error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for variable %s.",
313                 name);
314             return NULL;
315         }
316     } else { /* Parameter present */
317
318         /* Do we allow a parameter? */
319         if (var->metadata->argc_max == 0) {
320             *error_msg = apr_psprintf(engine->mp, "Variable %s does not support parameters.",
321                 name);
322             return NULL;
323         }
324
325         var->param = varparam;
326     }
327
328     return var;
329 }
330
331 /**
332  * Create a new variable object from the provided name and value.
333  *
334  * NOTE: this allocates out of the global pool and should not be used
335  *       per-request
336  */
337 msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
338     modsec_rec *msr, char **error_msg)
339 {
340     msre_var *var = msre_create_var_ex(ruleset->engine->mp, ruleset->engine, name, param, msr, error_msg);
341     if (var == NULL) return NULL;
342
343     /* Validate & initialise variable */
344     if (var->metadata->validate != NULL) {
345         *error_msg = var->metadata->validate(ruleset, var);
346         if (*error_msg != NULL) {
347             return NULL;
348         }
349     }
350
351     return var;
352 }
353
354 /**
355  * Creates a new action instance given its name and an (optional) parameter.
356  */
357 msre_action *msre_create_action(msre_engine *engine, const char *name, const char *param,
358     char **error_msg)
359 {
360     msre_action *action = apr_pcalloc(engine->mp, sizeof(msre_action));
361     if (action == NULL) return NULL;
362
363     if (error_msg == NULL) return NULL;
364     *error_msg = NULL;
365
366     /* Resolve action */
367     action->metadata = msre_resolve_action(engine, name);
368     if (action->metadata == NULL) {
369         *error_msg = apr_psprintf(engine->mp, "Unknown action: %s", name);
370         return NULL;
371     }
372
373     if (param == NULL) { /* Parameter not present */
374         if (action->metadata->argc_min > 0) {
375             *error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for action %s",
376                 name);
377             return NULL;
378         }
379     } else { /* Parameter present */
380
381         /* Should we allow the parameter? */
382         if (action->metadata->argc_max == 0) {
383             *error_msg = apr_psprintf(engine->mp, "Extra parameter provided to action %s", name);
384             return NULL;
385         }
386
387         /* Handle +/- modificators */
388         if ((param[0] == '+')||(param[0] == '-')) {
389             if (action->metadata->allow_param_plusminus == 0) {
390                 *error_msg = apr_psprintf(engine->mp,
391                      "Action %s does not allow +/- modificators.", name);
392                 return NULL;
393             }
394             else { /* Modificators allowed. */
395                 if (param[0] == '+') {
396                     action->param = param + 1;
397                     action->param_plusminus = POSITIVE_VALUE;
398                 } else
399                 if (param[0] == '-') {
400                     action->param = param + 1;
401                     action->param_plusminus = NEGATIVE_VALUE;
402                 }
403             }
404         } else {
405             action->param = param;
406         }
407
408         /* Validate parameter */
409         if (action->metadata->validate != NULL) {
410             *error_msg = action->metadata->validate(engine, action);
411             if (*error_msg != NULL) return NULL;
412         }
413     }
414
415     return action;
416 }
417
418 /**
419  * Generic parser that is used as basis for target and action parsing.
420  * It breaks up the input string into name-parameter pairs and places
421  * them into the given table.
422  */
423 int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable,
424     char **error_msg)
425 {
426     char *p = (char *)text;
427     int count = 0;
428
429     if (error_msg == NULL) return -1;
430     *error_msg = NULL;
431
432     count = 0;
433     while(*p != '\0') {
434         char *name = NULL, *value = NULL;
435
436         /* ignore whitespace */
437         while(isspace(*p)) p++;
438         if (*p == '\0') return count;
439
440         /* we are at the beginning of the name */
441         name = p;
442         while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */
443
444         /* get the name */
445         name = apr_pstrmemdup(mp, name, p - name);
446
447         if (*p != ':') { /* we don't have a parameter */
448             /* add to the table with no value */
449             apr_table_addn(vartable, name, NULL);
450             count++;
451
452             /* go over any whitespace present */
453             while(isspace(*p)) p++;
454
455             /* we're done */
456             if (*p == '\0') {
457                 return count;
458             }
459
460             /* skip over the separator character and continue */
461             if ((*p == ',')||(*p == '|')) {
462                 p++;
463                 continue;
464             }
465
466             *error_msg = apr_psprintf(mp, "Unexpected character at position %d: %s",
467                 (int)(p - text), text);
468             return -1;
469         }
470
471         /* we have a parameter */
472
473         p++; /* move over the colon */
474
475         /* we'll allow empty values */
476         if (*p == '\0') {
477             apr_table_addn(vartable, name, NULL);
478             count++;
479             return count;
480         }
481
482         if ((*p == ',')||(*p == '|')) {
483             apr_table_addn(vartable, name, NULL);
484             count++;
485             /* move over the separator char and continue */
486             p++;
487             continue;
488         }
489
490         /* we really have a parameter */
491
492         if (*p == '\'') { /* quoted value */
493             char *d = NULL;
494
495             p++; /* go over the openning quote */
496             value = d = strdup(p);
497             if (d == NULL) return -1;
498
499             for(;;) {
500                 if (*p == '\0') {
501                     *error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s",
502                         (int)(p - text), text);
503                     free(value);
504                     return -1;
505                 } else
506                 if (*p == '\\') {
507                     if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) {
508                         *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s",
509                             (int)(p - text), text);
510                         free(value);
511                         return -1;
512                     }
513                     p++;
514                     *(d++) = *(p++);
515                 } else
516                 if (*p == '\'') {
517                     *d = '\0';
518                     p++;
519                     break;
520                 }
521                 else {
522                     *(d++) = *(p++);
523                 }
524             }
525
526             d = value;
527             value = apr_pstrdup(mp, d);
528             free(d);
529         } else { /* non-quoted value */
530             value = p;
531             while((*p != '\0')&&(*p != ',')&&(*p != '|')&&(!isspace(*p))) p++;
532             value = apr_pstrmemdup(mp, value, p - value);
533         }
534
535         /* add to table */
536         apr_table_addn(vartable, name, value);
537         count++;
538
539         /* move to the first character of the next name-value pair */
540         while(isspace(*p)||(*p == ',')||(*p == '|')) p++;
541     }
542
543     return count;
544 }
545
546
547 /* -- Actionset functions -------------------------------------------------- */
548
549 /**
550  * Creates an actionset instance and (as an option) populates it by
551  * parsing the given string which contains a list of actions.
552  */
553 msre_actionset *msre_actionset_create(msre_engine *engine, const char *text,
554     char **error_msg)
555 {
556     msre_actionset *actionset = (msre_actionset *)apr_pcalloc(engine->mp,
557         sizeof(msre_actionset));
558     if (actionset == NULL) return NULL;
559
560     actionset->actions = apr_table_make(engine->mp, 25);
561     if (actionset->actions == NULL) return NULL;
562
563     /* Metadata */
564     actionset->id = NOT_SET_P;
565     actionset->rev = NOT_SET_P;
566     actionset->msg = NOT_SET_P;
567     actionset->logdata = NOT_SET_P;
568     actionset->phase = NOT_SET;
569     actionset->severity = -1;
570     actionset->rule = NOT_SET_P;
571
572     /* Flow */
573     actionset->is_chained = NOT_SET;
574     actionset->skip_count = NOT_SET;
575     actionset->skip_after = NOT_SET_P;
576
577     /* Disruptive */
578     actionset->parent_intercept_action_rec = NOT_SET_P;
579     actionset->intercept_action_rec = NOT_SET_P;
580     actionset->parent_intercept_action = NOT_SET;
581     actionset->intercept_action = NOT_SET;
582     actionset->intercept_uri = NOT_SET_P;
583     actionset->intercept_status = NOT_SET;
584     actionset->intercept_pause = NOT_SET;
585
586     /* Other */
587     actionset->auditlog = NOT_SET;
588     actionset->log = NOT_SET;
589
590     /* Parse the list of actions, if it's present */
591     if (text != NULL) {
592         if (msre_parse_actions(engine, actionset, text, error_msg) < 0) {
593             return NULL;
594         }
595     }
596
597     return actionset;
598 }
599
600 /**
601  * Create a (shallow) copy of the supplied actionset.
602  */
603 static msre_actionset *msre_actionset_copy(apr_pool_t *mp, msre_actionset *orig) {
604     msre_actionset *copy = NULL;
605
606     if (orig == NULL) return NULL;
607     copy = (msre_actionset *)apr_pmemdup(mp, orig, sizeof(msre_actionset));
608     if (copy == NULL) return NULL;
609     copy->actions = apr_table_copy(mp, orig->actions);
610
611     return copy;
612 }
613
614 /**
615  * Merges two actionsets into one.
616  */
617 msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent,
618     msre_actionset *child, int inherit_by_default)
619 {
620     msre_actionset *merged = NULL;
621     const apr_array_header_t *tarr;
622     const apr_table_entry_t *telts;
623     int i;
624
625     if (inherit_by_default == 0) {
626         /* There is nothing to merge in this case. */
627         return msre_actionset_copy(engine->mp, child);
628     }
629
630     /* Start with a copy of the parent configuration. */
631     merged = msre_actionset_copy(engine->mp, parent);
632     if (merged == NULL) return NULL;
633
634     if (child == NULL) {
635         /* The child actionset does not exist, hence
636          * go with the parent one.
637          */
638         return merged;
639     }
640
641     /* First merge the hard-coded stuff. */
642
643     /* Metadata */
644     if (child->id != NOT_SET_P) merged->id = child->id;
645     if (child->rev != NOT_SET_P) merged->rev = child->rev;
646     if (child->msg != NOT_SET_P) merged->msg = child->msg;
647     if (child->logdata != NOT_SET_P) merged->logdata = child->logdata;
648     if (child->severity != NOT_SET) merged->severity = child->severity;
649     if (child->phase != NOT_SET) merged->phase = child->phase;
650     if (child->rule != NOT_SET_P) merged->rule = child->rule;
651
652     /* Flow */
653     merged->is_chained = child->is_chained;
654     if (child->skip_count != NOT_SET) merged->skip_count = child->skip_count;
655     if (child->skip_after != NOT_SET_P) merged->skip_after = child->skip_after;
656
657     /* Disruptive */
658     if (child->intercept_action != NOT_SET) {
659         merged->intercept_action_rec = child->intercept_action_rec;
660         merged->intercept_action = child->intercept_action;
661         merged->intercept_uri = child->intercept_uri;
662     }
663
664     if (child->intercept_status != NOT_SET) merged->intercept_status = child->intercept_status;
665     if (child->intercept_pause != NOT_SET) merged->intercept_pause = child->intercept_pause;
666
667     /* Other */
668     if (child->auditlog != NOT_SET) merged->auditlog = child->auditlog;
669     if (child->log != NOT_SET) merged->log = child->log;
670
671
672     /* Now merge the actions. */
673
674     tarr = apr_table_elts(child->actions);
675     telts = (const apr_table_entry_t*)tarr->elts;
676     for (i = 0; i < tarr->nelts; i++) {
677         msre_actionset_action_add(merged, (msre_action *)telts[i].val);
678     }
679
680     return merged;
681 }
682
683 /**
684  * Creates an actionset that contains a default list of actions.
685  */
686 msre_actionset *msre_actionset_create_default(msre_engine *engine) {
687     char  *my_error_msg = NULL;
688     return msre_actionset_create(engine,
689         "phase:2,log,auditlog,pass",
690         &my_error_msg);
691 }
692
693 /**
694  * Sets the default values for the hard-coded actionset configuration.
695  */
696 void msre_actionset_set_defaults(msre_actionset *actionset) {
697     /* Metadata */
698     if (actionset->id == NOT_SET_P) actionset->id = NULL;
699     if (actionset->rev == NOT_SET_P) actionset->rev = NULL;
700     if (actionset->msg == NOT_SET_P) actionset->msg = NULL;
701     if (actionset->logdata == NOT_SET_P) actionset->logdata = NULL;
702     if (actionset->phase == NOT_SET) actionset->phase = 2;
703     if (actionset->severity == -1) {} /* leave at -1 */
704     if (actionset->rule == NOT_SET_P) actionset->rule = NULL;
705
706     /* Flow */
707     if (actionset->is_chained == NOT_SET) actionset->is_chained = 0;
708     if (actionset->skip_count == NOT_SET) actionset->skip_count = 0;
709     if (actionset->skip_after == NOT_SET_P) actionset->skip_after = NULL;
710
711     /* Disruptive */
712     if (actionset->parent_intercept_action_rec == NOT_SET_P) actionset->parent_intercept_action_rec = NULL;
713     if (actionset->intercept_action_rec == NOT_SET_P) actionset->intercept_action_rec = NULL;
714     if (actionset->parent_intercept_action == NOT_SET) actionset->parent_intercept_action = ACTION_NONE;
715     if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE;
716     if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL;
717     if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403;
718     if (actionset->intercept_pause == NOT_SET) actionset->intercept_pause = 0;
719
720     /* Other */
721     if (actionset->auditlog == NOT_SET) actionset->auditlog = 1;
722     if (actionset->log == NOT_SET) actionset->log = 1;
723 }
724
725 /* -- Engine functions ----------------------------------------------------- */
726
727 /**
728  * Creates a new engine instance.
729  */
730 msre_engine *msre_engine_create(apr_pool_t *parent_pool) {
731     msre_engine *engine;
732     apr_pool_t *mp;
733
734     /* Create new memory pool */
735     if (apr_pool_create(&mp, parent_pool) != APR_SUCCESS) return NULL;
736
737     /* Init fields */
738     engine = apr_pcalloc(mp, sizeof(msre_engine));
739     if (engine == NULL) return NULL;
740     engine->mp = mp;
741     engine->tfns = apr_table_make(mp, 25);
742     if (engine->tfns == NULL) return NULL;
743     engine->operators = apr_table_make(mp, 25);
744     if (engine->operators == NULL) return NULL;
745     engine->variables = apr_table_make(mp, 25);
746     if (engine->variables == NULL) return NULL;
747     engine->actions = apr_table_make(mp, 25);
748     if (engine->actions == NULL) return NULL;
749
750     return engine;
751 }
752
753 /**
754  * Destroys an engine instance, releasing the consumed memory.
755  */
756 void msre_engine_destroy(msre_engine *engine) {
757     /* Destroyed automatically by the parent pool.
758      * apr_pool_destroy(engine->mp);
759      */
760 }
761
762
763 /* -- Recipe functions ----------------------------------------------------- */
764
765 #define NEXT_CHAIN  1
766 #define NEXT_RULE   2
767 #define SKIP_RULES  3
768
769
770
771 /**
772  * Default implementation of the ruleset phase processing; it processes
773  * the rules in the ruleset attached to the currently active
774  * transaction phase.
775  */
776 #if defined(PERFORMANCE_MEASUREMENT)
777
778 #define PERFORMANCE_MEASUREMENT_LOOP 1000
779
780 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr);
781
782 apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
783     apr_array_header_t *arr = NULL;
784     msre_rule **rules = NULL;
785     apr_status_t rc;
786     int i;
787
788     switch (msr->phase) {
789         case PHASE_REQUEST_HEADERS :
790             arr = ruleset->phase_request_headers;
791             break;
792         case PHASE_REQUEST_BODY :
793             arr = ruleset->phase_request_body;
794             break;
795         case PHASE_RESPONSE_HEADERS :
796             arr = ruleset->phase_response_headers;
797             break;
798         case PHASE_RESPONSE_BODY :
799             arr = ruleset->phase_response_body;
800             break;
801         case PHASE_LOGGING :
802             arr = ruleset->phase_logging;
803             break;
804         default :
805             msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
806             return -1;
807     }
808
809     rules = (msre_rule **)arr->elts;
810     for (i = 0; i < arr->nelts; i++) {
811         msre_rule *rule = rules[i];
812         rule->execution_time = 0;
813     }
814
815     for (i = 0; i < PERFORMANCE_MEASUREMENT_LOOP; i++) {
816         rc = msre_ruleset_process_phase_(ruleset, msr);
817     }
818
819     msr_log(msr, 1, "Phase %d", msr->phase);
820
821     rules = (msre_rule **)arr->elts;
822     for (i = 0; i < arr->nelts; i++) {
823         msre_rule *rule = rules[i];
824
825         /* Ignore markers, which are never processed. */
826         if (rule->placeholder == RULE_PH_MARKER) continue;
827
828         msr_log(msr, 1, "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"]: %u usec", rule,
829             ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
830             rule->filename != NULL ? rule->filename : "-",
831             rule->line_num,
832             (rule->execution_time / PERFORMANCE_MEASUREMENT_LOOP));
833     }
834
835     return rc;
836 }
837
838 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr) {
839 #else
840 apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
841 #endif
842     apr_array_header_t *arr = NULL;
843     msre_rule **rules;
844     apr_status_t rc;
845     const char *skip_after = NULL;
846     int i, mode, skip;
847
848     /* First determine which set of rules we need to use. */
849     switch (msr->phase) {
850         case PHASE_REQUEST_HEADERS :
851             arr = ruleset->phase_request_headers;
852             break;
853         case PHASE_REQUEST_BODY :
854             arr = ruleset->phase_request_body;
855             break;
856         case PHASE_RESPONSE_HEADERS :
857             arr = ruleset->phase_response_headers;
858             break;
859         case PHASE_RESPONSE_BODY :
860             arr = ruleset->phase_response_body;
861             break;
862         case PHASE_LOGGING :
863             arr = ruleset->phase_logging;
864             break;
865         default :
866             msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
867             return -1;
868     }
869
870     if (msr->txcfg->debuglog_level >= 9) {
871         msr_log(msr, 9, "This phase consists of %d rule(s).", arr->nelts);
872     }
873
874     /* Loop through the rules in the selected set. */
875     skip = 0;
876     mode = NEXT_RULE;
877     rules = (msre_rule **)arr->elts;
878     for (i = 0; i < arr->nelts; i++) {
879         msre_rule *rule = rules[i];
880         #if defined(PERFORMANCE_MEASUREMENT)
881         apr_time_t time1 = 0;
882         #endif
883
884         /* Reset the rule interception flag */
885         msr->rule_was_intercepted = 0;
886
887         /* SKIP_RULES is used to skip all rules until we hit a placeholder
888          * with the specified rule ID and then resume execution after that.
889          */
890         if (mode == SKIP_RULES) {
891             /* Go to the next rule if we have not yet hit the skip_after ID */
892             if ((rule->placeholder == RULE_PH_NONE) || (rule->actionset->id == NULL) || (strcmp(skip_after, rule->actionset->id) != 0)) {
893                 if (msr->txcfg->debuglog_level >= 9) {
894                     if (rule->chain_starter != NULL) {
895                         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);
896
897                     }
898                     else {
899                         msr_log(msr, 9, "Skipping rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->actionset->id ? rule->actionset->id : "(none)"), skip_after);
900
901                     }
902                 }
903                 continue;
904             }
905             if (msr->txcfg->debuglog_level >= 9) {
906                 msr_log(msr, 9, "Found rule %pp id=\"%s\".", rule, skip_after);
907             }
908
909             /* Go to the rule *after* this one to continue execution. */
910             if (msr->txcfg->debuglog_level >= 4) {
911                 msr_log(msr, 4, "Continuing execution after rule id=\"%s\".", skip_after);
912             }
913
914             skip_after = NULL;
915             mode = NEXT_RULE;
916             continue;
917         }
918
919         /* Skip any rule marked as a placeholder */
920         if (rule->placeholder != RULE_PH_NONE) {
921             continue;
922         }
923
924         /* NEXT_CHAIN is used when one of the rules in a chain
925          * fails to match and then we need to skip the remaining
926          * rules in that chain in order to get to the next
927          * rule that can execute.
928          */
929         if (mode == NEXT_CHAIN) {
930             if (rule->actionset->is_chained == 0) {
931                 mode = NEXT_RULE;
932             }
933
934             /* Go to the next rule. */
935             continue;
936         }
937
938         /* If we are here that means the mode is NEXT_RULE, which
939          * then means we have done processing any chains. However,
940          * if the "skip" parameter is set we need to skip over.
941          */
942         if ((mode == NEXT_RULE)&&(skip > 0)) {
943             /* Decrement the skip counter by one. */
944             skip--;
945
946             /* If the current rule is part of a chain then
947              * we need to skip over the entire chain. Thus
948              * we change the mode to NEXT_CHAIN. The skip
949              * counter will not decrement as we are moving
950              * over the rules belonging to the chain.
951              */
952             if (rule->actionset->is_chained) {
953                 mode = NEXT_CHAIN;
954             }
955
956             /* Go to the next rule. */
957             continue;
958         }
959
960         /* Check if this rule was removed at runtime */
961         if ((rule->actionset->id !=NULL) && (! apr_is_empty_array(msr->removed_rules))) {
962                 int j;
963                 int do_process = 1;
964                 const char *range;
965
966                 for(j = 0; j < msr->removed_rules->nelts; j++) {
967                     range = ((const char**)msr->removed_rules->elts)[j];
968
969                     if (msr->txcfg->debuglog_level >= 9) {
970                         msr_log(msr, 9, "Checking removal of rule id=\"%s\" against: %s", rule->actionset->id, range);
971                     }
972
973                     if (rule_id_in_range(atoi(rule->actionset->id), range)) {
974                         do_process = 0;
975                         break;
976                     }
977                 }
978
979                 /* Go to the next rule if this one has been removed. */
980                 if (do_process == 0) {
981                     if (msr->txcfg->debuglog_level >= 5) {
982                         msr_log(msr, 5, "Not processing %srule id=\"%s\": "
983                                         "removed by ctl action",
984                                         rule->actionset->is_chained ? "chained " : "",
985                                         rule->actionset->id);
986                     }
987
988                     /* Skip the whole chain, if this is a chained rule */
989                     if (rule->actionset->is_chained) {
990                         mode = NEXT_CHAIN;
991                     }
992
993                     continue;
994                 }
995         }
996
997         if (msr->txcfg->debuglog_level >= 4) {
998             apr_pool_t *p = msr->mp;
999             const char *fn = NULL;
1000             const char *id = NULL;
1001             const char *rev = NULL;
1002
1003             if (rule->filename != NULL) {
1004                 fn = apr_psprintf(p, " [file \"%s\"] [line \"%d\"]", rule->filename, rule->line_num);
1005             }
1006
1007             if (rule->actionset != NULL && rule->actionset->id != NULL) {
1008                 id = apr_psprintf(p, " [id \"%s\"]", rule->actionset->id);
1009             }
1010
1011             if (rule->actionset != NULL && rule->actionset->rev != NULL) {
1012                 rev = apr_psprintf(p, " [rev \"%s\"]", rule->actionset->rev);
1013             }
1014
1015             msr_log(msr, 4, "Recipe: Invoking rule %pp;%s%s%s.",
1016                     rule, (fn ? fn : ""), (id ? id : ""), (rev ? rev : ""));
1017             msr_log(msr, 5, "Rule %pp: %s", rule, rule->unparsed);
1018         }
1019
1020         #if defined(PERFORMANCE_MEASUREMENT)
1021         time1 = apr_time_now();
1022         #endif
1023
1024         rc = msre_rule_process(rule, msr);
1025
1026         #if defined(PERFORMANCE_MEASUREMENT)
1027         rule->execution_time += (apr_time_now() - time1);
1028         #endif
1029
1030         if (msr->txcfg->debuglog_level >= 4) {
1031             msr_log(msr, 4, "Rule returned %d.", rc);
1032         }
1033
1034         if (rc == RULE_NO_MATCH) {
1035             if (rule->actionset->is_chained) {
1036                 /* If the current rule is part of a chain then
1037                  * we need to skip over all the rules in the chain.
1038                  */
1039                 mode = NEXT_CHAIN;
1040                 if (msr->txcfg->debuglog_level >= 9) {
1041                     msr_log(msr, 9, "No match, chained -> mode NEXT_CHAIN.");
1042                 }
1043             } else {
1044                 /* This rule is not part of a chain so we simply
1045                  * move to the next rule.
1046                  */
1047                 mode = NEXT_RULE;
1048                 if (msr->txcfg->debuglog_level >= 9) {
1049                     msr_log(msr, 9, "No match, not chained -> mode NEXT_RULE.");
1050                 }
1051             }
1052         }
1053         else
1054         if (rc == RULE_MATCH) {
1055             if (msr->rule_was_intercepted) {
1056                 /* If the transaction was intercepted by this rule we will
1057                  * go back. Do note that we are relying on the
1058                  * rule to know if it is a part of a chain and
1059                  * not intercept if it is.
1060                  */
1061                 if (msr->txcfg->debuglog_level >= 9) {
1062                     msr_log(msr, 9, "Match, intercepted -> returning.");
1063                 }
1064                 return 1;
1065             }
1066
1067             if (rule->actionset->skip_after != NULL) {
1068                 skip_after = rule->actionset->skip_after;
1069                 mode = SKIP_RULES;
1070
1071                 if (msr->txcfg->debuglog_level >= 9) {
1072                     msr_log(msr, 9, "Skipping after rule %pp id=\"%s\" -> mode SKIP_RULES.", rule, skip_after);
1073                 }
1074
1075                 continue;
1076             }
1077
1078             /* We had a match but the transaction was not
1079              * intercepted. In that case we proceed with the
1080              * next rule...
1081              */
1082             mode = NEXT_RULE;
1083             if (msr->txcfg->debuglog_level >= 9) {
1084                 msr_log(msr, 9, "Match -> mode NEXT_RULE.");
1085             }
1086
1087             /* ...unless we need to skip, in which case we
1088              * determine how many rules/chains we need to
1089              * skip and configure the counter accordingly.
1090              */
1091             if (rule->actionset->is_chained == 0) {
1092                 if (rule->chain_starter != NULL) {
1093                     if (rule->chain_starter->actionset->skip_count > 0) {
1094                         skip = rule->chain_starter->actionset->skip_count;
1095                         if (msr->txcfg->debuglog_level >= 4) {
1096                             msr_log(msr, 4, "Skipping %d rules/chains (from a chain).", skip);
1097                         }
1098                     }
1099                 }
1100                 else if (rule->actionset->skip_count > 0) {
1101                     skip = rule->actionset->skip_count;
1102                     if (msr->txcfg->debuglog_level >= 4) {
1103                         msr_log(msr, 4, "Skipping %d rules/chains.", skip);
1104                     }
1105                 }
1106             }
1107         }
1108         else if (rc < 0) {
1109             msr_log(msr, 1, "Rule processing failed.");
1110             return -1;
1111         }
1112         else {
1113             msr_log(msr, 1, "Rule processing failed with unknown return code: %d.", rc);
1114             return -1;
1115         }
1116     }
1117
1118     /* ENH warn if chained rules are missing. */
1119
1120     return 0;
1121 }
1122
1123 /**
1124  * Creates a ruleset that will be handled by the default
1125  * implementation.
1126  */
1127 msre_ruleset *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp) {
1128     msre_ruleset *ruleset;
1129
1130     ruleset = apr_pcalloc(mp, sizeof(msre_ruleset));
1131     if (ruleset == NULL) return NULL;
1132     ruleset->mp = mp;
1133     ruleset->engine = engine;
1134
1135     ruleset->phase_request_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1136     ruleset->phase_request_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1137     ruleset->phase_response_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1138     ruleset->phase_response_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1139     ruleset->phase_logging = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1140
1141     return ruleset;
1142 }
1143
1144 /**
1145  * Adds one rule to the given phase of the ruleset.
1146  */
1147 int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) {
1148     apr_array_header_t *arr = NULL;
1149
1150     switch (phase) {
1151         case PHASE_REQUEST_HEADERS :
1152             arr = ruleset->phase_request_headers;
1153             break;
1154         case PHASE_REQUEST_BODY :
1155             arr = ruleset->phase_request_body;
1156             break;
1157         case PHASE_RESPONSE_HEADERS :
1158             arr = ruleset->phase_response_headers;
1159             break;
1160         case PHASE_RESPONSE_BODY :
1161             arr = ruleset->phase_response_body;
1162             break;
1163         case PHASE_LOGGING :
1164             arr = ruleset->phase_logging;
1165             break;
1166         default :
1167             return -1;
1168     }
1169
1170     /* ENH verify the rule's use of targets is consistent with
1171      * the phase it selected to run at.
1172      */
1173
1174     msre_actionset_set_defaults(rule->actionset);
1175     rule->actionset->rule = rule;
1176
1177     *(const msre_rule **)apr_array_push(arr) = rule;
1178
1179     return 1;
1180 }
1181
1182 static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id,
1183     const apr_array_header_t *phase_arr)
1184 {
1185     msre_rule **rules = (msre_rule **)phase_arr->elts;
1186     int i;
1187
1188     for (i = 0; i < phase_arr->nelts; i++) {
1189         msre_rule *rule = (msre_rule *)rules[i];
1190
1191         /* Rule with an action, not a sub-rule (chain) and a matching id */
1192         if (  (rule->actionset != NULL)
1193            && (!rule->actionset->is_chained || !rule->chain_starter)
1194            && (rule->actionset->id != NULL)
1195            && (strcmp(rule->actionset->id, id) == 0))
1196         {
1197             /* Return rule that matched unless it is a placeholder */
1198             return (rule->placeholder == RULE_PH_NONE) ? rule : NULL;
1199         }
1200     }
1201
1202     return NULL;
1203 }
1204
1205 /**
1206  * Fetches rule from the ruleset all rules that match the given exception.
1207  */
1208 msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id) {
1209     msre_rule *rule = NULL;
1210
1211     if (ruleset == NULL) return NULL;
1212
1213     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers);
1214     if (rule != NULL) return rule;
1215
1216     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body);
1217     if (rule != NULL) return rule;
1218
1219     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers);
1220     if (rule != NULL) return rule;
1221
1222     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body);
1223     if (rule != NULL) return rule;
1224
1225     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging);
1226
1227     return rule;
1228 }
1229
1230 static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
1231     apr_array_header_t *phase_arr)
1232 {
1233     msre_rule **rules;
1234     int i, j, mode, removed_count;
1235
1236     j = 0;
1237     mode = 0;
1238     removed_count = 0;
1239     rules = (msre_rule **)phase_arr->elts;
1240     for (i = 0; i < phase_arr->nelts; i++) {
1241         msre_rule *rule = (msre_rule *)rules[i];
1242
1243         if (mode == 0) { /* Looking for next rule. */
1244             int remove_rule = 0;
1245
1246             /* Only remove non-placeholder rules */
1247             if (rule->placeholder == RULE_PH_NONE) {
1248                 switch(re->type) {
1249                     case RULE_EXCEPTION_REMOVE_ID :
1250                         if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
1251                             int ruleid = atoi(rule->actionset->id);
1252
1253                             if (rule_id_in_range(ruleid, re->param)) {
1254                                 remove_rule = 1;
1255                             }
1256                         }
1257
1258                         break;
1259
1260                     case RULE_EXCEPTION_REMOVE_MSG :
1261                         if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
1262                             char *my_error_msg = NULL;
1263
1264                             int rc = msc_regexec(re->param_data,
1265                                 rule->actionset->msg, strlen(rule->actionset->msg),
1266                                 &my_error_msg);
1267                             if (rc >= 0) {
1268                                 remove_rule = 1;
1269                             }
1270                         }
1271
1272                         break;
1273                 }
1274             }
1275
1276             if (remove_rule) {
1277                 /* Do not increment j. */
1278                 removed_count++;
1279                 if (rule->actionset->is_chained) mode = 2; /* Remove rules in this chain. */
1280             } else {
1281                 if (rule->actionset->is_chained) mode = 1; /* Keep rules in this chain. */
1282                 rules[j++] = rules[i];
1283             }
1284         } else { /* Handling rule that is part of a chain. */
1285             if (mode == 2) { /* We want to remove the rule. */
1286                 /* Do not increment j. */
1287                 removed_count++;
1288             } else {
1289                 rules[j++] = rules[i];
1290             }
1291
1292             if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
1293         }
1294     }
1295
1296     /* Update the number of rules in the array. */
1297     phase_arr->nelts -= removed_count;
1298
1299     return 0;
1300 }
1301
1302 /**
1303  * Removes from the ruleset all rules that match the given exception.
1304  */
1305 int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re) {
1306     int count = 0;
1307
1308     if (ruleset == NULL) return 0;
1309
1310     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_headers);
1311     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body);
1312     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers);
1313     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body);
1314     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_logging);
1315
1316     return count;
1317 }
1318
1319
1320 /* -- Rule functions ------------------------------------------------------- */
1321
1322 /**
1323  * Returns the name of the supplied severity level.
1324  */
1325 static const char *msre_format_severity(int severity) {
1326     if ((severity >= 0)&&(severity <= 7)) {
1327         return severities[severity];
1328     }
1329     else {
1330         return "(invalid value)";
1331     }
1332 }
1333
1334 /**
1335  * Creates a string containing the metadata of the supplied rule.
1336  */
1337 char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) {
1338     const apr_array_header_t *tarr;
1339     const apr_table_entry_t *telts;
1340     char *id = "";
1341     char *rev = "";
1342     char *msg = "";
1343     char *logdata = "";
1344     char *severity = "";
1345     char *tags = "";
1346     char *fn = "";
1347     int k;
1348
1349     if (actionset == NULL) return "";
1350
1351     if ((actionset->rule != NULL) && (actionset->rule->filename != NULL)) {
1352         fn = apr_psprintf(msr->mp, " [file \"%s\"] [line \"%d\"]",
1353                           actionset->rule->filename, actionset->rule->line_num);
1354     }
1355     if (actionset->id != NULL) {
1356         id = apr_psprintf(msr->mp, " [id \"%s\"]",
1357                           log_escape(msr->mp, actionset->id));
1358     }
1359     if (actionset->rev != NULL) {
1360         rev = apr_psprintf(msr->mp, " [rev \"%s\"]",
1361                            log_escape(msr->mp, actionset->rev));
1362     }
1363     if (actionset->msg != NULL) {
1364         /* Expand variables in the message string. */
1365         msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1366         var->value = (char *)actionset->msg;
1367         var->value_len = strlen(actionset->msg);
1368         expand_macros(msr, var, NULL, msr->mp);
1369
1370         msg = apr_psprintf(msr->mp, " [msg \"%s\"]",
1371                            log_escape_ex(msr->mp, var->value, var->value_len));
1372     }
1373     if (actionset->logdata != NULL) {
1374         /* Expand variables in the message string. */
1375         msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1376         var->value = (char *)actionset->logdata;
1377         var->value_len = strlen(actionset->logdata);
1378         expand_macros(msr, var, NULL, msr->mp);
1379
1380         logdata = apr_psprintf(msr->mp, " [data \"%s\"]",
1381                            log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len));
1382
1383         /* If it is > 512 bytes, then truncate at 512 with ellipsis.
1384          * NOTE: 512 actual data + 9 bytes of label = 521
1385          */
1386         if (strlen(logdata) > 521) {
1387             logdata[517] = '.';
1388             logdata[518] = '.';
1389             logdata[519] = '.';
1390             logdata[520] = '"';
1391             logdata[521] = ']';
1392             logdata[522] = '\0';
1393         }
1394     }
1395     if ((actionset->severity >= 0)&&(actionset->severity <= 7)) {
1396         severity = apr_psprintf(msr->mp, " [severity \"%s\"]",
1397                                 msre_format_severity(actionset->severity));
1398     }
1399
1400     /* Extract rule tags from the action list. */
1401     tarr = apr_table_elts(actionset->actions);
1402     telts = (const apr_table_entry_t*)tarr->elts;
1403
1404     for (k = 0; k < tarr->nelts; k++) {
1405         msre_action *action = (msre_action *)telts[k].val;
1406         if (strcmp(telts[k].key, "tag") == 0) {
1407             tags = apr_psprintf(msr->mp, "%s [tag \"%s\"]", tags,
1408                 log_escape(msr->mp, action->param));
1409         }
1410     }
1411
1412     return apr_pstrcat(msr->mp, fn, id, rev, msg, logdata, severity, tags, NULL);
1413 }
1414
1415 char * msre_rule_generate_unparsed(apr_pool_t *pool,  const msre_rule *rule, const char *targets,
1416     const char *args, const char *actions)
1417 {
1418     char *unparsed = NULL;
1419     const char *r_targets = targets;
1420     const char *r_args = args;
1421     const char *r_actions = actions;
1422
1423     if (r_targets == NULL) {
1424         r_targets = rule->p1;
1425     }
1426     if (r_args == NULL) {
1427         r_args = apr_pstrcat(pool, (rule->op_negated ? "!" : ""), "@", rule->op_name, " ", rule->op_param, NULL);
1428     }
1429     if (r_actions == NULL) {
1430         r_actions = msre_actionset_generate_action_string(pool, rule->actionset);
1431     }
1432
1433     switch (rule->type) {
1434     case RULE_TYPE_NORMAL:
1435         if (r_actions == NULL) {
1436             unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\"",
1437                            log_escape(pool, r_targets), log_escape(pool, r_args));
1438         }
1439         else {
1440             unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\" \"%s\"",
1441                            log_escape(pool, r_targets), log_escape(pool, r_args),
1442                            log_escape(pool, r_actions));
1443         }
1444         break;
1445     case RULE_TYPE_ACTION:
1446         unparsed = apr_psprintf(pool, "SecAction \"%s\"",
1447                        log_escape(pool, r_actions));
1448         break;
1449     case RULE_TYPE_MARKER:
1450         unparsed = apr_psprintf(pool, "SecMarker \"%s\"", rule->actionset->id);
1451         break;
1452     #if defined(WITH_LUA)
1453     case RULE_TYPE_LUA:
1454         /* SecRuleScript */
1455         if (r_actions == NULL) {
1456             unparsed = apr_psprintf(pool, "SecRuleScript \"%s\"", r_args);
1457         }
1458         else {
1459             unparsed = apr_psprintf(pool, "SecRuleScript \"%s\" \"%s\"",
1460                            r_args, log_escape(pool, r_actions));
1461         }
1462         break;
1463     #endif
1464     }
1465
1466     return unparsed;
1467 }
1468
1469 /**
1470  * Assembles a new rule using the strings that contain a list
1471  * of targets (variables), arguments, and actions.
1472  */
1473 msre_rule *msre_rule_create(msre_ruleset *ruleset, int type,
1474     const char *fn, int line, const char *targets,
1475     const char *args, const char *actions, char **error_msg)
1476 {
1477     msre_rule *rule;
1478     char *my_error_msg;
1479     const char *argsp;
1480     int rc;
1481
1482     if (error_msg == NULL) return NULL;
1483     *error_msg = NULL;
1484
1485     rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
1486     if (rule == NULL) return NULL;
1487
1488     rule->type = type;
1489     rule->ruleset = ruleset;
1490     rule->targets = apr_array_make(ruleset->mp, 10, sizeof(const msre_var *));
1491     rule->p1 = apr_pstrdup(ruleset->mp, targets);
1492     rule->filename = apr_pstrdup(ruleset->mp, fn);
1493     rule->line_num = line;
1494
1495     /* Parse targets */
1496     rc = msre_parse_targets(ruleset, targets, rule->targets, &my_error_msg);
1497     if (rc < 0) {
1498         *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
1499         return NULL;
1500     }
1501
1502     /* Parse args */
1503     argsp = args;
1504
1505     /* Is negation used? */
1506     if (*argsp == '!') {
1507         rule->op_negated = 1;
1508         argsp++;
1509         while((isspace(*argsp))&&(*argsp != '\0')) argsp++;
1510     }
1511
1512     /* Is the operator explicitly selected? */
1513     if (*argsp != '@') {
1514         /* Go with a regular expression. */
1515         rule->op_name = "rx";
1516         rule->op_param = argsp;
1517     } else  {
1518         /* Explicitly selected operator. */
1519         char *p = (char *)(argsp + 1);
1520         while((!isspace(*p))&&(*p != '\0')) p++;
1521         rule->op_name = apr_pstrmemdup(ruleset->mp, argsp + 1, p - (argsp + 1));
1522         while(isspace(*p)) p++; /* skip over the whitespace at the end*/
1523         rule->op_param = p; /* IMP1 So we always have a parameter even when it's empty? */
1524     }
1525
1526     /* Find the operator. */
1527     rule->op_metadata = msre_engine_op_resolve(ruleset->engine, rule->op_name);
1528     if (rule->op_metadata == NULL) {
1529         *error_msg = apr_psprintf(ruleset->mp,
1530             "Error creating rule: Failed to resolve operator: %s", rule->op_name);
1531         return NULL;
1532     }
1533
1534     /* Initialise & validate parameter */
1535     if (rule->op_metadata->param_init != NULL) {
1536         if (rule->op_metadata->param_init(rule, &my_error_msg) <= 0) {
1537             *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
1538             return NULL;
1539         }
1540     }
1541
1542     /* Parse actions */
1543     if (actions != NULL) {
1544         /* Create per-rule actionset */
1545         rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
1546         if (rule->actionset == NULL) {
1547             *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
1548             return NULL;
1549         }
1550     }
1551
1552     /* Add the unparsed rule */
1553     rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, targets, args, NULL);
1554
1555     return rule;
1556 }
1557
1558 #if defined(WITH_LUA)
1559 /**
1560  *
1561  */
1562 msre_rule *msre_rule_lua_create(msre_ruleset *ruleset,
1563     const char *fn, int line, const char *script_filename,
1564     const char *actions, char **error_msg)
1565 {
1566     msre_rule *rule;
1567     char *my_error_msg;
1568
1569     if (error_msg == NULL) return NULL;
1570     *error_msg = NULL;
1571
1572     rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
1573     if (rule == NULL) return NULL;
1574
1575     rule->type = RULE_TYPE_LUA;
1576     rule->ruleset = ruleset;
1577     rule->filename = apr_pstrdup(ruleset->mp, fn);
1578     rule->line_num = line;
1579
1580     /* Compile script. */
1581     *error_msg = lua_compile(&rule->script, script_filename, ruleset->mp);
1582     if (*error_msg != NULL) {
1583         return NULL;
1584     }
1585
1586     /* Parse actions */
1587     if (actions != NULL) {
1588         /* Create per-rule actionset */
1589         rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
1590         if (rule->actionset == NULL) {
1591             *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
1592             return NULL;
1593         }
1594     }
1595
1596     /* Add the unparsed rule */
1597     rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, script_filename, NULL);
1598
1599     return rule;
1600 }
1601 #endif
1602
1603 /**
1604  * Perform non-disruptive actions associated with the provided actionset.
1605  */
1606 static void msre_perform_nondisruptive_actions(modsec_rec *msr, msre_rule *rule,
1607     msre_actionset *actionset, apr_pool_t *mptmp)
1608 {
1609     const apr_array_header_t *tarr;
1610     const apr_table_entry_t *telts;
1611     int i;
1612
1613     tarr = apr_table_elts(actionset->actions);
1614     telts = (const apr_table_entry_t*)tarr->elts;
1615     for (i = 0; i < tarr->nelts; i++) {
1616         msre_action *action = (msre_action *)telts[i].val;
1617         if (action->metadata->type == ACTION_NON_DISRUPTIVE) {
1618             if (action->metadata->execute != NULL) {
1619                 action->metadata->execute(msr, mptmp, rule, action);
1620             }
1621         }
1622     }
1623 }
1624
1625 /**
1626  * Perform the disruptive actions associated with the given actionset.
1627  */
1628 static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule,
1629     msre_actionset *actionset, apr_pool_t *mptmp, const char *message)
1630 {
1631     const apr_array_header_t *tarr;
1632     const apr_table_entry_t *telts;
1633     int i;
1634
1635     /* Execute the disruptive actions. Do note that this does
1636      * not mean the request will be interrupted straight away. All
1637      * disruptive actions need to do here is update the information
1638      * that will be used to act later.
1639      */
1640     tarr = apr_table_elts(actionset->actions);
1641     telts = (const apr_table_entry_t*)tarr->elts;
1642     for (i = 0; i < tarr->nelts; i++) {
1643         msre_action *action = (msre_action *)telts[i].val;
1644         if (action->metadata->type == ACTION_DISRUPTIVE) {
1645             if (action->metadata->execute != NULL) {
1646                 action->metadata->execute(msr, mptmp, rule, action);
1647             }
1648         }
1649     }
1650
1651     /* If "noauditlog" was used do not mark the transaction relevant. */
1652     if (actionset->auditlog != 0) {
1653         msr->is_relevant++;
1654     }
1655
1656     /* We only do stuff when in ONLINE mode. In all other
1657      * cases we only emit warnings.
1658      */
1659     if ((msr->phase == PHASE_LOGGING)
1660         || (msr->txcfg->is_enabled == MODSEC_DETECTION_ONLY)
1661         || (msr->modsecurity->processing_mode == MODSEC_OFFLINE)
1662         || (actionset->intercept_action == ACTION_NONE))
1663     {
1664         int log_level;
1665
1666         /* If "nolog" was used log at a higher level to prevent an "alert". */
1667         if (actionset->log == 0) {
1668             log_level = 4;
1669
1670             /* But, if "auditlog" is enabled, then still add the message. */
1671             if (actionset->auditlog != 0) {
1672                 *(const char **)apr_array_push(msr->alerts) = msc_alert_message(msr, actionset, NULL, message);
1673             }
1674
1675         }
1676         else {
1677             log_level = 2;
1678         }
1679
1680         msc_alert(msr, log_level, actionset, "Warning.", message);
1681
1682         /* However, this will mark the txn relevant again if it is <= 3,
1683          * which will mess up noauditlog.  We need to compensate for this
1684          * so that we do not increment twice when auditlog is enabled and
1685          * prevent incrementing when auditlog is disabled.
1686          */
1687         if (log_level <= 3) {
1688             msr->is_relevant--;
1689         }
1690
1691         return;
1692     }
1693
1694     /* Signal to the engine we need to intercept this
1695      * transaction, and rememer the rule that caused it.
1696      */
1697     msr->was_intercepted = 1;
1698     msr->rule_was_intercepted = 1;
1699     msr->intercept_phase = msr->phase;
1700     msr->intercept_actionset = actionset;
1701     msr->intercept_message = message;
1702 }
1703
1704 /**
1705  * Invokes the rule operator against the given value.
1706  */
1707 static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr,
1708     msre_actionset *acting_actionset, apr_pool_t *mptmp)
1709 {
1710     apr_time_t time_before_op = 0;
1711     char *my_error_msg = NULL;
1712     const char *full_varname = NULL;
1713     int rc;
1714
1715     /* determine the full var name if not already resolved
1716      *
1717      * NOTE: this can happen if the var does not match but it is
1718      * being tested for non-existance as in:
1719      *   @REQUEST_HEADERS:Foo "@eq 0"
1720      *   @REQUEST_HEADERS:Foo "!@eq 1"
1721      */
1722     if ((var->param != NULL) && (var->name != NULL) && (strchr(var->name,':') == NULL)) {
1723         full_varname = apr_psprintf(mptmp, "%s%s:%s",
1724                                     (var->is_counting ? "&" : ""),
1725                                     var->name, var->param);
1726     }
1727     else if ((var->name != NULL) && var->is_counting && (*var->name != '&')) {
1728         full_varname = apr_pstrcat(mptmp, "&", var->name, NULL);
1729     }
1730     else {
1731         full_varname = var->name;
1732     }
1733
1734     if (msr->txcfg->debuglog_level >= 4) {
1735         msr_log(msr, 4, "Executing operator \"%s%s\" with param \"%s\" against %s.",
1736             (rule->op_negated ? "!" : ""), rule->op_name,
1737             log_escape(msr->mp, rule->op_param), full_varname);
1738     }
1739
1740     if (msr->txcfg->debuglog_level >= 9) {
1741         msr_log(msr, 9, "Target value: \"%s\"", log_escape_nq_ex(msr->mp, var->value,
1742             var->value_len));
1743     }
1744
1745     #if defined(PERFORMANCE_MEASUREMENT)
1746     time_before_op = apr_time_now();
1747     #else
1748     if (msr->txcfg->debuglog_level >= 4) {
1749         time_before_op = apr_time_now();
1750     }
1751     #endif
1752         
1753     rc = rule->op_metadata->execute(msr, rule, var, &my_error_msg);
1754
1755     #if defined(PERFORMANCE_MEASUREMENT)
1756     {
1757         /* Record performance but do not log anything. */
1758         apr_time_t t1 = apr_time_now();
1759         rule->op_time += (t1 - time_before_op);
1760     }
1761     #else
1762     if (msr->txcfg->debuglog_level >= 4) {
1763         apr_time_t t1 = apr_time_now();
1764         msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.", (t1 - time_before_op));
1765     }
1766     #endif
1767
1768     if (rc < 0) {
1769         msr_log(msr, 4, "Operator error: %s", my_error_msg);
1770         return -1;
1771     }
1772
1773     if (((rc == 0)&&(rule->op_negated == 0))||((rc == 1)&&(rule->op_negated == 1))) {
1774         /* No match, do nothing. */
1775         return RULE_NO_MATCH;
1776     }
1777     else {
1778         /* Match. */
1779         if (rc == 0) {
1780             char *op_param = log_escape(msr->mp, rule->op_param);
1781
1782             /* Truncate op parameter. */
1783             if (strlen(op_param) > 252) {
1784                 op_param = apr_psprintf(msr->mp, "%.252s ...", op_param);
1785             }
1786
1787             /* Operator did not match so we need to provide a message. */
1788             my_error_msg = apr_psprintf(msr->mp, "Match of \"%s %s\" against \"%s\" required.",
1789                 log_escape(msr->mp, rule->op_name), op_param,
1790                 log_escape(msr->mp, full_varname));
1791         }
1792
1793         /* Save the rules that match */
1794         *(const msre_rule **)apr_array_push(msr->matched_rules) = rule;
1795
1796         /* Save the last matched var data */
1797         msr->matched_var->name = apr_pstrdup(msr->mp, var->name);
1798         msr->matched_var->name_len = strlen(msr->matched_var->name);
1799         msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len);
1800         msr->matched_var->value_len = var->value_len;
1801
1802         /* Keep track of the highest severity matched so far */
1803         if ((acting_actionset->severity > 0) && (acting_actionset->severity < msr->highest_severity))
1804         {
1805             msr->highest_severity = acting_actionset->severity;
1806         }
1807
1808
1809         /* Perform non-disruptive actions. */
1810         msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp);
1811
1812         /* Perform disruptive actions, but only if
1813          * this rule is not part of a chain.
1814          */
1815         if (rule->actionset->is_chained == 0) {
1816             msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg);
1817         }
1818
1819         return RULE_MATCH;
1820     }
1821 }
1822
1823 /**
1824  * Executes rule against the given transaction.
1825  */
1826 static apr_status_t msre_rule_process_normal(msre_rule *rule, modsec_rec *msr) {
1827     const apr_array_header_t *arr = NULL;
1828     const apr_table_entry_t *te = NULL;
1829     msre_actionset *acting_actionset = NULL;
1830     msre_var **targets = NULL;
1831     apr_pool_t *mptmp = msr->msc_rule_mptmp;
1832     apr_table_t *tartab = NULL;
1833     apr_table_t *vartab = NULL;
1834     int i, rc = 0, match_count = 0;
1835     int invocations = 0;
1836     int multi_match = 0;
1837
1838     /* Choose the correct metadata/disruptive action actionset. */
1839     acting_actionset = rule->actionset;
1840     if (rule->chain_starter != NULL) {
1841         acting_actionset = rule->chain_starter->actionset;
1842     }
1843
1844     /* Configure recursive matching. */
1845     if (apr_table_get(rule->actionset->actions, "multiMatch") != NULL) {
1846         multi_match = 1;
1847     }
1848
1849     /* ENH: What is a good initial size? */
1850     tartab = apr_table_make(mptmp, 24);
1851     if (tartab == NULL) return -1;
1852     vartab = apr_table_make(mptmp, 24);
1853     if (vartab == NULL) return -1;
1854
1855     /* Expand variables to create a list of targets. */
1856
1857     targets = (msre_var **)rule->targets->elts;
1858     for (i = 0; i < rule->targets->nelts; i++) {
1859         int j, list_count;
1860
1861         apr_table_clear(vartab);
1862
1863         /* ENH Introduce a new variable hook that would allow the code
1864          *     behind the variable to return the size of the collection
1865          *     without having to generate the variables.
1866          */
1867
1868         /* Expand individual variables first. */
1869         list_count = targets[i]->metadata->generate(msr, targets[i], rule, vartab, mptmp);
1870
1871         if (targets[i]->is_counting) {
1872             /* Count how many there are and just add the score to the target list. */
1873             msre_var *newvar = (msre_var *)apr_pmemdup(mptmp, targets[i], sizeof(msre_var));
1874             newvar->value = apr_psprintf(mptmp, "%d", list_count);
1875             newvar->value_len = strlen(newvar->value);
1876             apr_table_addn(tartab, newvar->name, (void *)newvar);
1877         } else {
1878             /* And either add them or remove from the final target list. */
1879             arr = apr_table_elts(vartab);
1880             te = (apr_table_entry_t *)arr->elts;
1881             for(j = 0; j < arr->nelts; j++) {
1882                 if (targets[i]->is_negated == 0) {
1883                     apr_table_addn(tartab, te[j].key, te[j].val);
1884                 } else {
1885                     apr_table_unset(tartab, te[j].key);
1886                 }
1887             }
1888         }
1889     }
1890
1891     /* Log the target variable expansion */
1892     if (msr->txcfg->debuglog_level >= 4) {
1893         const char *expnames = NULL;
1894
1895         arr = apr_table_elts(tartab);
1896         if (arr->nelts > 1) {
1897             te = (apr_table_entry_t *)arr->elts;
1898             expnames = apr_pstrdup(mptmp, ((msre_var *)te[0].val)->name);
1899             for(i = 1; i < arr->nelts; i++) {
1900                 expnames = apr_psprintf(mptmp, "%s|%s", expnames, ((msre_var *)te[i].val)->name);
1901             }
1902             if (strcmp(rule->p1, expnames) != 0) {
1903                 msr_log(msr, 4, "Expanded \"%s\" to \"%s\".", rule->p1, expnames);
1904             }
1905         }
1906     }
1907
1908     /* Loop through targets on the final target list,
1909      * perform transformations as necessary, and invoke
1910      * the operator.
1911      */
1912
1913     arr = apr_table_elts(tartab);
1914     te = (apr_table_entry_t *)arr->elts;
1915     for (i = 0; i < arr->nelts; i++) {
1916         int changed;
1917         int usecache = 0;
1918         apr_table_t *cachetab = NULL;
1919         apr_time_t time_before_trans = 0;
1920         msre_var *var;
1921
1922         /* Take one target. */
1923         var = (msre_var *)te[i].val;
1924
1925         /* Is this var cacheable? */
1926         if (msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) {
1927             usecache = 1;
1928
1929             /* Counting vars are not cacheable due to them being created
1930              * in a local per-rule pool.
1931              */
1932             if (var->is_counting) {
1933                 if (msr->txcfg->debuglog_level >= 9) {
1934                     msr_log(msr, 9, "CACHE: Disabled - &%s is dynamic", var->name);
1935                 }
1936
1937                 usecache = 0;
1938             }
1939             /* Only cache if if the variable is available in this phase */
1940             else if (msr->phase < var->metadata->availability) {
1941                 if (msr->txcfg->debuglog_level >= 9) {
1942                     msr_log(msr, 9, "CACHE: Disabled - %s is not yet available in phase %d (requires phase %d or later)", var->name, msr->phase, var->metadata->availability);
1943                 }
1944
1945                 usecache = 0;
1946             }
1947             /* check the cache options */
1948             else if (var->value_len < msr->txcfg->cache_trans_min) {
1949                 if (msr->txcfg->debuglog_level >= 9) {
1950                     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);
1951                 }
1952
1953                 usecache = 0;
1954             }
1955             else if ((msr->txcfg->cache_trans_max != 0) && (var->value_len > msr->txcfg->cache_trans_max)) {
1956                 if (msr->txcfg->debuglog_level >= 9) {
1957                     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);
1958                 }
1959
1960                 usecache = 0;
1961             }
1962
1963             /* if cache is still enabled, check the VAR for cacheablity */
1964             if (usecache) {
1965                 if (var->metadata->is_cacheable == VAR_CACHE) {
1966                     if (msr->txcfg->debuglog_level >= 9) {
1967                         msr_log(msr, 9, "CACHE: Enabled");
1968                     }
1969
1970                     #ifdef CACHE_DEBUG
1971                     msr_log(msr, 9, "CACHE: Fetching cache entry from hash=%pp: %pp=%s", msr->tcache, var, var->name);
1972                     #endif
1973
1974                     /* Fetch cache table for this target */
1975                     cachetab = (apr_table_t *)apr_hash_get(msr->tcache, var->value, sizeof(var->value));
1976
1977                     /* Create an empty cache table if this is the first time */
1978                     #ifdef CACHE_DEBUG
1979                     if (cachetab) {
1980                         msr_log(msr, 9, "CACHE: Using cache table %pp", cachetab);
1981                     }
1982                     else
1983                     #else
1984                     if (cachetab == NULL)
1985                     #endif
1986                     {
1987                         /* NOTE: We use the pointer to the var value as a hash
1988                          *       key as it is unique. This pointer *must*
1989                          *       remain valid through the entire phase. If
1990                          *       it does not, then we will not receive a cache
1991                          *       hit and just wasted RAM. So, it is important
1992                          *       that any such vars be marked as VAR_DONT_CACHE.
1993                          *
1994                          * ENH: Only use pointer for non-scalar vars
1995                          */
1996                         cachetab = apr_table_make(msr->mp, 3);
1997                         apr_hash_set(msr->tcache, var->value, sizeof(var->value), cachetab);
1998
1999                         #ifdef CACHE_DEBUG
2000                         msr_log(msr, 9, "CACHE: Created a new cache table %pp for %pp", cachetab, var->value);
2001                         #endif
2002                     }
2003
2004                 }
2005                 else {
2006                     usecache = 0;
2007
2008                     if (msr->txcfg->debuglog_level >= 9) {
2009                         msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name);
2010                     }
2011                 }
2012             }
2013         }
2014
2015         #if defined(PERFORMANCE_MEASUREMENT)
2016         time_before_trans = apr_time_now();
2017         #else
2018         if (msr->txcfg->debuglog_level >= 4) {
2019             time_before_trans = apr_time_now();
2020         }
2021         #endif
2022
2023         /* Transform target. */
2024         {
2025             const apr_array_header_t *tarr;
2026             const apr_table_entry_t *telts;
2027             const char *tfnspath = NULL;
2028             char *tfnskey = NULL;
2029             int tfnscount = 0;
2030             int last_cached_tfn = 0;
2031             msre_cache_rec *crec = NULL;
2032             msre_cache_rec *last_crec = NULL;
2033             int k;
2034             msre_action *action;
2035             msre_tfn_metadata *metadata;
2036             apr_table_t *normtab;
2037             const char *lastvarval = NULL;
2038             apr_size_t lastvarlen = 0;
2039
2040             changed = 0;
2041             normtab = apr_table_make(mptmp, 10);
2042             if (normtab == NULL) return -1;
2043             tarr = apr_table_elts(rule->actionset->actions);
2044             telts = (const apr_table_entry_t*)tarr->elts;
2045
2046             /* Build the final list of transformation functions. */
2047             for (k = 0; k < tarr->nelts; k++) {
2048                 action = (msre_action *)telts[k].val;
2049
2050                 if (strcmp(telts[k].key, "t") == 0) {
2051                     if (strcmp(action->param, "none") == 0) {
2052                         apr_table_clear(normtab);
2053                         tfnspath = NULL;
2054                         tfnskey = NULL;
2055                         tfnscount = 0;
2056                         last_crec = NULL;
2057                         last_cached_tfn = 0;
2058                         continue;
2059                     }
2060
2061                     if (action->param_plusminus == NEGATIVE_VALUE) {
2062                         apr_table_unset(normtab, action->param);
2063                     }
2064                     else {
2065                         tfnscount++;
2066
2067                         apr_table_addn(normtab, action->param, (void *)action);
2068
2069                         /* Check the cache, saving the 'most complete' as a
2070                          * starting point
2071                          */
2072                         if (usecache) {
2073                             tfnspath = apr_psprintf(mptmp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param);
2074                             tfnskey = apr_psprintf(mptmp, "%x;%s", tfnscount, tfnspath);
2075                             crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey);
2076
2077                             #ifdef CACHE_DEBUG
2078                             msr_log(msr, 9, "CACHE: %s %s cached=%d", var->name, tfnskey, (crec ? 1 : 0));
2079                             #endif
2080
2081                             if (crec != NULL) {
2082                                 last_crec = crec;
2083                                 last_cached_tfn = tfnscount;
2084                             }
2085                         }
2086                     }
2087                 }
2088             }
2089
2090             /* If the last cached tfn is the last in the list
2091              * then we can stop here and just execute the action immediatly
2092              */
2093             if (usecache && !multi_match &&
2094                 (crec != NULL) && (crec == last_crec))
2095             {
2096                 crec->hits++;
2097
2098                 if (crec->changed) {
2099                     var->value = apr_pmemdup(mptmp, crec->val, crec->val_len);
2100                     var->value_len = crec->val_len;
2101                 }
2102
2103                 if (msr->txcfg->debuglog_level >= 9) {
2104                     msr_log(msr, 9, "T (%d) %s: \"%s\" [fully cached hits=%d]", crec->changed, crec->path,
2105                         log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits);
2106                 }
2107
2108                 #if defined(PERFORMANCE_MEASUREMENT) 
2109                 {
2110                     apr_time_t t1 = apr_time_now();
2111                     rule->trans_time += (t1 - time_before_trans);
2112                 }
2113                 #else
2114                 if (msr->txcfg->debuglog_level >= 4) {
2115                     apr_time_t t1 = apr_time_now();
2116
2117                     msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2118                         (t1 - time_before_trans));
2119                 }
2120                 #endif
2121
2122                 rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2123
2124                 if (rc < 0) {
2125                     return -1;
2126                 }
2127
2128                 if (rc == RULE_MATCH) {
2129                     match_count++;
2130
2131                     /* Return straight away if the transaction
2132                      * was intercepted - no need to process the remaining
2133                      * targets.
2134                      */
2135                     if (msr->rule_was_intercepted) {
2136                         return RULE_MATCH;
2137                     }
2138                 }
2139
2140                 continue; /* next target */
2141             }
2142
2143
2144             /* Perform transformations. */
2145
2146             tarr = apr_table_elts(normtab);
2147
2148             /* Execute transformations in a loop. */
2149
2150             /* Start after the last known cached transformation if we can */
2151             if (!multi_match && (last_crec != NULL)) {
2152                 k = last_cached_tfn;
2153                 tfnspath = last_crec->path;
2154                 last_crec->hits++;
2155
2156                 if ((changed = last_crec->changed) > 0) {
2157                     var->value = last_crec->val;
2158                     var->value_len = last_crec->val_len;
2159                 }
2160
2161                 if (msr->txcfg->debuglog_level >= 9) {
2162                     msr_log(msr, 9, "T (%d) %s: \"%s\" [partially cached hits=%d]", last_crec->changed,
2163                         tfnspath, log_escape_nq_ex(mptmp, var->value, var->value_len), last_crec->hits);
2164                 }
2165             }
2166             else {
2167                 tfnspath = NULL;
2168                 k = 0;
2169             }
2170
2171             /* Make a copy of the value so that we can change it in-place. */
2172             if (tarr->nelts) {
2173                 var->value = apr_pstrmemdup(mptmp, var->value, var->value_len);
2174                 /* var->value_len remains the same */
2175             }
2176
2177             telts = (const apr_table_entry_t*)tarr->elts;
2178             for (; k < tarr->nelts; k++) {
2179                 char *rval = NULL;
2180                 long int rval_length = -1;
2181                 int tfnchanged = 0;
2182
2183                 /* In multi-match mode we execute the operator
2184                  * once at the beginning and then once every
2185                  * time the variable is changed by the transformation
2186                  * function.
2187                  */
2188                 if (multi_match && (k == 0 || tfnchanged)) {
2189                     invocations++;
2190
2191                     #if defined(PERFORMANCE_MEASUREMENT)
2192                     {
2193                         apr_time_t t1 = apr_time_now();
2194                         rule->trans_time += (t1 - time_before_trans);
2195                     }
2196                     #else
2197                     if (msr->txcfg->debuglog_level >= 4) {
2198                         apr_time_t t1 = apr_time_now();
2199
2200                         msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2201                             (t1 - time_before_trans));
2202                     }
2203                     #endif
2204
2205                     rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2206
2207                     if (rc < 0) {
2208                         return -1;
2209                     }
2210
2211                     if (rc == RULE_MATCH) {
2212                         match_count++;
2213
2214                         /* Return straight away if the transaction
2215                         * was intercepted - no need to process the remaining
2216                         * targets.
2217                         */
2218                         if (msr->rule_was_intercepted) {
2219                             return RULE_MATCH;
2220                         }
2221                     }
2222                 }
2223
2224                 /* Perform one transformation. */
2225                 action = (msre_action *)telts[k].val;
2226                 metadata = (msre_tfn_metadata *)action->param_data;
2227                 tfnchanged = metadata->execute(mptmp,
2228                     (unsigned char *)var->value, var->value_len,
2229                     &rval, &rval_length);
2230
2231                 if (tfnchanged < 0) {
2232                     return -1;
2233                 }
2234
2235                 if (tfnchanged) {
2236                     changed++;
2237                 }
2238
2239                 /* Use the new values */
2240                 var->value = rval;
2241                 var->value_len = rval_length;
2242
2243                 /* Cache the transformation */
2244                 if (usecache) {
2245                     int tfnsnum = k + 1;
2246
2247                     /* Generate the cache key */
2248                     tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath ? tfnspath : ""),
2249                         (tfnspath ? "," : ""), action->param);
2250                     tfnskey = apr_psprintf(msr->mp, "%x;%s", tfnsnum, tfnspath);
2251
2252                     if ((msr->txcfg->cache_trans_maxitems != 0) &&
2253                         (msr->tcache_items >= msr->txcfg->cache_trans_maxitems))
2254                     {
2255                         /* Warn only once if we attempt to go over the cache limit. */
2256                         if (msr->tcache_items == msr->txcfg->cache_trans_maxitems) {
2257                             msr->tcache_items++;
2258                             msr_log(msr, 4, "CACHE: Disabled - phase=%d"
2259                                             " maxitems=%" APR_SIZE_T_FMT
2260                                             " limit reached.",
2261                                             msr->phase,
2262                                             msr->txcfg->cache_trans_maxitems);
2263                         }
2264                     }
2265                     else if (msr->txcfg->cache_trans_incremental ||
2266                         (tfnsnum == tarr->nelts))
2267                     {
2268                         /* ENH1: Add flag to vars to tell which ones can change across phases store the rest in a global cache */
2269                         crec = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec));
2270                         if (crec == NULL) return -1;
2271
2272                         crec->hits = 0;
2273                         crec->changed = changed;
2274                         crec->num = k + 1;
2275                         crec->path = tfnspath;
2276
2277                         /* We want to cache a copy if it changed otherwise
2278                          * we just want to use a pointer to the last changed value.
2279                          */
2280                         crec->val = (!lastvarval || tfnchanged) ? apr_pmemdup(msr->mp, var->value, var->value_len) : lastvarval;
2281                         crec->val_len = changed ? ((!lastvarval || tfnchanged) ? var->value_len : lastvarlen) : 0;
2282
2283                         /* Keep track of the last changed var value */
2284                         if (tfnchanged) {
2285                             lastvarval = crec->val;
2286                             lastvarlen = crec->val_len;
2287                         }
2288
2289                         #ifdef CACHE_DEBUG
2290                         if (changed) {
2291                             msr_log(msr, 9, "CACHE: Caching %s=\"%s\" (%pp)",
2292                                             tfnskey,
2293                                             log_escape_nq_ex(mptmp,
2294                                                              crec->val,
2295                                                              crec->val_len),
2296                                             var);
2297                         }
2298                         else {
2299                             msr_log(msr, 9, "CACHE: Caching %s=<no change> (%pp)",
2300                                             tfnskey,
2301                                             var);
2302                         }
2303                         #endif
2304
2305                         msr->tcache_items++;
2306
2307                         apr_table_setn(cachetab, tfnskey, (void *)crec);
2308                     }
2309                 }
2310
2311                 if (msr->txcfg->debuglog_level >= 9) {
2312                     msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, metadata->name,
2313                         log_escape_nq_ex(mptmp, var->value, var->value_len));
2314                 }
2315             }
2316         }
2317
2318         /* Execute operator if multi-matching is not enabled,
2319          * or if it is and we need to process the result of the
2320          * last transformation.
2321          */
2322         if (!multi_match || changed) {
2323             invocations++;
2324
2325             #if defined(PERFORMANCE_MEASUREMENT)
2326             {
2327                 apr_time_t t1 = apr_time_now();
2328                 rule->trans_time += (t1 - time_before_trans);
2329             }
2330             #else
2331             if (msr->txcfg->debuglog_level >= 4) {
2332                 apr_time_t t1 = apr_time_now();
2333
2334                 msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2335                     (t1 - time_before_trans));
2336             }
2337             #endif
2338
2339             rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2340
2341             if (rc < 0) {
2342                 return -1;
2343             }
2344
2345             if (rc == RULE_MATCH) {
2346                 match_count++;
2347
2348                 /* Return straight away if the transaction
2349                  * was intercepted - no need to process the remaining
2350                  * targets.
2351                  */
2352                 if (msr->rule_was_intercepted) {
2353                     return RULE_MATCH;
2354                 }
2355             }
2356         }
2357     }
2358
2359
2360     return (match_count ? RULE_MATCH : RULE_NO_MATCH);
2361 }
2362
2363 #if defined(WITH_LUA)
2364 /**
2365  *
2366  */
2367 static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
2368     msre_actionset *acting_actionset = NULL;
2369     char *my_error_msg = NULL;
2370     int rc;
2371
2372     /* Choose the correct metadata/disruptive action actionset. */
2373     acting_actionset = rule->actionset;
2374     if (rule->chain_starter != NULL) {
2375         acting_actionset = rule->chain_starter->actionset;
2376     }
2377
2378     rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg);
2379     if (rc < 0) {
2380         msr_log(msr, 1, "%s", my_error_msg);
2381         return -1;
2382     }
2383
2384     /* A non-NULL error message means the rule matched. */
2385     if (my_error_msg != NULL) {
2386         /* Perform non-disruptive actions. */
2387         msre_perform_nondisruptive_actions(msr, rule, rule->actionset, msr->msc_rule_mptmp);
2388
2389         /* Perform disruptive actions, but only if
2390          * this rule is not part of a chain.
2391          */
2392         if (rule->actionset->is_chained == 0) {
2393             msre_perform_disruptive_actions(msr, rule, acting_actionset, msr->msc_rule_mptmp, my_error_msg);
2394         }
2395     }
2396
2397     return rc;
2398 }
2399 #endif
2400
2401 /**
2402  *
2403  */
2404 apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) {
2405     /* Use a fresh memory sub-pool for processing each rule */
2406     if (msr->msc_rule_mptmp == NULL) {
2407         if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) {
2408             return -1;
2409         }
2410     } else {
2411         apr_pool_clear(msr->msc_rule_mptmp);
2412     }
2413
2414     #if defined(WITH_LUA)
2415     if (rule->type == RULE_TYPE_LUA) {
2416         return msre_rule_process_lua(rule, msr);
2417     }
2418     #endif
2419
2420     return msre_rule_process_normal(rule, msr);
2421 }
2422
2423 /**
2424  * Checks whether the given rule ID is in the given range.
2425  */
2426 int rule_id_in_range(int ruleid, const char *range) {
2427     char *p = NULL, *saveptr = NULL;
2428     char *data = NULL;
2429
2430     if (range == NULL) return 0;
2431     data = strdup(range);
2432     if (data == NULL) return 0;
2433
2434     p = apr_strtok(data, ",", &saveptr);
2435     while(p != NULL) {
2436         char *s = strstr(p, "-");
2437         if (s == NULL) {
2438             if (ruleid == atoi(p)) {
2439                 free(data);
2440                 return 1;
2441             }
2442         } else {
2443             int start = atoi(p);
2444             int end = atoi(s + 1);
2445             if ((ruleid >= start)&&(ruleid <= end)) {
2446                 free(data);
2447                 return 1;
2448             }
2449         }
2450         p = apr_strtok(NULL, ",", &saveptr);
2451     }
2452
2453     free(data);
2454
2455     return 0;
2456 }