1 /*
2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
4 *
5 * You may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * If any of the files related to licensing are missing or if you have any
11 * other questions related to licensing please contact Trustwave Holdings, Inc.
12 * directly using the email address security@modsecurity.org.
13 */
14 
15 #include <ctype.h>
16 
17 #include "re.h"
18 
19 #if defined(WITH_LUA)
20 #include "msc_lua.h"
21 #endif
22 
23 static const char *const severities[] = {
24     "EMERGENCY",
25     "ALERT",
26     "CRITICAL",
27     "ERROR",
28     "WARNING",
29     "NOTICE",
30     "INFO",
31     "DEBUG",
32     NULL,
33 };
34 
35 static int fetch_target_exception(msre_rule *rule, modsec_rec *msr, msre_var *var, const char *exceptions);
36 static apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
37     apr_array_header_t *arr, char **error_msg);
38 static char *msre_generate_target_string(apr_pool_t *pool, msre_rule *rule);
39 static msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
40     modsec_rec *msr, char **error_msg);
41 static msre_action *msre_create_action(msre_engine *engine, apr_pool_t *mp, const char *name,
42     const char *param, char **error_msg);
43 static apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr);
44 
45 /* -- Actions, variables, functions and operator functions ----------------- */
46 
47 /**
48  * \brief Remove rule targets to be processed
49  *
50  * \param rule Pointer to the rule
51  * \param msr ModSecurity transaction resource
52  * \param var Pointer to target structure.
53  * \param targets Exception list.
54  */
fetch_target_exception(msre_rule * rule,modsec_rec * msr,msre_var * var,const char * exceptions)55 static int fetch_target_exception(msre_rule *rule, modsec_rec *msr, msre_var *var, const char *exceptions)   {
56     const char *targets = NULL;
57     char *savedptr = NULL, *target = NULL;
58     char *c = NULL, *name = NULL, *value = NULL;
59     char *variable = NULL, *myvar = NULL;
60     char *myvalue = NULL, *myname = NULL;
61     int match = 0;
62 
63     if(msr == NULL)
64         return 0;
65 
66     if(var == NULL)
67         return 0;
68 
69     if(rule == NULL)
70         return 0;
71 
72     if(rule->actionset == NULL)
73         return 0;
74 
75     if(rule->actionset->id !=NULL)    {
76 
77         myvar = apr_pstrdup(msr->mp, var->name);
78 
79         c = strchr(myvar,':');
80 
81         if(c != NULL) {
82             myname = apr_strtok(myvar,":",&myvalue);
83         } else {
84             myname = myvar;
85         }
86 
87         match = 0;
88 
89         targets = apr_pstrdup(msr->mp, exceptions);
90 
91         if(targets != NULL) {
92             if (msr->txcfg->debuglog_level >= 9) {
93                 msr_log(msr, 9, "fetch_target_exception: Found exception target list [%s] for rule id %s", targets, rule->actionset->id);
94             }
95             target = apr_strtok((char *)targets, ",", &savedptr);
96 
97             while(target != NULL)   {
98 
99                 variable = apr_pstrdup(msr->mp, target);
100 
101                 c = strchr(variable,':');
102 
103                 if(c != NULL) {
104                     name = apr_strtok(variable,":",&value);
105                 } else {
106                     name = variable;
107                     value = NULL;
108                 }
109 
110                 if((strlen(myname) == strlen(name)) &&
111                         (strncasecmp(myname, name,strlen(myname)) == 0))   {
112 
113                     if(value != NULL && myvalue != NULL)  {
114                         if((strlen(myvalue) == strlen(value)) &&
115                                 strncasecmp(myvalue,value,strlen(myvalue)) == 0) {
116                             if (msr->txcfg->debuglog_level >= 9) {
117                                 msr_log(msr, 9, "fetch_target_exception: Target %s will not be processed.", target);
118                             }
119                             match = 1;
120                         }
121                     } else if (value == NULL && myvalue == NULL)    {
122                         if (msr->txcfg->debuglog_level >= 9) {
123                             msr_log(msr, 9, "fetch_target_exception: Target %s will not be processed.", target);
124                         }
125                         match = 1;
126                     } else if (value == NULL && myvalue != NULL)   {
127                         if (msr->txcfg->debuglog_level >= 9) {
128                             msr_log(msr, 9, "fetch_target_exception: Target %s will not be processed.", target);
129                         }
130                         match = 1;
131                     }
132                 }
133 
134                 target = apr_strtok(NULL, ",", &savedptr);
135             }
136         } else  {
137             if (msr->txcfg->debuglog_level >= 9) {
138                 msr_log(msr, 9, "fetch_target_exception: No exception target found for rule id %s.", rule->actionset->id);
139 
140             }
141         }
142 
143     }
144 
145     if(match == 1)
146         return 1;
147 
148     return 0;
149 }
150 
151 /**
152  * \brief Update target for all matching rules in set, in any phase
153  *
154  * \param msr ModSecurity transaction resource
155  * \param ruleset Pointer to set of rules to modify
156  * \param re Pointer to exception object describing which rules to modify
157  * \param p2 Pointer to configuration option TARGET
158  * \param p3 Pointer to configuration option REPLACED_TARGET
159  */
msre_ruleset_rule_update_target_matching_exception(modsec_rec * msr,msre_ruleset * ruleset,rule_exception * re,const char * p2,const char * p3)160 char *msre_ruleset_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re, const char *p2, const char *p3) {
161     char *err;
162 
163     if(ruleset == NULL)
164         return NULL;
165 
166     if(p2 == NULL)  {
167         return apr_psprintf(ruleset->mp, "Trying to update without a target");
168     }
169 
170     if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_request_headers, p2, p3)))
171         return err;
172     if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_request_body, p2, p3)))
173         return err;
174     if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_response_headers, p2, p3)))
175         return err;
176     if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_response_body, p2, p3)))
177         return err;
178     if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_logging, p2, p3)))
179         return err;
180 
181     /* Everything worked! */
182     return NULL;
183 }
184 
185 
186 /**
187  * \brief Update target for all matching rules in set for a specific phase
188  *
189  * \param msr ModSecurity transaction resource
190  * \param ruleset Pointer to set of rules to modify
191  * \param re Pointer to exception object describing which rules to modify
192  * \param phase_arr Pointer to phase that should be edited
193  * \param p2 Pointer to configuration option TARGET
194  * \param p3 Pointer to configuration option REPLACED_TARGET
195  *
196  * \todo Figure out error checking
197  */
msre_ruleset_phase_rule_update_target_matching_exception(modsec_rec * msr,msre_ruleset * ruleset,rule_exception * re,apr_array_header_t * phase_arr,const char * p2,const char * p3)198 char *msre_ruleset_phase_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re,
199         apr_array_header_t *phase_arr, const char *p2,
200         const char *p3)
201 {
202     msre_rule **rules;
203     int i, j, mode;
204     char *err;
205 
206     j = 0;
207     mode = 0;
208     rules = (msre_rule **)phase_arr->elts;
209     for (i = 0; i < phase_arr->nelts; i++) {
210         msre_rule *rule = (msre_rule *)rules[i];
211 
212         if (mode == 0) { /* Looking for next rule. */
213             if (msre_ruleset_rule_matches_exception(rule, re)) {
214 
215                 err = update_rule_target_ex(NULL, ruleset, rule, p2, p3);
216                 if (err) return err;
217                 if (rule->actionset->is_chained) mode = 2; /* Match all rules in this chain. */
218             } else {
219                 if (rule->actionset->is_chained) mode = 1; /* Skip all rules in this chain. */
220             }
221         } else { /* Handling rule that is part of a chain. */
222             if (mode == 2) { /* We want to change the rule. */
223                 err = update_rule_target_ex(msr, ruleset, rule, p2, p3);
224                 if (err) return err;
225             }
226 
227             if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
228         }
229     }
230 
231     return NULL;
232 }
233 
234 
update_rule_target_ex(modsec_rec * msr,msre_ruleset * ruleset,msre_rule * rule,const char * p2,const char * p3)235 char *update_rule_target_ex(modsec_rec *msr, msre_ruleset *ruleset, msre_rule *rule, const char *p2,
236         const char *p3)   {
237 
238     msre_var **targets = NULL;
239     const char *current_targets = NULL;
240     char *my_error_msg = NULL, *target = NULL;
241     char *p = NULL, *savedptr = NULL;
242     unsigned int is_negated = 0, is_counting = 0;
243     int name_len = 0, value_len = 0;
244     char *name = NULL, *value = NULL;
245     char *opt = NULL, *param = NULL;
246     char *target_list = NULL, *replace = NULL;
247     int i, rc, match = 0, var_appended = 0;
248 
249     if(rule != NULL)    {
250 
251         target_list = strdup(p2);
252         if(target_list == NULL)
253             return apr_psprintf(ruleset->mp, "Error to update target - memory allocation");;
254 
255         if(p3 != NULL)  {
256             replace = strdup(p3);
257             if(replace == NULL) {
258                 free(target_list);
259                 target_list = NULL;
260                 return apr_psprintf(ruleset->mp, "Error to update target - memory allocation");;
261             }
262         }
263 
264         if(replace != NULL) {
265 
266             opt = strchr(replace,'!');
267 
268             if(opt != NULL)  {
269                 *opt = '\0';
270                 opt++;
271                 param = opt;
272                 is_negated = 1;
273             } else if ((opt = strchr(replace,'&')) != NULL)  {
274                 *opt = '\0';
275                 opt++;
276                 param = opt;
277                 is_counting = 1;
278             } else  {
279                 param = replace;
280             }
281 
282             opt = strchr(param,':');
283 
284             if(opt != NULL) {
285                 name = apr_strtok(param,":",&value);
286             } else {
287                 name = param;
288             }
289 
290             if(apr_table_get(ruleset->engine->variables, name) == NULL)   {
291                 if(target_list != NULL)
292                     free(target_list);
293                 if(replace != NULL)
294                     free(replace);
295                 if(msr) {
296                     msr_log(msr, 9, "Error to update target - [%s] is not valid target", name);
297                 }
298                 return apr_psprintf(ruleset->mp, "Error to update target - [%s] is not valid target", name);
299             }
300 
301             name_len = strlen(name);
302 
303             if(value != NULL)
304                 value_len = strlen(value);
305 
306             if(msr) {
307                 msr_log(msr, 9, "Trying to replace by variable name [%s] value [%s]", name, value);
308             }
309 #if !defined(MSC_TEST)
310             else {
311                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Trying to replace by variable name [%s] value [%s]", name, value);
312             }
313 #endif
314 
315             targets = (msre_var **)rule->targets->elts;
316             // TODO need a good way to remove the element from array, maybe change array by tables or rings
317             for (i = 0; i < rule->targets->nelts; i++) {
318                 if((strlen(targets[i]->name) == strlen(name)) &&
319                         (strncasecmp(targets[i]->name,name,strlen(targets[i]->name)) == 0) &&
320                         (targets[i]->is_negated == is_negated) &&
321                         (targets[i]->is_counting == is_counting))    {
322 
323                     if(value != NULL && targets[i]->param != NULL)  {
324                         if((strlen(targets[i]->param) == strlen(value)) &&
325                                 strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) {
326                             memset(targets[i]->name,0,strlen(targets[i]->name));
327                             memset(targets[i]->param,0,strlen(targets[i]->param));
328                             targets[i]->is_counting = 0;
329                             targets[i]->is_negated = 1;
330                             match = 1;
331                         }
332                     } else if (value == NULL && targets[i]->param == NULL){
333                         memset(targets[i]->name,0,strlen(targets[i]->name));
334                         targets[i]->is_counting = 0;
335                         targets[i]->is_negated = 1;
336                         match = 1;
337                     } else
338                         continue;
339 
340                 }
341             }
342         }
343 
344         p = apr_strtok(target_list, ",", &savedptr);
345 
346         while(p != NULL) {
347             if(replace != NULL) {
348                 if(match == 1)  {
349                     rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg);
350                     if (rc < 0) {
351                         if(msr) {
352                             msr_log(msr, 9, "Error parsing rule targets to replace variable");
353                         }
354 #if !defined(MSC_TEST)
355                         else {
356                             ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Error parsing rule targets to replace variable");
357                         }
358 #endif
359                         goto end;
360                     }
361                     if(msr) {
362                         msr_log(msr, 9, "Successfully replaced variable");
363                     }
364 #if !defined(MSC_TEST)
365                     else {
366                         ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Successfully replaced variable");
367                     }
368 #endif
369                 var_appended = 1;
370 
371                 } else  {
372                     if(msr) {
373                         msr_log(msr, 9, "Cannot find variable to replace");
374                     }
375 #if !defined(MSC_TEST)
376                     else {
377                         ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Cannot find varibale to replace");
378                     }
379 #endif
380                     goto end;
381                 }
382             } else {
383 
384                 target = strdup(p);
385                 if(target == NULL)
386                     return NULL;
387 
388                 is_negated = is_counting = 0;
389                 param = name = value = NULL;
390 
391                 opt = strchr(target,'!');
392 
393                 if(opt != NULL)  {
394                     *opt = '\0';
395                     opt++;
396                     param = opt;
397                     is_negated = 1;
398                 } else if ((opt = strchr(target,'&')) != NULL)  {
399                     *opt = '\0';
400                     opt++;
401                     param = opt;
402                     is_counting = 1;
403                 } else  {
404                     param = target;
405                 }
406 
407                 opt = strchr(param,':');
408 
409                 if(opt != NULL) {
410                     name = apr_strtok(param,":",&value);
411                 } else {
412                     name = param;
413                 }
414 
415                 if(apr_table_get(ruleset->engine->variables, name) == NULL)   {
416                     if(target_list != NULL)
417                         free(target_list);
418                     if(replace != NULL)
419                         free(replace);
420                     if(msr) {
421                         msr_log(msr, 9, "Error to update target - [%s] is not valid target", name);
422                     }
423                     return apr_psprintf(ruleset->mp, "Error to update target - [%s] is not valid target", name);
424                 }
425 
426                 name_len = strlen(name);
427 
428                 if(value != NULL)
429                     value_len = strlen(value);
430 
431                 if(msr) {
432                     msr_log(msr, 9, "Trying to append variable name [%s] value [%s]", name, value);
433                 }
434 #if !defined(MSC_TEST)
435                 else {
436                     ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Trying to append variable name [%s] value [%s]", name, value);
437                 }
438 #endif
439                 match = 0;
440 
441                 targets = (msre_var **)rule->targets->elts;
442                 for (i = 0; i < rule->targets->nelts; i++) {
443                     if((strlen(targets[i]->name) == strlen(name)) &&
444                             (strncasecmp(targets[i]->name,name,strlen(targets[i]->name)) == 0) &&
445                             (targets[i]->is_negated == is_negated) &&
446                             (targets[i]->is_counting == is_counting))    {
447 
448                         if(value != NULL && targets[i]->param != NULL)  {
449                             if((strlen(targets[i]->param) == strlen(value)) &&
450                                     strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) {
451                                 match = 1;
452                             }
453                         } else if (value == NULL && targets[i]->param == NULL){
454                             match = 1;
455                         } else
456                             continue;
457 
458                     }
459                 }
460 
461                 if(target != NULL)  {
462                     free(target);
463                     target = NULL;
464                 }
465 
466                 if(match == 0 ) {
467                     rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg);
468                     if (rc < 0) {
469                         if(msr) {
470                             msr_log(msr, 9, "Error parsing rule targets to append variable");
471                         }
472 #if !defined(MSC_TEST)
473                         else {
474                             ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Error parsing rule targets to append variable");
475                         }
476 #endif
477                         goto end;
478                     }
479                     var_appended = 1;
480                 } else {
481                     if(msr) {
482                         msr_log(msr, 9, "Skipping variable, already appended");
483                     }
484 #if !defined(MSC_TEST)
485                     else {
486                         ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Skipping variable, already appended");
487                     }
488 #endif
489                 }
490             }
491 
492             p = apr_strtok(NULL,",",&savedptr);
493         }
494 
495         if(var_appended == 1)  {
496             current_targets = msre_generate_target_string(ruleset->mp, rule);
497             rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, current_targets, NULL, NULL);
498             rule->p1 = apr_pstrdup(ruleset->mp, current_targets);
499             if(msr) {
500                 msr_log(msr, 9, "Successfully appended variable");
501             }
502 #if !defined(MSC_TEST)
503             else {
504                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Successfully appended variable");
505             }
506 #endif
507         }
508     }
509 
510 end:
511     if(target_list != NULL) {
512         free(target_list);
513         target_list = NULL;
514     }
515     if(replace != NULL) {
516         free(replace);
517         replace = NULL;
518     }
519     if(target != NULL)  {
520         free(target);
521         target = NULL;
522     }
523     return NULL;
524 }
525 
msre_ruleset_rule_matches_exception(msre_rule * rule,rule_exception * re)526 int msre_ruleset_rule_matches_exception(msre_rule *rule, rule_exception *re)   {
527     int match = 0;
528 
529     /* Only remove non-placeholder rules */
530     if (rule->placeholder == RULE_PH_NONE) {
531         switch(re->type) {
532             case RULE_EXCEPTION_REMOVE_ID :
533                 if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
534                     int ruleid = atoi(rule->actionset->id);
535 
536                     if (rule_id_in_range(ruleid, re->param)) {
537                         match = 1;
538                     }
539                 }
540 
541                 break;
542             case RULE_EXCEPTION_REMOVE_MSG :
543                 if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
544                     char *my_error_msg = NULL;
545 
546                     int rc = msc_regexec(re->param_data,
547                             rule->actionset->msg, strlen(rule->actionset->msg),
548                             &my_error_msg);
549                     if (rc >= 0) {
550                         match = 1;
551                     }
552                 }
553 
554                 break;
555             case RULE_EXCEPTION_REMOVE_TAG :
556                 if ((rule->actionset != NULL)&&(apr_is_empty_table(rule->actionset->actions) == 0)) {
557                     char *my_error_msg = NULL;
558                     const apr_array_header_t *tarr = NULL;
559                     const apr_table_entry_t *telts = NULL;
560                     int act;
561 
562                     tarr = apr_table_elts(rule->actionset->actions);
563                     telts = (const apr_table_entry_t*)tarr->elts;
564 
565                     for (act = 0; act < tarr->nelts; act++) {
566                         msre_action *action = (msre_action *)telts[act].val;
567                         if((action != NULL) && (action->metadata != NULL) && (strcmp("tag", action->metadata->name) == 0))  {
568 
569                             int rc = msc_regexec(re->param_data,
570                                     action->param, strlen(action->param),
571                                     &my_error_msg);
572                             if (rc >= 0)    {
573                                 match = 1;
574                             }
575                         }
576                     }
577                 }
578                 break;
579         }
580     }
581     return match;
582 }
583 
584 
585 
586 /**
587  * Remove actions with the same cardinality group from the actionset.
588  */
msre_actionset_cardinality_fixup(msre_actionset * actionset,msre_action * action)589 static void msre_actionset_cardinality_fixup(msre_actionset *actionset, msre_action *action) {
590     const apr_array_header_t *tarr = NULL;
591     const apr_table_entry_t *telts = NULL;
592     int i;
593 
594     if ((actionset == NULL) || (action == NULL)) return;
595 
596     tarr = apr_table_elts(actionset->actions);
597     telts = (const apr_table_entry_t*)tarr->elts;
598 
599     for (i = 0; i < tarr->nelts; i++) {
600         msre_action *target = (msre_action *)telts[i].val;
601         if (target->metadata->cardinality_group == action->metadata->cardinality_group) {
602 
603             apr_table_unset(actionset->actions, target->metadata->name);
604         }
605     }
606 }
607 
msre_generate_target_string(apr_pool_t * pool,msre_rule * rule)608 static char *msre_generate_target_string(apr_pool_t *pool, msre_rule *rule)  {
609 
610     char *target_str = NULL;
611     msre_var **targets = NULL;
612     int i = 0;
613 
614     targets = (msre_var **)rule->targets->elts;
615 
616     for (i = 0; i < rule->targets->nelts; i++) {
617 
618         if(targets[i]->name != NULL && strlen(targets[i]->name) > 0)    {
619             target_str = apr_pstrcat(pool,
620                     (target_str == NULL) ? "" : apr_psprintf(pool, "%s|", target_str),
621                     (targets[i]->is_negated == 0) ? "" : "!",
622                     (targets[i]->is_counting == 0) ? "" : "&",
623                     (targets[i]->name == NULL) ? "" : targets[i]->name,
624                     (targets[i]->param == NULL) ? "" : apr_psprintf(pool, ":%s", targets[i]->param),
625                     NULL);
626         }
627 
628     }
629 
630     return target_str;
631 }
632 
633 /**
634  * Generate an action string from an actionset.
635  */
msre_actionset_generate_action_string(apr_pool_t * pool,const msre_actionset * actionset)636 static char *msre_actionset_generate_action_string(apr_pool_t *pool, const msre_actionset *actionset)  {
637     const apr_array_header_t *tarr = NULL;
638     const apr_table_entry_t *telts = NULL;
639     char *actions = NULL;
640     int chain;
641     int i;
642 
643     if (actionset == NULL) return NULL;
644 
645     chain = ((actionset->rule != NOT_SET_P) && actionset->rule->chain_starter) ? 1 : 0;
646 
647     tarr = apr_table_elts(actionset->actions);
648     telts = (const apr_table_entry_t*)tarr->elts;
649 
650     for (i = 0; i < tarr->nelts; i++) {
651         msre_action *action = (msre_action *)telts[i].val;
652         int use_quotes = 0;
653 
654         if (chain) {
655             /* Skip some actions that are not used in a chain. */
656             if (   (action->metadata->type == ACTION_DISRUPTIVE)
657                     || (action->metadata->type == ACTION_METADATA)
658                     || (strcmp("log", action->metadata->name) == 0)
659                     || (strcmp("auditlog", action->metadata->name) == 0)
660                     || (strcmp("nolog", action->metadata->name) == 0)
661                     || (strcmp("noauditlog", action->metadata->name) == 0)
662                     || (strcmp("severity", action->metadata->name) == 0)
663                     || (strcmp("ver", action->metadata->name) == 0)
664                     || (strcmp("maturity", action->metadata->name) == 0)
665                     || (strcmp("accuracy", action->metadata->name) == 0)
666                     || (strcmp("tag", action->metadata->name) == 0)
667                     || (strcmp("phase", action->metadata->name) == 0))
668             {
669                 continue;
670             }
671         }
672 
673         /* Check if we need any quotes */
674         if (action->param != NULL) {
675             int j;
676             for(j = 0; action->param[j] != '\0'; j++) {
677                 if (isspace(action->param[j])) {
678                     use_quotes = 1;
679                     break;
680                 }
681             }
682             if (j == 0) use_quotes = 1;
683         }
684 
685         actions = apr_pstrcat(pool,
686                 (actions == NULL) ? "" : actions,
687                 (actions == NULL) ? "" : ",",
688                 action->metadata->name,
689                 (action->param == NULL) ? "" : ":",
690                 (use_quotes) ? "'" : "",
691                 (action->param == NULL) ? "" : action->param,
692                 (use_quotes) ? "'" : "",
693                 NULL);
694     }
695 
696     return actions;
697 }
698 
699 /**
700  * Add an action to an actionset.
701  */
msre_actionset_action_add(msre_actionset * actionset,msre_action * action)702 static void msre_actionset_action_add(msre_actionset *actionset, msre_action *action)
703 {
704     msre_action *add_action = action;
705 
706     if ((actionset == NULL)) return;
707 
708     /**
709      * The "block" action is just a placeholder for the parent action.
710      */
711     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)) {
712         /* revert back to parent */
713         actionset->intercept_action = actionset->parent_intercept_action;
714         add_action = actionset->parent_intercept_action_rec;
715     }
716 
717     if ((add_action == NULL)) return;
718 
719     if (add_action->metadata->cardinality_group != ACTION_CGROUP_NONE) {
720         msre_actionset_cardinality_fixup(actionset, add_action);
721     }
722 
723     if (add_action->metadata->cardinality == ACTION_CARDINALITY_ONE) {
724         /* One action per actionlist. */
725         apr_table_setn(actionset->actions, add_action->metadata->name, (void *)add_action);
726     } else {
727         /* Multiple actions per actionlist. */
728         apr_table_addn(actionset->actions, add_action->metadata->name, (void *)add_action);
729     }
730 }
731 
732 /**
733  * Creates msre_var instances (rule variables) out of the
734  * given text string and places them into the supplied table.
735  */
msre_parse_targets(msre_ruleset * ruleset,const char * text,apr_array_header_t * arr,char ** error_msg)736 static apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
737         apr_array_header_t *arr, char **error_msg)
738 {
739     const apr_array_header_t *tarr;
740     const apr_table_entry_t *telts;
741     apr_table_t *vartable;
742     unsigned int count = 0;
743     apr_status_t rc;
744     msre_var *var;
745     int i;
746 
747     if (text == NULL) return -1;
748 
749     /* Extract name & value pairs first */
750     vartable = apr_table_make(ruleset->mp, 10);
751     if (vartable == NULL) return -1;
752     rc = msre_parse_generic(ruleset->mp, text, vartable, error_msg);
753     if (rc < 0) return rc;
754 
755     /* Loop through the table and create variables */
756     tarr = apr_table_elts(vartable);
757     telts = (const apr_table_entry_t*)tarr->elts;
758     for (i = 0; i < tarr->nelts; i++) {
759         var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg);
760         if (var == NULL) return -1;
761         *(msre_var **)apr_array_push(arr) = var;
762         count++;
763     }
764 
765     return count;
766 }
767 
768 /**
769  * Creates msre_action instances by parsing the given string, placing
770  * them into the supplied array.
771  */
msre_parse_actions(msre_engine * engine,apr_pool_t * mp,msre_actionset * actionset,const char * text,char ** error_msg)772 static apr_status_t msre_parse_actions(msre_engine *engine, apr_pool_t *mp, msre_actionset *actionset,
773         const char *text, char **error_msg)
774 {
775     const apr_array_header_t *tarr;
776     const apr_table_entry_t *telts;
777     apr_table_t *vartable;
778     unsigned int count = 0;
779     apr_status_t rc;
780     msre_action *action;
781     int i;
782 
783 
784     if (error_msg == NULL) {
785         return -1;
786     }
787     *error_msg = NULL;
788 
789 
790     if (text == NULL) {
791         *error_msg = apr_psprintf(mp, "Internal error: " \
792             "msre_parse_actions, variable text is NULL");
793         return -1;
794     }
795 
796     /* Extract name & value pairs first */
797     vartable = apr_table_make(mp, 10);
798     if (vartable == NULL) {
799         *error_msg = apr_psprintf(mp, "Internal error: " \
800             "msre_parse_actions, failed to create vartable");
801 
802         return -1;
803     }
804     rc = msre_parse_generic(mp, text, vartable, error_msg);
805     if (rc < 0) {
806         if (*error_msg == NULL)
807             *error_msg = apr_psprintf(mp, "Internal error: " \
808                 "msre_parse_actions, msre_parse_generic failed. Return " \
809                 "code: %d", rc);
810 
811         return rc;
812     }
813 
814     /* Loop through the table and create actions */
815     tarr = apr_table_elts(vartable);
816     telts = (const apr_table_entry_t*)tarr->elts;
817     for (i = 0; i < tarr->nelts; i++) {
818         /* Create action. */
819         action = msre_create_action(engine, mp, telts[i].key, telts[i].val, error_msg);
820         if (action == NULL) {
821             if (*error_msg == NULL)
822                 *error_msg = apr_psprintf(mp, "Internal error: " \
823                     "msre_parse_actions, msre_create_action failed.");
824             return -1;
825         }
826 
827         /* Initialise action (option). */
828         if (action->metadata->init != NULL) {
829             action->metadata->init(engine, mp, actionset, action);
830         }
831 
832         msre_actionset_action_add(actionset, action);
833 
834         count++;
835     }
836 
837     return count;
838 }
839 
840 /**
841  * Locates variable metadata given the variable name.
842  */
msre_resolve_var(msre_engine * engine,const char * name)843 msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name)
844 {
845     return (msre_var_metadata *)apr_table_get(engine->variables, name);
846 }
847 
848 /**
849  * Locates action metadata given the action name.
850  */
msre_resolve_action(msre_engine * engine,const char * name)851 static msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name)
852 {
853     return (msre_action_metadata *)apr_table_get(engine->actions, name);
854 }
855 
856 /**
857  * Creates a new variable instance given the variable name
858  * and an (optional) parameter.
859  */
msre_create_var_ex(apr_pool_t * pool,msre_engine * engine,const char * name,const char * param,modsec_rec * msr,char ** error_msg)860 msre_var *msre_create_var_ex(apr_pool_t *pool, msre_engine *engine, const char *name, const char *param,
861         modsec_rec *msr, char **error_msg)
862 {
863     const char *varparam = param;
864     msre_var *var = apr_pcalloc(pool, sizeof(msre_var));
865     if (var == NULL) return NULL;
866 
867     if (error_msg == NULL) return NULL;
868     *error_msg = NULL;
869 
870     /* Handle negation and member counting */
871     if (name[0] == '!') {
872         var->is_negated = 1;
873         var->name = (char *)name + 1;
874     }
875     else
876         if (name[0] == '&') {
877             var->is_counting = 1;
878             var->name = (char *)name + 1;
879         }
880         else {
881             var->name = (char *)name;
882         }
883 
884     /* Treat HTTP_* targets as an alias for REQUEST_HEADERS:* */
885     if (   (var->name != NULL)
886             && (strlen(var->name) > 5)
887             && (strncmp("HTTP_", var->name, 5) == 0))
888     {
889         const char *oldname = var->name;
890         var->name = apr_pstrdup(pool, "REQUEST_HEADERS");
891         varparam = apr_pstrdup(pool, oldname + 5);
892     }
893 
894 
895     /* Resolve variable */
896     var->metadata = msre_resolve_var(engine, var->name);
897     if (var->metadata == NULL) {
898         *error_msg = apr_psprintf(pool, "Unknown variable: %s", name);
899         return NULL;
900     }
901 
902     /* The counting operator "&" can only be used against collections. */
903     if (var->is_counting) {
904         if (var->metadata->type == VAR_SIMPLE) {
905             *error_msg = apr_psprintf(pool, "The & modificator does not apply to "
906                     "non-collection variables.");
907             return NULL;
908         }
909     }
910 
911     /* Check the parameter. */
912     if (varparam == NULL) {
913         if (var->metadata->argc_min > 0) {
914             *error_msg = apr_psprintf(pool, "Missing mandatory parameter for variable %s.",
915                     name);
916             return NULL;
917         }
918     } else { /* Parameter present */
919 
920         /* Do we allow a parameter? */
921         if (var->metadata->argc_max == 0) {
922             *error_msg = apr_psprintf(pool, "Variable %s does not support parameters.",
923                     name);
924             return NULL;
925         }
926 
927         var->param = (char *)varparam;
928 
929     }
930 
931     return var;
932 }
933 
934 /**
935  * Create a new variable object from the provided name and value.
936  *
937  * NOTE: this allocates out of the global pool and should not be used
938  *       per-request
939  */
msre_create_var(msre_ruleset * ruleset,const char * name,const char * param,modsec_rec * msr,char ** error_msg)940 static msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
941         modsec_rec *msr, char **error_msg)
942 {
943     msre_var *var = msre_create_var_ex(ruleset->mp, ruleset->engine, name, param, msr, error_msg);
944     if (var == NULL) return NULL;
945 
946     /* Validate & initialise variable */
947     if (var->metadata->validate != NULL) {
948         *error_msg = var->metadata->validate(ruleset, var);
949         if (*error_msg != NULL) {
950             return NULL;
951         }
952     }
953 
954     return var;
955 }
956 
957 /**
958  * Creates a new action instance given its name and an (optional) parameter.
959  */
msre_create_action(msre_engine * engine,apr_pool_t * mp,const char * name,const char * param,char ** error_msg)960 msre_action *msre_create_action(msre_engine *engine, apr_pool_t *mp, const char *name, const char *param,
961         char **error_msg)
962 {
963     msre_action *action = NULL;
964 
965     if (error_msg == NULL) {
966         return NULL;
967     }
968     *error_msg = NULL;
969 
970 
971     action = apr_pcalloc(mp, sizeof(msre_action));
972 
973     if (action == NULL) {
974         *error_msg = apr_psprintf(mp, "Internal error: " \
975             "msre_create_action, not able to allocate action");
976 
977         return NULL;
978     }
979 
980     /* Resolve action */
981     action->metadata = msre_resolve_action(engine, name);
982     if (action->metadata == NULL) {
983         *error_msg = apr_psprintf(mp, "Unknown action: %s", name);
984         return NULL;
985     }
986 
987     if (param == NULL) { /* Parameter not present */
988         if (action->metadata->argc_min > 0) {
989             *error_msg = apr_psprintf(mp, "Missing mandatory parameter for action %s",
990                     name);
991             return NULL;
992         }
993     } else { /* Parameter present */
994 
995         /* Should we allow the parameter? */
996         if (action->metadata->argc_max == 0) {
997             *error_msg = apr_psprintf(mp, "Extra parameter provided to action %s", name);
998             return NULL;
999         }
1000 
1001         /* Handle +/- modificators */
1002         if ((param[0] == '+')||(param[0] == '-')) {
1003             if (action->metadata->allow_param_plusminus == 0) {
1004                 *error_msg = apr_psprintf(mp,
1005                         "Action %s does not allow +/- modificators.", name);
1006                 return NULL;
1007             }
1008             else { /* Modificators allowed. */
1009                 if (param[0] == '+') {
1010                     action->param = param + 1;
1011                     action->param_plusminus = POSITIVE_VALUE;
1012                 } else
1013                     if (param[0] == '-') {
1014                         action->param = param + 1;
1015                         action->param_plusminus = NEGATIVE_VALUE;
1016                     }
1017             }
1018         } else {
1019             action->param = param;
1020         }
1021 
1022         /* Validate parameter */
1023         if (action->metadata->validate != NULL) {
1024             *error_msg = action->metadata->validate(engine, mp, action);
1025             if (*error_msg != NULL) return NULL;
1026         }
1027     }
1028 
1029     return action;
1030 }
1031 
1032 /**
1033  * Generic parser that is used as basis for target and action parsing.
1034  * It breaks up the input string into name-parameter pairs and places
1035  * them into the given table.
1036  */
msre_parse_generic(apr_pool_t * mp,const char * text,apr_table_t * vartable,char ** error_msg)1037 int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable,
1038         char **error_msg)
1039 {
1040     char *p = (char *)text;
1041     int count = 0;
1042 
1043     if (error_msg == NULL) return -1;
1044     *error_msg = NULL;
1045 
1046     count = 0;
1047     while(*p != '\0') {
1048         char *name = NULL, *value = NULL;
1049 
1050         /* ignore whitespace */
1051         while(isspace(*p)) p++;
1052         if (*p == '\0') return count;
1053 
1054         /* we are at the beginning of the name */
1055         name = p;
1056         while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */
1057 
1058         /* get the name */
1059         name = apr_pstrmemdup(mp, name, p - name);
1060 
1061         if (*p != ':') { /* we don't have a parameter */
1062             /* add to the table with no value */
1063             apr_table_addn(vartable, name, NULL);
1064             count++;
1065 
1066             /* go over any whitespace present */
1067             while(isspace(*p)) p++;
1068 
1069             /* we're done */
1070             if (*p == '\0') {
1071                 return count;
1072             }
1073 
1074             /* skip over the separator character and continue */
1075             if ((*p == ',')||(*p == '|')) {
1076                 p++;
1077                 continue;
1078             }
1079 
1080             *error_msg = apr_psprintf(mp, "Unexpected character at position %d: %s",
1081                     (int)(p - text), text);
1082             return -1;
1083         }
1084 
1085         /* we have a parameter */
1086 
1087         p++; /* move over the colon */
1088 
1089         /* we'll allow empty values */
1090         if (*p == '\0') {
1091             apr_table_addn(vartable, name, NULL);
1092             count++;
1093             return count;
1094         }
1095 
1096         if ((*p == ',')||(*p == '|')) {
1097             apr_table_addn(vartable, name, NULL);
1098             count++;
1099             /* move over the separator char and continue */
1100             p++;
1101             continue;
1102         }
1103 
1104         /* we really have a parameter */
1105 
1106         if (*p == '\'') { /* quoted value */
1107             char *d = NULL;
1108 
1109             p++; /* go over the openning quote */
1110             value = d = strdup(p);
1111             if (d == NULL) return -1;
1112 
1113             for(;;) {
1114                 if (*p == '\0') {
1115                     *error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s",
1116                             (int)(p - text), text);
1117                     free(value);
1118                     return -1;
1119                 } else
1120                     if (*p == '\\') {
1121                         if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) {
1122                             *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s",
1123                                     (int)(p - text), text);
1124                             free(value);
1125                             return -1;
1126                         }
1127                         p++;
1128                         *(d++) = *(p++);
1129                     } else
1130                         if (*p == '\'') {
1131                             *d = '\0';
1132                             p++;
1133                             break;
1134                         }
1135                         else {
1136                             *(d++) = *(p++);
1137                         }
1138             }
1139 
1140             d = value;
1141             value = apr_pstrdup(mp, d);
1142             free(d);
1143         } else { /* non-quoted value */
1144             value = p;
1145             while((*p != '\0')&&(*p != ',')&&(*p != '|')&&(!isspace(*p))) p++;
1146             value = apr_pstrmemdup(mp, value, p - value);
1147         }
1148 
1149         /* add to table */
1150         apr_table_addn(vartable, name, value);
1151         count++;
1152 
1153         /* move to the first character of the next name-value pair */
1154         while(isspace(*p)||(*p == ',')||(*p == '|')) p++;
1155     }
1156 
1157     return count;
1158 }
1159 
1160 
1161 /* -- Actionset functions -------------------------------------------------- */
1162 
1163 /**
1164  * Creates an actionset instance and (as an option) populates it by
1165  * parsing the given string which contains a list of actions.
1166  */
msre_actionset_create(msre_engine * engine,apr_pool_t * mp,const char * text,char ** error_msg)1167 msre_actionset *msre_actionset_create(msre_engine *engine, apr_pool_t *mp, const char *text,
1168         char **error_msg)
1169 {
1170     msre_actionset *actionset = NULL;
1171 
1172     if (error_msg == NULL) {
1173         return NULL;
1174     }
1175 
1176     *error_msg = NULL;
1177 
1178     actionset = (msre_actionset *)apr_pcalloc(mp,
1179             sizeof(msre_actionset));
1180 
1181     if (actionset == NULL) {
1182         *error_msg = apr_psprintf(mp, "Internal error: " \
1183                 "msre_actionset_create, not able to allocate msre_actionset");
1184         return NULL;
1185     }
1186 
1187     actionset->actions = apr_table_make(mp, 25);
1188     if (actionset->actions == NULL) {
1189         *error_msg = apr_psprintf(mp, "Internal error: " \
1190                 "msre_actionset_create, not able to create actions table");
1191         return NULL;
1192     }
1193 
1194     /* Metadata */
1195     actionset->id = NOT_SET_P;
1196     actionset->rev = NOT_SET_P;
1197     actionset->msg = NOT_SET_P;
1198     actionset->version = NOT_SET_P;
1199     actionset->logdata = NOT_SET_P;
1200     actionset->phase = NOT_SET;
1201     actionset->severity = -1;
1202     actionset->accuracy = -1;
1203     actionset->maturity = -1;
1204     actionset->rule = NOT_SET_P;
1205     actionset->arg_max = -1;
1206     actionset->arg_min = -1;
1207 
1208     /* Flow */
1209     actionset->is_chained = NOT_SET;
1210     actionset->skip_count = NOT_SET;
1211     actionset->skip_after = NOT_SET_P;
1212 
1213     /* Disruptive */
1214     actionset->parent_intercept_action_rec = NOT_SET_P;
1215     actionset->intercept_action_rec = NOT_SET_P;
1216     actionset->parent_intercept_action = NOT_SET;
1217     actionset->intercept_action = NOT_SET;
1218     actionset->intercept_uri = NOT_SET_P;
1219     actionset->intercept_status = NOT_SET;
1220     actionset->intercept_pause = NOT_SET_P;
1221 
1222     /* Other */
1223     actionset->auditlog = NOT_SET;
1224     actionset->log = NOT_SET;
1225 
1226     /* Parse the list of actions, if it's present */
1227     if (text != NULL) {
1228         int ret = msre_parse_actions(engine, mp, actionset, text, error_msg);
1229         if (ret < 0) {
1230             if (*error_msg == NULL)
1231                 *error_msg = apr_psprintf(mp, "Internal error: " \
1232                         "msre_actionset_create, msre_parse_actions failed " \
1233                         "without further information. Return code: %d", ret);
1234             return NULL;
1235         }
1236     }
1237 
1238     return actionset;
1239 }
1240 
1241 /**
1242  * Create a (shallow) copy of the supplied actionset.
1243  */
msre_actionset_copy(apr_pool_t * mp,msre_actionset * orig)1244 static msre_actionset *msre_actionset_copy(apr_pool_t *mp, msre_actionset *orig) {
1245     msre_actionset *copy = NULL;
1246 
1247     if (orig == NULL) return NULL;
1248     copy = (msre_actionset *)apr_pmemdup(mp, orig, sizeof(msre_actionset));
1249     if (copy == NULL) return NULL;
1250     copy->actions = apr_table_copy(mp, orig->actions);
1251 
1252     return copy;
1253 }
1254 
1255 /**
1256  * Merges two actionsets into one.
1257  */
msre_actionset_merge(msre_engine * engine,apr_pool_t * mp,msre_actionset * parent,msre_actionset * child,int inherit_by_default)1258 msre_actionset *msre_actionset_merge(msre_engine *engine, apr_pool_t *mp, msre_actionset *parent,
1259         msre_actionset *child, int inherit_by_default)
1260 {
1261     msre_actionset *merged = NULL;
1262     const apr_array_header_t *tarr;
1263     const apr_table_entry_t *telts;
1264     int i;
1265 
1266     if (inherit_by_default == 0) {
1267         /* There is nothing to merge in this case. */
1268 	    return msre_actionset_copy(mp, child);
1269     }
1270 
1271     /* Start with a copy of the parent configuration. */
1272     merged = msre_actionset_copy(mp, parent);
1273     if (merged == NULL) return NULL;
1274 
1275     if (child == NULL) {
1276         /* The child actionset does not exist, hence
1277          * go with the parent one.
1278          */
1279         return merged;
1280     }
1281 
1282     /* First merge the hard-coded stuff. */
1283 
1284     /* Metadata */
1285     if (child->id != NOT_SET_P) merged->id = child->id;
1286     if (child->rev != NOT_SET_P) merged->rev = child->rev;
1287     if (child->msg != NOT_SET_P) merged->msg = child->msg;
1288     if (child->version != NOT_SET_P) merged->version = child->version;
1289     if (child->logdata != NOT_SET_P) merged->logdata = child->logdata;
1290     if (child->severity != NOT_SET) merged->severity = child->severity;
1291     if (child->accuracy != NOT_SET) merged->accuracy = child->accuracy;
1292     if (child->maturity != NOT_SET) merged->maturity = child->maturity;
1293     if (child->phase != NOT_SET) merged->phase = child->phase;
1294     if (child->rule != NOT_SET_P) merged->rule = child->rule;
1295     if (child->arg_min != NOT_SET) merged->arg_min = child->arg_min;
1296     if (child->arg_max != NOT_SET) merged->arg_max = child->arg_max;
1297 
1298     /* Flow */
1299     merged->is_chained = child->is_chained;
1300     if (child->skip_count != NOT_SET) merged->skip_count = child->skip_count;
1301     if (child->skip_after != NOT_SET_P) merged->skip_after = child->skip_after;
1302 
1303     /* Disruptive */
1304     if (child->intercept_action != NOT_SET) {
1305         merged->intercept_action_rec = child->intercept_action_rec;
1306         merged->intercept_action = child->intercept_action;
1307         merged->intercept_uri = child->intercept_uri;
1308     }
1309 
1310     if (child->intercept_status != NOT_SET) merged->intercept_status = child->intercept_status;
1311     if (child->intercept_pause != NOT_SET_P) merged->intercept_pause = child->intercept_pause;
1312 
1313     /* Other */
1314     if (child->auditlog != NOT_SET) merged->auditlog = child->auditlog;
1315     if (child->log != NOT_SET) merged->log = child->log;
1316 
1317 
1318     /* Now merge the actions. */
1319 
1320     tarr = apr_table_elts(child->actions);
1321     telts = (const apr_table_entry_t*)tarr->elts;
1322     for (i = 0; i < tarr->nelts; i++) {
1323         msre_actionset_action_add(merged, (msre_action *)telts[i].val);
1324     }
1325 
1326     return merged;
1327 }
1328 
1329 /**
1330  * Creates an actionset that contains a default list of actions.
1331  */
msre_actionset_create_default(msre_engine * engine)1332 msre_actionset *msre_actionset_create_default(msre_engine *engine) {
1333     char  *my_error_msg = NULL;
1334     return msre_actionset_create(engine,
1335             engine->mp,
1336             "phase:2,log,auditlog,pass",
1337             &my_error_msg);
1338 }
1339 
1340 /**
1341  * Sets the default values for the hard-coded actionset configuration.
1342  */
msre_actionset_set_defaults(msre_actionset * actionset)1343 void msre_actionset_set_defaults(msre_actionset *actionset) {
1344 
1345     if (actionset == NULL) {
1346         return;
1347     }
1348     /* Metadata */
1349     if (actionset->id == NOT_SET_P) actionset->id = NULL;
1350     if (actionset->rev == NOT_SET_P) actionset->rev = NULL;
1351     if (actionset->msg == NOT_SET_P) actionset->msg = NULL;
1352     if (actionset->version == NOT_SET_P) actionset->version = NULL;
1353     if (actionset->logdata == NOT_SET_P) actionset->logdata = NULL;
1354     if (actionset->phase == NOT_SET) actionset->phase = 2;
1355     if (actionset->severity == -1) {} /* leave at -1 */
1356     if (actionset->accuracy == -1) {} /* leave at -1 */
1357     if (actionset->maturity == -1) {} /* leave at -1 */
1358     if (actionset->rule == NOT_SET_P) actionset->rule = NULL;
1359     if (actionset->arg_max == NOT_SET) actionset->arg_max = -1;
1360     if (actionset->arg_min == NOT_SET) actionset->arg_min = -1;
1361 
1362     /* Flow */
1363     if (actionset->is_chained == NOT_SET) actionset->is_chained = 0;
1364     if (actionset->skip_count == NOT_SET) actionset->skip_count = 0;
1365     if (actionset->skip_after == NOT_SET_P) actionset->skip_after = NULL;
1366 
1367     /* Disruptive */
1368     if (actionset->parent_intercept_action_rec == NOT_SET_P) actionset->parent_intercept_action_rec = NULL;
1369     if (actionset->intercept_action_rec == NOT_SET_P) actionset->intercept_action_rec = NULL;
1370     if (actionset->parent_intercept_action == NOT_SET) actionset->parent_intercept_action = ACTION_NONE;
1371     if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE;
1372     if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL;
1373     if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403;
1374     if (actionset->intercept_pause == NOT_SET_P) actionset->intercept_pause = NULL;
1375 
1376     /* Other */
1377     if (actionset->auditlog == NOT_SET) actionset->auditlog = 1;
1378     if (actionset->log == NOT_SET) actionset->log = 1;
1379 }
1380 
1381 /* -- Engine functions ----------------------------------------------------- */
1382 
1383 /**
1384  * Creates a new engine instance.
1385  */
msre_engine_create(apr_pool_t * parent_pool)1386 msre_engine *msre_engine_create(apr_pool_t *parent_pool) {
1387     msre_engine *engine;
1388     apr_pool_t *mp;
1389 
1390     /* Create new memory pool */
1391     if (apr_pool_create(&mp, parent_pool) != APR_SUCCESS) return NULL;
1392 
1393     /* Init fields */
1394     engine = apr_pcalloc(mp, sizeof(msre_engine));
1395     if (engine == NULL) return NULL;
1396     engine->mp = mp;
1397     engine->tfns = apr_table_make(mp, 50);
1398     if (engine->tfns == NULL) return NULL;
1399     engine->operators = apr_table_make(mp, 25);
1400     if (engine->operators == NULL) return NULL;
1401     engine->variables = apr_table_make(mp, 100);
1402     if (engine->variables == NULL) return NULL;
1403     engine->actions = apr_table_make(mp, 50);
1404     if (engine->actions == NULL) return NULL;
1405     engine->reqbody_processors = apr_table_make(mp, 10);
1406     if (engine->reqbody_processors == NULL) return NULL;
1407 
1408     return engine;
1409 }
1410 
1411 
1412 /* -- Recipe functions ----------------------------------------------------- */
1413 
1414 #define NEXT_CHAIN  1
1415 #define NEXT_RULE   2
1416 #define SKIP_RULES  3
1417 
1418 
1419 
1420 /**
1421  * Default implementation of the ruleset phase processing; it processes
1422  * the rules in the ruleset attached to the currently active
1423  * transaction phase.
1424  */
1425 #if defined(PERFORMANCE_MEASUREMENT)
1426 
1427 #define PERFORMANCE_MEASUREMENT_LOOP 5000
1428 
1429 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr);
1430 
msre_ruleset_process_phase(msre_ruleset * ruleset,modsec_rec * msr)1431 apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
1432     apr_array_header_t *arr = NULL;
1433     msre_rule **rules = NULL;
1434     apr_status_t rc;
1435     int i;
1436 
1437     switch (msr->phase) {
1438         case PHASE_REQUEST_HEADERS :
1439             arr = ruleset->phase_request_headers;
1440             break;
1441         case PHASE_REQUEST_BODY :
1442             arr = ruleset->phase_request_body;
1443             break;
1444         case PHASE_RESPONSE_HEADERS :
1445             arr = ruleset->phase_response_headers;
1446             break;
1447         case PHASE_RESPONSE_BODY :
1448             arr = ruleset->phase_response_body;
1449             break;
1450         case PHASE_LOGGING :
1451             arr = ruleset->phase_logging;
1452             break;
1453         default :
1454             msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
1455             return -1;
1456     }
1457 
1458     rules = (msre_rule **)arr->elts;
1459     for (i = 0; i < arr->nelts; i++) {
1460         msre_rule *rule = rules[i];
1461         rule->execution_time = 0;
1462     }
1463 
1464     for (i = 0; i < PERFORMANCE_MEASUREMENT_LOOP; i++) {
1465         rc = msre_ruleset_process_phase_(ruleset, msr);
1466     }
1467 
1468     msr_log(msr, 1, "Phase %d", msr->phase);
1469 
1470     rules = (msre_rule **)arr->elts;
1471     for (i = 0; i < arr->nelts; i++) {
1472         msre_rule *rule = rules[i];
1473 
1474         /* Ignore markers, which are never processed. */
1475         if (rule->placeholder == RULE_PH_MARKER) continue;
1476 
1477         msr_log(msr, 1, "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"]: %u usec", rule,
1478                 ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
1479                 rule->filename != NULL ? rule->filename : "-",
1480                 rule->line_num,
1481                 (rule->execution_time / PERFORMANCE_MEASUREMENT_LOOP));
1482     }
1483 
1484     return rc;
1485 }
1486 
msre_ruleset_process_phase_(msre_ruleset * ruleset,modsec_rec * msr)1487 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr) {
1488 #else
1489     apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
1490 #endif
1491         apr_array_header_t *arr = NULL;
1492         msre_rule **rules;
1493         apr_status_t rc;
1494         const char *skip_after = NULL;
1495         msre_rule *last_rule = NULL;
1496         msre_rule *rule_starter = NULL;
1497         int i, mode, skip, skipped, saw_starter;
1498 
1499         /* First determine which set of rules we need to use. */
1500         switch (msr->phase) {
1501             case PHASE_REQUEST_HEADERS :
1502                 arr = ruleset->phase_request_headers;
1503                 break;
1504             case PHASE_REQUEST_BODY :
1505                 arr = ruleset->phase_request_body;
1506                 break;
1507             case PHASE_RESPONSE_HEADERS :
1508                 arr = ruleset->phase_response_headers;
1509                 break;
1510             case PHASE_RESPONSE_BODY :
1511                 arr = ruleset->phase_response_body;
1512                 break;
1513             case PHASE_LOGGING :
1514                 arr = ruleset->phase_logging;
1515                 break;
1516             default :
1517                 msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
1518                 return -1;
1519         }
1520 
1521         if (msr->txcfg->debuglog_level >= 9) {
1522             msr_log(msr, 9, "This phase consists of %d rule(s).", arr->nelts);
1523         }
1524 
1525         apr_table_clear(msr->matched_vars);
1526 
1527         /* Loop through the rules in the selected set. */
1528         skip = 0;
1529         skipped = 0;
1530         saw_starter = 0;
1531         mode = NEXT_RULE;
1532         rules = (msre_rule **)arr->elts;
1533         for (i = 0; i < arr->nelts; i++) {
1534             msre_rule *rule = rules[i];
1535 #if defined(PERFORMANCE_MEASUREMENT)
1536             apr_time_t time1 = 0;
1537 #endif
1538 
1539             /* Reset the rule interception flag */
1540             msr->rule_was_intercepted = 0;
1541 
1542             /* SKIP_RULES is used to skip all rules until we hit a placeholder
1543              * with the specified rule ID and then resume execution after that.
1544              */
1545             if (mode == SKIP_RULES) {
1546                 /* Go to the next rule if we have not yet hit the skip_after ID */
1547 
1548                 if ((rule->placeholder == RULE_PH_NONE) || (rule->actionset->id == NULL) || (strcmp(skip_after, rule->actionset->id) != 0)) {
1549 
1550                     if(i-1 >=0)
1551                         last_rule = rules[i-1];
1552                     else
1553                         last_rule = rules[0];
1554 
1555                     if((last_rule != NULL) && (last_rule->actionset != NULL) && last_rule->actionset->is_chained && (saw_starter == 1)) {
1556                         mode = NEXT_RULE;
1557                         skipped = 1;
1558                         --i;
1559                     } else {
1560                         mode = SKIP_RULES;
1561                         skipped = 0;
1562                         saw_starter = 0;
1563 
1564                         if (msr->txcfg->debuglog_level >= 9) {
1565                             msr_log(msr, 9, "Current rule is id=\"%s\" [chained %d] is trying to find the SecMarker=\"%s\" [stater %d]",rule->actionset->id,last_rule->actionset->is_chained,skip_after,saw_starter);
1566                         }
1567 
1568                     }
1569 
1570                     continue;
1571                 }
1572 
1573                 if (msr->txcfg->debuglog_level >= 9) {
1574                     msr_log(msr, 9, "Found rule %pp id=\"%s\".", rule, skip_after);
1575                 }
1576 
1577                 /* Go to the rule *after* this one to continue execution. */
1578                 if (msr->txcfg->debuglog_level >= 4) {
1579                     msr_log(msr, 4, "Continuing execution after rule id=\"%s\".", skip_after);
1580                 }
1581 
1582                 saw_starter = 0;
1583                 skipped = 0;
1584                 skip_after = NULL;
1585                 mode = NEXT_RULE;
1586                 apr_table_clear(msr->matched_vars);
1587                 continue;
1588             }
1589 
1590             /* Skip any rule marked as a placeholder */
1591             if (rule->placeholder != RULE_PH_NONE) {
1592                 continue;
1593             }
1594 
1595             /* NEXT_CHAIN is used when one of the rules in a chain
1596              * fails to match and then we need to skip the remaining
1597              * rules in that chain in order to get to the next
1598              * rule that can execute.
1599              */
1600             if (mode == NEXT_CHAIN) {
1601                 if (rule->actionset->is_chained == 0) {
1602                     mode = NEXT_RULE;
1603                 }
1604 
1605                 /* Go to the next rule. */
1606                 apr_table_clear(msr->matched_vars);
1607                 continue;
1608             }
1609 
1610             /* If we are here that means the mode is NEXT_RULE, which
1611              * then means we have done processing any chains. However,
1612              * if the "skip" parameter is set we need to skip over.
1613              */
1614             if ((mode == NEXT_RULE)&&(skip > 0)) {
1615                 /* Decrement the skip counter by one. */
1616                 skip--;
1617 
1618                 /* If the current rule is part of a chain then
1619                  * we need to skip over the entire chain. Thus
1620                  * we change the mode to NEXT_CHAIN. The skip
1621                  * counter will not decrement as we are moving
1622                  * over the rules belonging to the chain.
1623                  */
1624                 if (rule->actionset->is_chained) {
1625                     mode = NEXT_CHAIN;
1626                 }
1627 
1628                 /* Go to the next rule. */
1629                 apr_table_clear(msr->matched_vars);
1630                 continue;
1631             }
1632 
1633             /* Check if this rule was removed at runtime */
1634         if (((rule->actionset->id !=NULL) && !apr_is_empty_array(msr->removed_rules)) ||
1635                  (apr_is_empty_array(msr->removed_rules_tag)==0) || (apr_is_empty_array(msr->removed_rules_msg)==0)) {
1636             int j, act, rc;
1637             int do_process = 1;
1638             const char *range = NULL;
1639             rule_exception *re = NULL;
1640             char *my_error_msg;
1641             const apr_array_header_t *tag_tarr = NULL;
1642             const apr_table_entry_t *tag_telts = NULL;
1643 
1644             for(j = 0; j < msr->removed_rules_msg->nelts; j++) {
1645                 re = ((rule_exception **)msr->removed_rules_msg->elts)[j];
1646 
1647                 if(rule->actionset->msg !=NULL)  {
1648 
1649                     if (msr->txcfg->debuglog_level >= 9) {
1650                         msr_log(msr, 9, "Checking removal of rule msg=\"%s\" against: %s", rule->actionset->msg, re->param);
1651                     }
1652 
1653                     rc = msc_regexec(re->param_data,
1654                             rule->actionset->msg, strlen(rule->actionset->msg),
1655                             &my_error_msg);
1656                     if (rc >= 0)    {
1657                         do_process = 0;
1658                         break;
1659                     }
1660                 }
1661             }
1662 
1663             for(j = 0; j < msr->removed_rules->nelts; j++) {
1664                 range = ((const char**)msr->removed_rules->elts)[j];
1665 
1666                 if(rule->actionset->id !=NULL)  {
1667 
1668                     if (msr->txcfg->debuglog_level >= 9) {
1669                         msr_log(msr, 9, "Checking removal of rule id=\"%s\" against: %s", rule->actionset->id, range);
1670                     }
1671 
1672                     if (rule_id_in_range(atoi(rule->actionset->id), range)) {
1673                         do_process = 0;
1674                         break;
1675                     }
1676                 }
1677             }
1678 
1679             tag_tarr = apr_table_elts(rule->actionset->actions);
1680             tag_telts = (const apr_table_entry_t*)tag_tarr->elts;
1681 
1682             for (act = 0; act < tag_tarr->nelts; act++) {
1683                 msre_action *action = (msre_action *)tag_telts[act].val;
1684 
1685                 if((action != NULL) && (action->metadata != NULL ) && strcmp("tag", action->metadata->name) == 0)  {
1686 
1687                     for(j = 0; j < msr->removed_rules_tag->nelts; j++) {
1688                         re = ((rule_exception **)msr->removed_rules_tag->elts)[j];
1689 
1690 
1691                         if(action->param != NULL)   {
1692                             /* Expand variables in the tag argument. */
1693                             msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1694 
1695                             var->value = (char *)action->param;
1696                             var->value_len = strlen(action->param);
1697                             expand_macros(msr, var, NULL, msr->mp);
1698 
1699                             if (msr->txcfg->debuglog_level >= 9) {
1700                                 msr_log(msr, 9, "Checking removal of rule tag=\"%s\" against: %s", var->value, re->param);
1701                             }
1702 
1703                             rc = msc_regexec(re->param_data,
1704                                     var->value, strlen(var->value),
1705                                     &my_error_msg);
1706                             if (rc >= 0)    {
1707                                 do_process = 0;
1708                                 break;
1709                             }
1710 
1711                         }
1712                     }
1713                 }
1714             }
1715 
1716             /* Go to the next rule if this one has been removed. */
1717             if (do_process == 0) {
1718                 if (msr->txcfg->debuglog_level >= 5) {
1719                     msr_log(msr, 5, "Not processing %srule id=\"%s\": "
1720                             "removed by ctl action",
1721                             rule->actionset->is_chained ? "chained " : "",
1722                             rule->actionset->id);
1723                 }
1724 
1725                 /* Skip the whole chain, if this is a chained rule */
1726                 if (rule->actionset->is_chained) {
1727                     mode = NEXT_CHAIN;
1728                 }
1729 
1730                 skipped = 0;
1731                 saw_starter = 0;
1732                 apr_table_clear(msr->matched_vars);
1733                 continue;
1734             }
1735         }
1736 
1737         if(msr->txcfg->is_enabled == MODSEC_DISABLED)   {
1738             saw_starter = 0;
1739             skipped = 0;
1740             skip_after = NULL;
1741             mode = NEXT_RULE;
1742             apr_table_clear(msr->matched_vars);
1743             continue;
1744         }
1745 
1746         if (msr->txcfg->debuglog_level >= 4) {
1747             apr_pool_t *p = msr->mp;
1748             const char *fn = NULL;
1749             const char *id = NULL;
1750             const char *rev = NULL;
1751 
1752             if (rule->filename != NULL) {
1753                 fn = apr_psprintf(p, " [file \"%s\"] [line \"%d\"]", rule->filename, rule->line_num);
1754             }
1755 
1756             if (rule->actionset != NULL && rule->actionset->id != NULL) {
1757                 id = apr_psprintf(p, " [id \"%s\"]", rule->actionset->id);
1758             }
1759 
1760             if (rule->actionset != NULL && rule->actionset->rev != NULL) {
1761                 rev = apr_psprintf(p, " [rev \"%s\"]", rule->actionset->rev);
1762             }
1763 
1764             msr_log(msr, 4, "Recipe: Invoking rule %pp;%s%s%s.",
1765                     rule, (fn ? fn : ""), (id ? id : ""), (rev ? rev : ""));
1766             msr_log(msr, 5, "Rule %pp: %s", rule, rule->unparsed);
1767         }
1768 
1769 #if defined(PERFORMANCE_MEASUREMENT)
1770         time1 = apr_time_now();
1771 #endif
1772 
1773         rc = msre_rule_process(rule, msr);
1774 
1775 #if defined(PERFORMANCE_MEASUREMENT)
1776         rule->execution_time += (apr_time_now() - time1);
1777 #endif
1778 
1779         if (msr->txcfg->debuglog_level >= 4) {
1780             msr_log(msr, 4, "Rule returned %d.", rc);
1781         }
1782 
1783         if (rc == RULE_NO_MATCH) {
1784             if (rule->actionset->is_chained) {
1785                 /* If the current rule is part of a chain then
1786                  * we need to skip over all the rules in the chain.
1787                  */
1788                 mode = NEXT_CHAIN;
1789                 if (msr->txcfg->debuglog_level >= 9) {
1790                     msr_log(msr, 9, "No match, chained -> mode NEXT_CHAIN.");
1791                 }
1792             } else {
1793                 /* This rule is not part of a chain so we simply
1794                  * move to the next rule.
1795                  */
1796                 mode = NEXT_RULE;
1797                 if (msr->txcfg->debuglog_level >= 9) {
1798                     msr_log(msr, 9, "No match, not chained -> mode NEXT_RULE.");
1799                 }
1800             }
1801 
1802             apr_table_clear(msr->matched_vars);
1803             skipped = 0;
1804             saw_starter = 0;
1805         }
1806         else if (rc == RULE_MATCH) {
1807             if (msr->rule_was_intercepted) {
1808                 /* If the transaction was intercepted by this rule we will
1809                  * go back. Do note that we are relying on the
1810                  * rule to know if it is a part of a chain and
1811                  * not intercept if it is.
1812                  */
1813                 if (msr->txcfg->debuglog_level >= 9) {
1814                     msr_log(msr, 9, "Match, intercepted -> returning.");
1815                 }
1816 
1817                 if(i-1 >= 0)
1818                     last_rule = rules[i-1];
1819                 else
1820                     last_rule = rules[0];
1821 
1822                 if((last_rule != NULL) && (last_rule->actionset != NULL) && last_rule->actionset->is_chained) {
1823 
1824                     int st = 0;
1825 
1826                     for(st=i;st>=0;st--)  {
1827 
1828                         rule_starter = rules[st];
1829 
1830                         if(rule_starter != NULL && rule_starter->chain_starter != NULL)    {
1831                             if((msr != NULL) && (msr->intercept_actionset != NULL) && (rule_starter->actionset != NULL))
1832                                 msr->intercept_actionset->intercept_uri = rule_starter->actionset->intercept_uri;
1833                             break;
1834                         }
1835                     }
1836 
1837                 }
1838 
1839                 apr_table_clear(msr->matched_vars);
1840                 return 1;
1841             }
1842 
1843             if (rule->actionset->skip_after != NULL) {
1844                 skip_after = rule->actionset->skip_after;
1845                 mode = SKIP_RULES;
1846                 saw_starter = 1;
1847 
1848                 if (msr->txcfg->debuglog_level >= 9) {
1849                     msr_log(msr, 9, "Skipping after rule %pp id=\"%s\" -> mode SKIP_RULES.", rule, skip_after);
1850                 }
1851 
1852                 continue;
1853             }
1854 
1855             if(skipped == 1)    {
1856                 mode = SKIP_RULES;
1857                 continue;
1858             }
1859 
1860             /* We had a match but the transaction was not
1861              * intercepted. In that case we proceed with the
1862              * next rule...
1863              */
1864             mode = NEXT_RULE;
1865             if (msr->txcfg->debuglog_level >= 9) {
1866                 msr_log(msr, 9, "Match -> mode NEXT_RULE.");
1867             }
1868 
1869             /* ...unless we need to skip, in which case we
1870              * determine how many rules/chains we need to
1871              * skip and configure the counter accordingly.
1872              */
1873             if (rule->actionset->is_chained == 0) {
1874                 apr_table_clear(msr->matched_vars);
1875                 if (rule->chain_starter != NULL) {
1876                     if (rule->chain_starter->actionset->skip_count > 0) {
1877                         skip = rule->chain_starter->actionset->skip_count;
1878                         if (msr->txcfg->debuglog_level >= 4) {
1879                             msr_log(msr, 4, "Skipping %d rules/chains (from a chain).", skip);
1880                         }
1881                     }
1882                 }
1883                 else if (rule->actionset->skip_count > 0) {
1884                     skip = rule->actionset->skip_count;
1885                     if (msr->txcfg->debuglog_level >= 4) {
1886                         msr_log(msr, 4, "Skipping %d rules/chains.", skip);
1887                     }
1888                 }
1889             }
1890         }
1891         else if (rc < 0) {
1892             const char *id = "";
1893             const char *msg = "";
1894             if (rule->actionset) {
1895                 if (rule->actionset->id) {
1896                     id = rule->actionset->id;
1897                 }
1898                 if (rule->actionset->msg) {
1899                     msg = rule->actionset->msg;
1900                 }
1901             }
1902             msr_log(msr, 1, "Rule processing failed (id=%s, msg=%s).", id, msg);
1903 
1904             if (msr->txcfg->reqintercept_oe == 1)   {
1905                 apr_table_clear(msr->matched_vars);
1906                 return -1;
1907             } else  {
1908                 if (rule->actionset && rule->actionset->is_chained) {
1909                     /* If the current rule is part of a chain then
1910                      * we need to skip over all the rules in the chain.
1911                      */
1912                     mode = NEXT_CHAIN;
1913                     if (msr->txcfg->debuglog_level >= 9) {
1914                         msr_log(msr, 9, "Ruled failed, chained -> mode NEXT_CHAIN.");
1915                     }
1916                 } else {
1917                     /* This rule is not part of a chain so we simply
1918                      * move to the next rule.
1919                      */
1920                     mode = NEXT_RULE;
1921                     if (msr->txcfg->debuglog_level >= 9) {
1922                         msr_log(msr, 9, "Rule failed, not chained -> mode NEXT_RULE.");
1923                     }
1924                 }
1925 
1926                 apr_table_clear(msr->matched_vars);
1927                 skipped = 0;
1928                 saw_starter = 0;
1929             }
1930         }
1931         else {
1932             const char *id = "";
1933             const char *msg = "";
1934             if (rule->actionset) {
1935                 if (rule->actionset->id) {
1936                     id = rule->actionset->id;
1937                 }
1938                 if (rule->actionset->msg) {
1939                     msg = rule->actionset->msg;
1940                 }
1941             }
1942             msr_log(msr, 1, "Rule processing failed with unknown return code: %d (id=%s, msg=%s).", rc, id, msg);
1943             apr_table_clear(msr->matched_vars);
1944             return -1;
1945         }
1946     }
1947 
1948     /* ENH warn if chained rules are missing. */
1949     apr_table_clear(msr->matched_vars);
1950     return 0;
1951 }
1952 
1953 /**
1954  * Creates a ruleset that will be handled by the default
1955  * implementation.
1956  */
1957 msre_ruleset *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp) {
1958     msre_ruleset *ruleset;
1959 
1960     ruleset = apr_pcalloc(mp, sizeof(msre_ruleset));
1961     if (ruleset == NULL) return NULL;
1962     ruleset->mp = mp;
1963     ruleset->engine = engine;
1964 
1965     ruleset->phase_request_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1966     ruleset->phase_request_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1967     ruleset->phase_response_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1968     ruleset->phase_response_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1969     ruleset->phase_logging = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1970 
1971     return ruleset;
1972 }
1973 
1974 /**
1975  * Adds one rule to the given phase of the ruleset.
1976  */
1977 int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) {
1978     apr_array_header_t *arr = NULL;
1979 
1980     switch (phase) {
1981         case PHASE_REQUEST_HEADERS :
1982             arr = ruleset->phase_request_headers;
1983             break;
1984         case PHASE_REQUEST_BODY :
1985             arr = ruleset->phase_request_body;
1986             break;
1987         case PHASE_RESPONSE_HEADERS :
1988             arr = ruleset->phase_response_headers;
1989             break;
1990         case PHASE_RESPONSE_BODY :
1991             arr = ruleset->phase_response_body;
1992             break;
1993         case PHASE_LOGGING :
1994             arr = ruleset->phase_logging;
1995             break;
1996         default :
1997             return -1;
1998     }
1999 
2000     /* ENH verify the rule's use of targets is consistent with
2001      * the phase it selected to run at.
2002      */
2003 
2004     msre_actionset_set_defaults(rule->actionset);
2005     rule->actionset->rule = rule;
2006 
2007     *(const msre_rule **)apr_array_push(arr) = rule;
2008 
2009     return 1;
2010 }
2011 
2012 static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id,
2013         const apr_array_header_t *phase_arr, int offset)
2014 {
2015     msre_rule **rules = (msre_rule **)phase_arr->elts;
2016     int i;
2017 
2018     for (i = 0; i < phase_arr->nelts; i++) {
2019         msre_rule *rule = (msre_rule *)rules[i];
2020 
2021         /* Rule with an action, not a sub-rule (chain) and a matching id */
2022         if (  (rule->actionset != NULL)
2023                 && (!rule->actionset->is_chained || !rule->chain_starter)
2024                 && (rule->actionset->id != NULL)
2025                 && (strcmp(rule->actionset->id, id) == 0))
2026         {
2027             /* Return rule that matched unless it is a placeholder */
2028             if(offset == 0) {
2029                 return (rule->placeholder == RULE_PH_NONE) ? rule : NULL;
2030             }
2031             else    {
2032                 if (i+offset < phase_arr->nelts)    {
2033                     msre_rule *rule_off = (msre_rule *)rules[i+offset];
2034                     return (rule_off->placeholder == RULE_PH_NONE) ? rule_off : NULL;
2035                 }
2036             }
2037         }
2038     }
2039 
2040     return NULL;
2041 }
2042 
2043 /**
2044  * Fetches rule from the ruleset all rules that match the given exception.
2045  */
2046 msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id, int offset) {
2047     msre_rule *rule = NULL;
2048 
2049     if (ruleset == NULL) return NULL;
2050 
2051     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers, offset);
2052     if (rule != NULL) return rule;
2053 
2054     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body, offset);
2055     if (rule != NULL) return rule;
2056 
2057     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers, offset);
2058     if (rule != NULL) return rule;
2059 
2060     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body, offset);
2061     if (rule != NULL) return rule;
2062 
2063     rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging, offset);
2064 
2065     return rule;
2066 }
2067 
2068 static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
2069         apr_array_header_t *phase_arr)
2070 {
2071     msre_rule **rules;
2072     int i, j, mode, removed_count;
2073 
2074     j = 0;
2075     mode = 0;
2076     removed_count = 0;
2077     rules = (msre_rule **)phase_arr->elts;
2078     for (i = 0; i < phase_arr->nelts; i++) {
2079         msre_rule *rule = (msre_rule *)rules[i];
2080 
2081         if (mode == 0) { /* Looking for next rule. */
2082             int remove_rule = 0;
2083 
2084             /* Only remove non-placeholder rules */
2085             if (rule->placeholder == RULE_PH_NONE) {
2086                 switch(re->type) {
2087                     case RULE_EXCEPTION_REMOVE_ID :
2088                         if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
2089                             int ruleid = atoi(rule->actionset->id);
2090 
2091                             if (rule_id_in_range(ruleid, re->param)) {
2092                                 remove_rule = 1;
2093                             }
2094                         }
2095 
2096                         break;
2097 
2098                     case RULE_EXCEPTION_REMOVE_MSG :
2099                         if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
2100                             char *my_error_msg = NULL;
2101 
2102                             int rc = msc_regexec(re->param_data,
2103                                     rule->actionset->msg, strlen(rule->actionset->msg),
2104                                     &my_error_msg);
2105                             if (rc >= 0) {
2106                                 remove_rule = 1;
2107                             }
2108                         }
2109 
2110                         break;
2111                     case RULE_EXCEPTION_REMOVE_TAG :
2112                         if ((rule->actionset != NULL)&&(apr_is_empty_table(rule->actionset->actions) == 0)) {
2113                             char *my_error_msg = NULL;
2114                             const apr_array_header_t *tarr = NULL;
2115                             const apr_table_entry_t *telts = NULL;
2116                             int act;
2117 
2118                             tarr = apr_table_elts(rule->actionset->actions);
2119                             telts = (const apr_table_entry_t*)tarr->elts;
2120 
2121                             for (act = 0; act < tarr->nelts; act++) {
2122                                 msre_action *action = (msre_action *)telts[act].val;
2123                                 if((action != NULL) && (action->metadata != NULL) && (strcmp("tag", action->metadata->name) == 0))  {
2124 
2125                                     int rc = msc_regexec(re->param_data,
2126                                             action->param, strlen(action->param),
2127                                             &my_error_msg);
2128                                     if (rc >= 0)    {
2129                                         remove_rule = 1;
2130                                     }
2131                                 }
2132                             }
2133                         }
2134                         break;
2135                 }
2136             }
2137 
2138             if (remove_rule) {
2139                 /* Do not increment j. */
2140                 removed_count++;
2141                 if (rule->actionset && rule->actionset->is_chained) mode = 2; /* Remove rules in this chain. */
2142             } else {
2143                 if (rule->actionset && rule->actionset->is_chained) mode = 1; /* Keep rules in this chain. */
2144                 rules[j++] = rules[i];
2145             }
2146         } else { /* Handling rule that is part of a chain. */
2147             if (mode == 2) { /* We want to remove the rule. */
2148                 /* Do not increment j. */
2149                 removed_count++;
2150             } else {
2151                 rules[j++] = rules[i];
2152             }
2153 
2154             if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
2155         }
2156     }
2157 
2158     /* Update the number of rules in the array. */
2159     phase_arr->nelts -= removed_count;
2160 
2161     return 0;
2162 }
2163 
2164 /**
2165  * Removes from the ruleset all rules that match the given exception.
2166  */
2167 int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re) {
2168     int count = 0;
2169 
2170     if (ruleset == NULL) return 0;
2171 
2172     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_headers);
2173     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body);
2174     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers);
2175     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body);
2176     count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_logging);
2177 
2178     return count;
2179 }
2180 
2181 
2182 /* -- Rule functions ------------------------------------------------------- */
2183 
2184 /**
2185  * Returns the name of the supplied severity level.
2186  */
2187 static const char *msre_format_severity(int severity) {
2188     if ((severity >= 0)&&(severity <= 7)) {
2189         return severities[severity];
2190     }
2191     else {
2192         return "(invalid value)";
2193     }
2194 }
2195 
2196 /**
2197  * Creates a string containing the metadata of the supplied rule.
2198  */
2199 char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) {
2200     const apr_array_header_t *tarr;
2201     const apr_table_entry_t *telts;
2202     char *id = "";
2203     char *rev = "";
2204     char *msg = "";
2205     char *logdata = "";
2206     char *severity = "";
2207     char *accuracy = "";
2208     char *maturity = "";
2209     char *version = "";
2210     char *tags = "";
2211     char *fn = "";
2212     int k;
2213 
2214     if (actionset == NULL) return "";
2215 
2216 #ifndef LOG_NO_FILENAME
2217     if ((actionset->rule != NULL) && (actionset->rule->filename != NULL)) {
2218         fn = apr_psprintf(msr->mp, " [file \"%s\"] [line \"%d\"]",
2219                 actionset->rule->filename, actionset->rule->line_num);
2220     }
2221 #endif
2222     if (actionset->id != NULL) {
2223         id = apr_psprintf(msr->mp, " [id \"%s\"]",
2224                 log_escape(msr->mp, actionset->id));
2225     }
2226     if (actionset->rev != NULL) {
2227         rev = apr_psprintf(msr->mp, " [rev \"%s\"]",
2228                 log_escape(msr->mp, actionset->rev));
2229     }
2230     if (actionset->msg != NULL) {
2231         /* Expand variables in the message string. */
2232         msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
2233         var->value = (char *)actionset->msg;
2234         var->value_len = strlen(actionset->msg);
2235         expand_macros(msr, var, NULL, msr->mp);
2236 
2237         msg = apr_psprintf(msr->mp, " [msg \"%s\"]",
2238                 log_escape_ex(msr->mp, var->value, var->value_len));
2239     }
2240     if (actionset->logdata != NULL) {
2241         /* Expand variables in the message string. */
2242         msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
2243         var->value = (char *)actionset->logdata;
2244         var->value_len = strlen(actionset->logdata);
2245         expand_macros(msr, var, NULL, msr->mp);
2246 
2247         logdata = apr_psprintf(msr->mp, " [data \"%s",
2248                 log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len));
2249         logdata = apr_pstrcat(msr->mp, logdata, "\"]", NULL);
2250 
2251         /* If it is > 512 bytes, then truncate at 512 with ellipsis.
2252          * NOTE: 512 actual data + 9 bytes of label = 521
2253          */
2254         if (strlen(logdata) > 521) {
2255             logdata[517] = '.';
2256             logdata[518] = '.';
2257             logdata[519] = '.';
2258             logdata[520] = '"';
2259             logdata[521] = ']';
2260             logdata[522] = '\0';
2261         }
2262     }
2263     if ((actionset->severity >= 0)&&(actionset->severity <= 7)) {
2264         severity = apr_psprintf(msr->mp, " [severity \"%s\"]",
2265                 msre_format_severity(actionset->severity));
2266     }
2267     if (actionset->version != NULL) {
2268         version = apr_psprintf(msr->mp, " [ver \"%s\"]",
2269                 log_escape(msr->mp, actionset->version));
2270     }
2271     if (actionset->maturity >= 0) {
2272         maturity = apr_psprintf(msr->mp, " [maturity \"%d\"]",
2273                 actionset->maturity);
2274     }
2275     if (actionset->accuracy >= 0) {
2276         accuracy = apr_psprintf(msr->mp, " [accuracy \"%d\"]",
2277                 actionset->accuracy);
2278     }
2279 
2280     /* Extract rule tags from the action list. */
2281     tarr = apr_table_elts(actionset->actions);
2282     telts = (const apr_table_entry_t*)tarr->elts;
2283 
2284     for (k = 0; k < tarr->nelts; k++) {
2285         msre_action *action = (msre_action *)telts[k].val;
2286         if (strcmp(telts[k].key, "tag") == 0) {
2287             /* Expand variables in the tag argument. */
2288             msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
2289 
2290             var->value = (char *)action->param;
2291             var->value_len = strlen(action->param);
2292             expand_macros(msr, var, NULL, msr->mp);
2293 
2294             tags = apr_psprintf(msr->mp, "%s [tag \"%s\"]", tags,
2295                log_escape(msr->mp, var->value));
2296         }
2297     }
2298 
2299     return apr_pstrcat(msr->mp, fn, id, rev, msg, logdata, severity, version, maturity, accuracy, tags, NULL);
2300 }
2301 
2302 char * msre_rule_generate_unparsed(apr_pool_t *pool,  const msre_rule *rule, const char *targets,
2303         const char *args, const char *actions)
2304 {
2305     char *unparsed = NULL;
2306     const char *r_targets = targets;
2307     const char *r_args = args;
2308     const char *r_actions = actions;
2309 
2310     if (r_targets == NULL) {
2311         r_targets = rule->p1;
2312     }
2313     if (r_args == NULL) {
2314         r_args = apr_pstrcat(pool, (rule->op_negated ? "!" : ""), "@", rule->op_name, " ", rule->op_param, NULL);
2315     }
2316     if (r_actions == NULL) {
2317         r_actions = msre_actionset_generate_action_string(pool, rule->actionset);
2318     }
2319 
2320     switch (rule->type) {
2321         case RULE_TYPE_NORMAL:
2322             if (r_actions == NULL) {
2323                 unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\"",
2324                         log_escape(pool, r_targets), log_escape(pool, r_args));
2325             }
2326             else {
2327                 unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\" \"%s\"",
2328                         log_escape(pool, r_targets), log_escape(pool, r_args),
2329                         log_escape(pool, r_actions));
2330             }
2331             break;
2332         case RULE_TYPE_ACTION:
2333             unparsed = apr_psprintf(pool, "SecAction \"%s\"",
2334                     log_escape(pool, r_actions));
2335             break;
2336         case RULE_TYPE_MARKER:
2337             unparsed = apr_psprintf(pool, "SecMarker \"%s\"", rule->actionset->id);
2338             break;
2339 #if defined(WITH_LUA)
2340         case RULE_TYPE_LUA:
2341             /* SecRuleScript */
2342             if (r_actions == NULL) {
2343                 unparsed = apr_psprintf(pool, "SecRuleScript \"%s\"", r_args);
2344             }
2345             else {
2346                 unparsed = apr_psprintf(pool, "SecRuleScript \"%s\" \"%s\"",
2347                         r_args, log_escape(pool, r_actions));
2348             }
2349             break;
2350 #endif
2351     }
2352 
2353     return unparsed;
2354 }
2355 
2356 /**
2357  * Assembles a new rule using the strings that contain a list
2358  * of targets (variables), arguments, and actions.
2359  */
2360 msre_rule *msre_rule_create(msre_ruleset *ruleset, int type,
2361         const char *fn, int line, const char *targets,
2362         const char *args, const char *actions, char **error_msg)
2363 {
2364     msre_rule *rule;
2365     char *my_error_msg;
2366     const char *argsp;
2367     int rc;
2368 
2369     if (error_msg == NULL) return NULL;
2370     *error_msg = NULL;
2371 
2372     rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
2373     if (rule == NULL) return NULL;
2374 
2375     rule->type = type;
2376     rule->ruleset = ruleset;
2377     rule->targets = apr_array_make(ruleset->mp, 10, sizeof(const msre_var *));
2378     rule->p1 = apr_pstrdup(ruleset->mp, targets);
2379     rule->filename = apr_pstrdup(ruleset->mp, fn);
2380     rule->line_num = line;
2381 
2382     /* Parse targets */
2383     rc = msre_parse_targets(ruleset, targets, rule->targets, &my_error_msg);
2384     if (rc < 0) {
2385         *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
2386         return NULL;
2387     }
2388 
2389     /* Parse args */
2390     argsp = args;
2391 
2392     /* Is negation used? */
2393     if (*argsp == '!') {
2394         rule->op_negated = 1;
2395         argsp++;
2396         while((isspace(*argsp))&&(*argsp != '\0')) argsp++;
2397     }
2398 
2399     /* Is the operator explicitly selected? */
2400     if (*argsp != '@') {
2401         /* Go with a regular expression. */
2402         rule->op_name = "rx";
2403         rule->op_param = argsp;
2404     } else  {
2405         /* Explicitly selected operator. */
2406         char *p = (char *)(argsp + 1);
2407         while((!isspace(*p))&&(*p != '\0')) p++;
2408         rule->op_name = apr_pstrmemdup(ruleset->mp, argsp + 1, p - (argsp + 1));
2409         while(isspace(*p)) p++; /* skip over the whitespace at the end*/
2410         rule->op_param = p; /* IMP1 So we always have a parameter even when it's empty? */
2411     }
2412 
2413     /* Find the operator. */
2414     rule->op_metadata = msre_engine_op_resolve(ruleset->engine, rule->op_name);
2415     if (rule->op_metadata == NULL) {
2416         *error_msg = apr_psprintf(ruleset->mp,
2417                 "Error creating rule: Failed to resolve operator: %s", rule->op_name);
2418         return NULL;
2419     }
2420 
2421     /* Initialise & validate parameter */
2422     if (rule->op_metadata->param_init != NULL) {
2423         if (rule->op_metadata->param_init(rule, &my_error_msg) <= 0) {
2424             *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
2425             return NULL;
2426         }
2427     }
2428 
2429     /* Parse actions */
2430     if (actions != NULL) {
2431         /* Create per-rule actionset */
2432         rule->actionset = msre_actionset_create(ruleset->engine, ruleset->mp, actions, &my_error_msg);
2433         if (rule->actionset == NULL) {
2434             *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
2435             return NULL;
2436         }
2437     }
2438 
2439     /* Add the unparsed rule */
2440     rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, targets, args, NULL);
2441 
2442     return rule;
2443 }
2444 
2445 #if defined(WITH_LUA)
2446 /**
2447  *
2448  */
2449 msre_rule *msre_rule_lua_create(msre_ruleset *ruleset,
2450         const char *fn, int line, const char *script_filename,
2451         const char *actions, char **error_msg)
2452 {
2453     msre_rule *rule;
2454     char *my_error_msg;
2455 
2456     if (error_msg == NULL) return NULL;
2457     *error_msg = NULL;
2458 
2459     rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
2460     if (rule == NULL) return NULL;
2461 
2462     rule->type = RULE_TYPE_LUA;
2463     rule->ruleset = ruleset;
2464     rule->filename = apr_pstrdup(ruleset->mp, fn);
2465     rule->line_num = line;
2466 
2467     /* Compile script. */
2468     *error_msg = lua_compile(&rule->script, script_filename, ruleset->mp);
2469     if (*error_msg != NULL) {
2470         return NULL;
2471     }
2472 
2473     /* Parse actions */
2474     if (actions != NULL) {
2475         /* Create per-rule actionset */
2476         rule->actionset = msre_actionset_create(ruleset->engine, ruleset->mp, actions, &my_error_msg);
2477         if (rule->actionset == NULL) {
2478             *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
2479             return NULL;
2480         }
2481     }
2482 
2483     /* Add the unparsed rule */
2484     rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, script_filename, NULL);
2485 
2486     return rule;
2487 }
2488 #endif
2489 
2490 /**
2491  * Perform non-disruptive actions associated with the provided actionset.
2492  */
2493 static void msre_perform_nondisruptive_actions(modsec_rec *msr, msre_rule *rule,
2494         msre_actionset *actionset, apr_pool_t *mptmp)
2495 {
2496     const apr_array_header_t *tarr;
2497     const apr_table_entry_t *telts;
2498     int i;
2499 
2500     tarr = apr_table_elts(actionset->actions);
2501     telts = (const apr_table_entry_t*)tarr->elts;
2502     for (i = 0; i < tarr->nelts; i++) {
2503         msre_action *action = (msre_action *)telts[i].val;
2504         if (action->metadata->type == ACTION_NON_DISRUPTIVE) {
2505             if (action->metadata->execute != NULL) {
2506                 action->metadata->execute(msr, mptmp, rule, action);
2507             }
2508         }
2509     }
2510 }
2511 
2512 /**
2513  * Perform the disruptive actions associated with the given actionset.
2514  */
2515 static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule,
2516         msre_actionset *actionset, apr_pool_t *mptmp, const char *message)
2517 {
2518     const apr_array_header_t *tarr;
2519     const apr_table_entry_t *telts;
2520     int i;
2521 
2522     /* Execute the disruptive actions. Do note that this does
2523      * not mean the request will be interrupted straight away. All
2524      * disruptive actions need to do here is update the information
2525      * that will be used to act later.
2526      */
2527     tarr = apr_table_elts(actionset->actions);
2528     telts = (const apr_table_entry_t*)tarr->elts;
2529     for (i = 0; i < tarr->nelts; i++) {
2530         msre_action *action = (msre_action *)telts[i].val;
2531         if (action->metadata->type == ACTION_DISRUPTIVE) {
2532             if (action->metadata->execute != NULL) {
2533                 action->metadata->execute(msr, mptmp, rule, action);
2534             }
2535         }
2536     }
2537     if (actionset->intercept_action_rec->metadata->type == ACTION_DISRUPTIVE) {
2538         if (actionset->intercept_action_rec->metadata->execute != NULL) {
2539             actionset->intercept_action_rec->metadata->execute(msr, mptmp, rule, actionset->intercept_action_rec);
2540         }
2541     }
2542 
2543     /* If "noauditlog" was used do not mark the transaction relevant. */
2544     if (actionset->auditlog != 0) {
2545         msr->is_relevant++;
2546     }
2547 
2548     /* We only do stuff when in ONLINE mode. In all other
2549      * cases we only emit warnings.
2550      */
2551     if ((msr->phase == PHASE_LOGGING)
2552             || (msr->txcfg->is_enabled == MODSEC_DETECTION_ONLY)
2553             || (msr->modsecurity->processing_mode == MODSEC_OFFLINE)
2554             || (actionset->intercept_action == ACTION_NONE))
2555     {
2556         int log_level;
2557 
2558         /* If "nolog" was used log at a higher level to prevent an "alert". */
2559         if (actionset->log == 0) {
2560             log_level = 4;
2561 
2562             /* But, if "auditlog" is enabled, then still add the message. */
2563             if (actionset->auditlog != 0) {
2564                 *(const char **)apr_array_push(msr->alerts) = msc_alert_message(msr, actionset, NULL, message);
2565             }
2566 
2567         }
2568         else {
2569             log_level = 2;
2570         }
2571 
2572         msc_alert(msr, log_level, actionset, "Warning.", message);
2573 
2574         /* However, this will mark the txn relevant again if it is <= 3,
2575          * which will mess up noauditlog.  We need to compensate for this
2576          * so that we do not increment twice when auditlog is enabled and
2577          * prevent incrementing when auditlog is disabled.
2578          */
2579         if (log_level <= 3) {
2580             msr->is_relevant--;
2581         }
2582 
2583         return;
2584     }
2585 
2586     /* Signal to the engine we need to intercept this
2587      * transaction, and rememer the rule that caused it.
2588      */
2589     msr->was_intercepted = 1;
2590     msr->rule_was_intercepted = 1;
2591     msr->intercept_phase = msr->phase;
2592     msr->intercept_actionset = actionset;
2593     msr->intercept_message = message;
2594 }
2595 
2596 /**
2597  * Invokes the rule operator against the given value.
2598  */
2599 static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr,
2600     msre_actionset *acting_actionset, apr_pool_t *mptmp)
2601 {
2602     apr_time_t time_before_op = 0;
2603     char *my_error_msg = NULL;
2604     const char *full_varname = NULL;
2605     const apr_array_header_t *tarr = NULL;
2606     const apr_table_entry_t *telts = NULL;
2607     rule_exception *re = NULL;
2608     char *exceptions = NULL;
2609     int rc, i;
2610 
2611     /* determine the full var name if not already resolved
2612      *
2613      * NOTE: this can happen if the var does not match but it is
2614      * being tested for non-existance as in:
2615      *   @REQUEST_HEADERS:Foo "@eq 0"
2616      *   @REQUEST_HEADERS:Foo "!@eq 1"
2617      */
2618     if ((var->param != NULL) && (var->name != NULL) && (strchr(var->name,':') == NULL)) {
2619         full_varname = apr_psprintf(mptmp, "%s%s:%s",
2620                                     (var->is_counting ? "&" : ""),
2621                                     var->name, var->param);
2622     }
2623     else if ((var->name != NULL) && var->is_counting && (*var->name != '&')) {
2624         full_varname = apr_pstrcat(mptmp, "&", var->name, NULL);
2625     }
2626     else {
2627         full_varname = var->name;
2628     }
2629 
2630     tarr = apr_table_elts(msr->removed_targets);
2631     telts = (const apr_table_entry_t*)tarr->elts;
2632 
2633     for (i = 0; i < tarr->nelts; i++) {
2634         exceptions = (char *)telts[i].key;
2635         re = (rule_exception *)telts[i].val;
2636 
2637         rc = msre_ruleset_rule_matches_exception(rule, re);
2638 
2639         if (rc > 0) {
2640             rc = fetch_target_exception(rule, msr, var, exceptions);
2641 
2642             if(rc > 0)  {
2643 
2644                 if (msr->txcfg->debuglog_level >= 4) {
2645                     msr_log(msr, 4, "Executing operator \"%s%s\" with param \"%s\" against %s skipped.",
2646                             (rule->op_negated ? "!" : ""), rule->op_name,
2647                             log_escape(msr->mp, rule->op_param), full_varname);
2648                 }
2649 
2650                 return RULE_NO_MATCH;
2651 
2652             }
2653         }
2654 
2655     }
2656 
2657     if (msr->txcfg->debuglog_level >= 4) {
2658         msr_log(msr, 4, "Executing operator \"%s%s\" with param \"%s\" against %s.",
2659                 (rule->op_negated ? "!" : ""), rule->op_name,
2660                 log_escape(msr->mp, rule->op_param), full_varname);
2661     }
2662 
2663     if (msr->txcfg->debuglog_level >= 9) {
2664         msr_log(msr, 9, "Target value: \"%s\"", log_escape_nq_ex(msr->mp, var->value,
2665                     var->value_len));
2666     }
2667 
2668 #if defined(PERFORMANCE_MEASUREMENT)
2669     time_before_op = apr_time_now();
2670 #else
2671     if (msr->txcfg->debuglog_level >= 4 || msr->txcfg->max_rule_time > 0) {
2672         time_before_op = apr_time_now();
2673     }
2674 #endif
2675 
2676     rc = rule->op_metadata->execute(msr, rule, var, &my_error_msg);
2677 
2678 #if defined(PERFORMANCE_MEASUREMENT)
2679     {
2680         /* Record performance but do not log anything. */
2681         apr_time_t t1 = apr_time_now();
2682         rule->op_time += (t1 - time_before_op);
2683     }
2684     #else
2685     if (msr->txcfg->debuglog_level >= 4) {
2686         apr_time_t t1 = apr_time_now();
2687         msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.", (t1 - time_before_op));
2688     }
2689 
2690     if(msr->txcfg->max_rule_time > 0)  {
2691         apr_time_t t1 = apr_time_now();
2692         apr_time_t rule_time = 0;
2693         const char *rt_time = NULL;
2694 
2695         if(rule->actionset->id != NULL) {
2696             rt_time = apr_table_get(msr->perf_rules, rule->actionset->id);
2697             if(rt_time == NULL) {
2698                 rt_time = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (t1 - time_before_op));
2699                 rule_time = (apr_time_t)atoi(rt_time);
2700                 if(rule_time >= msr->txcfg->max_rule_time)
2701                     apr_table_setn(msr->perf_rules, rule->actionset->id, rt_time);
2702             } else  {
2703                 rule_time = (apr_time_t)atoi(rt_time);
2704                 rule_time += (t1 - time_before_op);
2705                 if(rule_time >= msr->txcfg->max_rule_time)  {
2706                     rt_time = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, rule_time);
2707                     apr_table_setn(msr->perf_rules, rule->actionset->id, rt_time);
2708                 }
2709             }
2710         }
2711     }
2712 #endif
2713 
2714     if (rc < 0) {
2715         msr_log(msr, 4, "Operator error: %s", my_error_msg);
2716         return -1;
2717     }
2718 
2719     if (((rc == 0)&&(rule->op_negated == 0))||((rc == 1)&&(rule->op_negated == 1))) {
2720         /* No match, do nothing. */
2721         return RULE_NO_MATCH;
2722     }
2723     else {
2724         /* Match. */
2725         if (rc == 0) {
2726             char *op_param = log_escape(msr->mp, rule->op_param);
2727 
2728             /* Truncate op parameter. */
2729             if (strlen(op_param) > 252) {
2730                 op_param = apr_psprintf(msr->mp, "%.252s ...", op_param);
2731             }
2732 
2733             /* Operator did not match so we need to provide a message. */
2734             my_error_msg = apr_psprintf(msr->mp, "Match of \"%s %s\" against \"%s\" required.",
2735                 log_escape(msr->mp, rule->op_name), op_param,
2736                 log_escape(msr->mp, full_varname));
2737         }
2738 
2739         /* Save the rules that match */
2740         *(const msre_rule **)apr_array_push(msr->matched_rules) = rule;
2741 
2742         /* Save the last matched var data */
2743         if(var != NULL && msr != NULL)   {
2744             msc_string *mvar = NULL;
2745 
2746             msr->matched_var->name = apr_pstrdup(msr->mp, var->name);
2747             msr->matched_var->name_len = strlen(msr->matched_var->name);
2748             msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len);
2749             msr->matched_var->value_len = var->value_len;
2750 
2751             mvar = apr_palloc(msr->mp, sizeof(msc_string));
2752             mvar->name = apr_pstrdup(msr->mp, var->name);
2753             mvar->name_len = strlen(mvar->name);
2754             mvar->value = apr_pmemdup(msr->mp, var->value, var->value_len);
2755             mvar->value_len = var->value_len;
2756 
2757             apr_table_addn(msr->matched_vars, mvar->name, (void *)mvar);
2758 
2759         }
2760 
2761         /* Keep track of the highest severity matched so far */
2762         if ((acting_actionset->severity > 0) && (acting_actionset->severity < msr->highest_severity)
2763             && !rule->actionset->is_chained)   {
2764             msr->highest_severity = acting_actionset->severity;
2765         }
2766 
2767 
2768         /* Perform non-disruptive actions. */
2769         msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp);
2770 
2771         /* Perform disruptive actions, but only if
2772          * this rule is not part of a chain.
2773          */
2774         if (rule->actionset->is_chained == 0) {
2775             msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg);
2776         }
2777 
2778         return RULE_MATCH;
2779     }
2780 }
2781 
2782 /**
2783  * Executes rule against the given transaction.
2784  */
2785 static apr_status_t msre_rule_process_normal(msre_rule *rule, modsec_rec *msr) {
2786     const apr_array_header_t *arr = NULL;
2787     const apr_table_entry_t *te = NULL;
2788     msre_actionset *acting_actionset = NULL;
2789     msre_var **targets = NULL;
2790     apr_pool_t *mptmp = msr->msc_rule_mptmp;
2791     apr_table_t *tartab = NULL;
2792     apr_table_t *vartab = NULL;
2793     int i, rc = 0, match_count = 0;
2794     int invocations = 0;
2795     int multi_match = 0;
2796 
2797     /* Choose the correct metadata/disruptive action actionset. */
2798     acting_actionset = rule->actionset;
2799     if (rule->chain_starter != NULL) {
2800         acting_actionset = rule->chain_starter->actionset;
2801     }
2802 
2803     /* Configure recursive matching. */
2804     if (apr_table_get(rule->actionset->actions, "multiMatch") != NULL) {
2805         multi_match = 1;
2806     }
2807 
2808     /* ENH: What is a good initial size? */
2809     tartab = apr_table_make(mptmp, 24);
2810     if (tartab == NULL) return -1;
2811     vartab = apr_table_make(mptmp, 24);
2812     if (vartab == NULL) return -1;
2813 
2814     /* Expand variables to create a list of targets. */
2815 
2816     targets = (msre_var **)rule->targets->elts;
2817     for (i = 0; i < rule->targets->nelts; i++) {
2818         int j, list_count;
2819 
2820         apr_table_clear(vartab);
2821 
2822         /* ENH Introduce a new variable hook that would allow the code
2823          *     behind the variable to return the size of the collection
2824          *     without having to generate the variables.
2825          */
2826 
2827         /* Expand individual variables first. */
2828         list_count = targets[i]->metadata->generate(msr, targets[i], rule, vartab, mptmp);
2829 
2830         if (targets[i]->is_counting) {
2831             /* Count how many there are and just add the score to the target list. */
2832             msre_var *newvar = (msre_var *)apr_pmemdup(mptmp, targets[i], sizeof(msre_var));
2833             newvar->value = apr_psprintf(mptmp, "%d", list_count);
2834             newvar->value_len = strlen(newvar->value);
2835             apr_table_addn(tartab, newvar->name, (void *)newvar);
2836         } else {
2837             /* And either add them or remove from the final target list. */
2838             arr = apr_table_elts(vartab);
2839             te = (apr_table_entry_t *)arr->elts;
2840             for(j = 0; j < arr->nelts; j++) {
2841                 if (targets[i]->is_negated == 0) {
2842                     apr_table_addn(tartab, te[j].key, te[j].val);
2843                 } else {
2844                     apr_table_unset(tartab, te[j].key);
2845                 }
2846             }
2847         }
2848     }
2849 
2850     /* Log the target variable expansion */
2851     if (msr->txcfg->debuglog_level >= 4) {
2852         const char *expnames = NULL;
2853 
2854         arr = apr_table_elts(tartab);
2855         if (arr->nelts > 1) {
2856             te = (apr_table_entry_t *)arr->elts;
2857             expnames = apr_pstrdup(mptmp, ((msre_var *)te[0].val)->name);
2858             for(i = 1; i < arr->nelts; i++) {
2859                 expnames = apr_psprintf(mptmp, "%s|%s", expnames, ((msre_var *)te[i].val)->name);
2860             }
2861             if (strcmp(rule->p1, expnames) != 0) {
2862                 msr_log(msr, 4, "Expanded \"%s\" to \"%s\".", rule->p1, expnames);
2863             }
2864         }
2865     }
2866 
2867     /* Loop through targets on the final target list,
2868      * perform transformations as necessary, and invoke
2869      * the operator.
2870      */
2871 
2872     arr = apr_table_elts(tartab);
2873     te = (apr_table_entry_t *)arr->elts;
2874     for (i = 0; i < arr->nelts; i++) {
2875         /* Variable was modified by *any* transformation */
2876         int changed;
2877         /* Variable was modified by *last applied* transformation (needed by multimatch) */
2878         int tfnchanged;
2879         int usecache = 0;
2880         apr_table_t *cachetab = NULL;
2881         apr_time_t time_before_trans = 0;
2882         msre_var *var;
2883 
2884         /* Take one target. */
2885         var = (msre_var *)te[i].val;
2886 
2887         /* Is this var cacheable? */
2888         if (msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) {
2889             usecache = 1;
2890 
2891             /* Counting vars are not cacheable due to them being created
2892              * in a local per-rule pool.
2893              */
2894             if (var->is_counting) {
2895                 if (msr->txcfg->debuglog_level >= 9) {
2896                     msr_log(msr, 9, "CACHE: Disabled - &%s is dynamic", var->name);
2897                 }
2898 
2899                 usecache = 0;
2900             }
2901             /* Only cache if if the variable is available in this phase */
2902             else if (msr->phase < var->metadata->availability) {
2903                 if (msr->txcfg->debuglog_level >= 9) {
2904                     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);
2905                 }
2906 
2907                 usecache = 0;
2908             }
2909             /* check the cache options */
2910             else if (var->value_len < msr->txcfg->cache_trans_min) {
2911                 if (msr->txcfg->debuglog_level >= 9) {
2912                     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);
2913                 }
2914 
2915                 usecache = 0;
2916             }
2917             else if ((msr->txcfg->cache_trans_max != 0) && (var->value_len > msr->txcfg->cache_trans_max)) {
2918                 if (msr->txcfg->debuglog_level >= 9) {
2919                     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);
2920                 }
2921 
2922                 usecache = 0;
2923             }
2924 
2925             /* if cache is still enabled, check the VAR for cacheablity */
2926             if (usecache) {
2927                 if (var->metadata->is_cacheable == VAR_CACHE) {
2928                     if (msr->txcfg->debuglog_level >= 9) {
2929                         msr_log(msr, 9, "CACHE: Enabled");
2930                     }
2931 
2932                     #ifdef CACHE_DEBUG
2933                     msr_log(msr, 9, "CACHE: Fetching cache entry from hash=%pp: %pp=%s", msr->tcache, var, var->name);
2934                     #endif
2935 
2936                     /* Fetch cache table for this target */
2937                     cachetab = (apr_table_t *)apr_hash_get(msr->tcache, var->value, sizeof(var->value));
2938 
2939                     /* Create an empty cache table if this is the first time */
2940                     #ifdef CACHE_DEBUG
2941                     if (cachetab) {
2942                         msr_log(msr, 9, "CACHE: Using cache table %pp", cachetab);
2943                     }
2944                     else
2945                     #else
2946                     if (cachetab == NULL)
2947                     #endif
2948                     {
2949                         /* NOTE: We use the pointer to the var value as a hash
2950                          *       key as it is unique. This pointer *must*
2951                          *       remain valid through the entire phase. If
2952                          *       it does not, then we will not receive a cache
2953                          *       hit and just wasted RAM. So, it is important
2954                          *       that any such vars be marked as VAR_DONT_CACHE.
2955                          *
2956                          * ENH: Only use pointer for non-scalar vars
2957                          */
2958                         cachetab = apr_table_make(msr->mp, 3);
2959                         apr_hash_set(msr->tcache, var->value, sizeof(var->value), cachetab);
2960 
2961                         #ifdef CACHE_DEBUG
2962                         msr_log(msr, 9, "CACHE: Created a new cache table %pp for %pp", cachetab, var->value);
2963                         #endif
2964                     }
2965 
2966                 }
2967                 else {
2968                     usecache = 0;
2969 
2970                     if (msr->txcfg->debuglog_level >= 9) {
2971                         msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name);
2972                     }
2973                 }
2974             }
2975         }
2976 
2977         #if defined(PERFORMANCE_MEASUREMENT)
2978         time_before_trans = apr_time_now();
2979         #else
2980         if (msr->txcfg->debuglog_level >= 4) {
2981             time_before_trans = apr_time_now();
2982         }
2983         #endif
2984 
2985         /* Transform target. */
2986         {
2987             const apr_array_header_t *tarr;
2988             const apr_table_entry_t *telts;
2989             const char *tfnspath = NULL;
2990             char *tfnskey = NULL;
2991             int tfnscount = 0;
2992             int last_cached_tfn = 0;
2993             msre_cache_rec *crec = NULL;
2994             msre_cache_rec *last_crec = NULL;
2995             int k;
2996             msre_action *action;
2997             msre_tfn_metadata *metadata;
2998             apr_table_t *normtab;
2999             const char *lastvarval = NULL;
3000             apr_size_t lastvarlen = 0;
3001 
3002             tfnchanged = 0;
3003             changed = 0;
3004             normtab = apr_table_make(mptmp, 10);
3005             if (normtab == NULL) return -1;
3006             tarr = apr_table_elts(rule->actionset->actions);
3007             telts = (const apr_table_entry_t*)tarr->elts;
3008 
3009             /* Build the final list of transformation functions. */
3010             for (k = 0; k < tarr->nelts; k++) {
3011                 action = (msre_action *)telts[k].val;
3012 
3013                 if (strcmp(telts[k].key, "t") == 0) {
3014                     if (strcmp(action->param, "none") == 0) {
3015                         apr_table_clear(normtab);
3016                         tfnspath = NULL;
3017                         tfnskey = NULL;
3018                         tfnscount = 0;
3019                         last_crec = NULL;
3020                         last_cached_tfn = 0;
3021                         continue;
3022                     }
3023 
3024                     if (action->param_plusminus == NEGATIVE_VALUE) {
3025                         apr_table_unset(normtab, action->param);
3026                     }
3027                     else {
3028                         tfnscount++;
3029 
3030                         apr_table_addn(normtab, action->param, (void *)action);
3031 
3032                         /* Check the cache, saving the 'most complete' as a
3033                          * starting point
3034                          */
3035                         if (usecache) {
3036                             tfnspath = apr_psprintf(mptmp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param);
3037                             tfnskey = apr_psprintf(mptmp, "%x;%s", tfnscount, tfnspath);
3038                             crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey);
3039 
3040                             #ifdef CACHE_DEBUG
3041                             msr_log(msr, 9, "CACHE: %s %s cached=%d", var->name, tfnskey, (crec ? 1 : 0));
3042                             #endif
3043 
3044                             if (crec != NULL) {
3045                                 last_crec = crec;
3046                                 last_cached_tfn = tfnscount;
3047                             }
3048                         }
3049                     }
3050                 }
3051             }
3052 
3053             /* If the last cached tfn is the last in the list
3054              * then we can stop here and just execute the action immediatly
3055              */
3056             if (usecache && !multi_match &&
3057                 (crec != NULL) && (crec == last_crec))
3058             {
3059                 crec->hits++;
3060 
3061                 if (crec->changed) {
3062                     var->value = apr_pmemdup(mptmp, crec->val, crec->val_len);
3063                     var->value_len = crec->val_len;
3064                 }
3065 
3066                 if (msr->txcfg->debuglog_level >= 9) {
3067                     msr_log(msr, 9, "T (%d) %s: \"%s\" [fully cached hits=%d]", crec->changed, crec->path,
3068                         log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits);
3069                 }
3070 
3071                 #if defined(PERFORMANCE_MEASUREMENT)
3072                 {
3073                     apr_time_t t1 = apr_time_now();
3074                     rule->trans_time += (t1 - time_before_trans);
3075                 }
3076                 #else
3077                 if (msr->txcfg->debuglog_level >= 4) {
3078                     apr_time_t t1 = apr_time_now();
3079 
3080                     msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
3081                         (t1 - time_before_trans));
3082                 }
3083                 #endif
3084 
3085                 rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
3086 
3087                 if (rc < 0) {
3088                     return -1;
3089                 }
3090 
3091                 if (rc == RULE_MATCH) {
3092                     match_count++;
3093 
3094                     /* Return straight away if the transaction
3095                      * was intercepted - no need to process the remaining
3096                      * targets.
3097                      */
3098                     if (msr->rule_was_intercepted) {
3099                         return RULE_MATCH;
3100                     }
3101                 }
3102 
3103                 continue; /* next target */
3104             }
3105 
3106 
3107             /* Perform transformations. */
3108 
3109             tarr = apr_table_elts(normtab);
3110 
3111             /* Execute transformations in a loop. */
3112 
3113             /* Start after the last known cached transformation if we can */
3114             if (!multi_match && (last_crec != NULL)) {
3115                 k = last_cached_tfn;
3116                 tfnspath = last_crec->path;
3117                 last_crec->hits++;
3118 
3119                 if ((changed = last_crec->changed) > 0) {
3120                     var->value = last_crec->val;
3121                     var->value_len = last_crec->val_len;
3122                 }
3123 
3124                 if (msr->txcfg->debuglog_level >= 9) {
3125                     msr_log(msr, 9, "T (%d) %s: \"%s\" [partially cached hits=%d]", last_crec->changed,
3126                         tfnspath, log_escape_nq_ex(mptmp, var->value, var->value_len), last_crec->hits);
3127                 }
3128             }
3129             else {
3130                 tfnspath = NULL;
3131                 k = 0;
3132             }
3133 
3134             /* Make a copy of the value so that we can change it in-place. */
3135             if (tarr->nelts) {
3136                 var->value = apr_pstrmemdup(mptmp, var->value, var->value_len);
3137                 /* var->value_len remains the same */
3138             }
3139 
3140             telts = (const apr_table_entry_t*)tarr->elts;
3141             for (; k < tarr->nelts; k++) {
3142                 char *rval = NULL;
3143                 long int rval_length = -1;
3144 
3145                 /* In multi-match mode we execute the operator
3146                  * once at the beginning and then once every
3147                  * time the variable is changed by the transformation
3148                  * function.
3149                  */
3150                 if (multi_match && (k == 0 || tfnchanged)) {
3151                     invocations++;
3152 
3153                     #if defined(PERFORMANCE_MEASUREMENT)
3154                     {
3155                         apr_time_t t1 = apr_time_now();
3156                         rule->trans_time += (t1 - time_before_trans);
3157                     }
3158                     #else
3159                     if (msr->txcfg->debuglog_level >= 4) {
3160                         apr_time_t t1 = apr_time_now();
3161 
3162                         msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
3163                             (t1 - time_before_trans));
3164                     }
3165                     #endif
3166 
3167                     rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
3168 
3169                     if (rc < 0) {
3170                         return -1;
3171                     }
3172 
3173                     if (rc == RULE_MATCH) {
3174                         match_count++;
3175 
3176                         /* Return straight away if the transaction
3177                         * was intercepted - no need to process the remaining
3178                         * targets.
3179                         */
3180                         if (msr->rule_was_intercepted) {
3181                             return RULE_MATCH;
3182                         }
3183                     }
3184                 }
3185 
3186                 /* Perform one transformation. */
3187                 action = (msre_action *)telts[k].val;
3188                 metadata = (msre_tfn_metadata *)action->param_data;
3189                 tfnchanged = metadata->execute(mptmp,
3190                     (unsigned char *)var->value, var->value_len,
3191                     &rval, &rval_length);
3192 
3193                 if (tfnchanged < 0) {
3194                     return -1;
3195                 }
3196 
3197                 if (tfnchanged) {
3198                     changed++;
3199                 }
3200 
3201                 /* Use the new values */
3202                 var->value = rval;
3203                 var->value_len = rval_length;
3204 
3205                 /* Cache the transformation */
3206                 if (usecache) {
3207                     int tfnsnum = k + 1;
3208 
3209                     /* Generate the cache key */
3210                     tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath ? tfnspath : ""),
3211                         (tfnspath ? "," : ""), action->param);
3212                     tfnskey = apr_psprintf(msr->mp, "%x;%s", tfnsnum, tfnspath);
3213 
3214                     if ((msr->txcfg->cache_trans_maxitems != 0) &&
3215                         (msr->tcache_items >= msr->txcfg->cache_trans_maxitems))
3216                     {
3217                         /* Warn only once if we attempt to go over the cache limit. */
3218                         if (msr->tcache_items == msr->txcfg->cache_trans_maxitems) {
3219                             msr->tcache_items++;
3220                             msr_log(msr, 4, "CACHE: Disabled - phase=%d"
3221                                             " maxitems=%" APR_SIZE_T_FMT
3222                                             " limit reached.",
3223                                             msr->phase,
3224                                             msr->txcfg->cache_trans_maxitems);
3225                         }
3226                     }
3227                     else if (msr->txcfg->cache_trans_incremental ||
3228                         (tfnsnum == tarr->nelts))
3229                     {
3230                         /* ENH1: Add flag to vars to tell which ones can change across phases store the rest in a global cache */
3231                         crec = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec));
3232                         if (crec == NULL) return -1;
3233 
3234                         crec->hits = 0;
3235                         crec->changed = changed;
3236                         crec->num = k + 1;
3237                         crec->path = tfnspath;
3238 
3239                         /* We want to cache a copy if it changed otherwise
3240                          * we just want to use a pointer to the last changed value.
3241                          */
3242                         crec->val = (!lastvarval || tfnchanged) ? apr_pmemdup(msr->mp, var->value, var->value_len) : lastvarval;
3243                         crec->val_len = changed ? ((!lastvarval || tfnchanged) ? var->value_len : lastvarlen) : 0;
3244 
3245                         /* Keep track of the last changed var value */
3246                         if (tfnchanged) {
3247                             lastvarval = crec->val;
3248                             lastvarlen = crec->val_len;
3249                         }
3250 
3251                         #ifdef CACHE_DEBUG
3252                         if (changed) {
3253                             msr_log(msr, 9, "CACHE: Caching %s=\"%s\" (%pp)",
3254                                             tfnskey,
3255                                             log_escape_nq_ex(mptmp,
3256                                                              crec->val,
3257                                                              crec->val_len),
3258                                             var);
3259                         }
3260                         else {
3261                             msr_log(msr, 9, "CACHE: Caching %s=<no change> (%pp)",
3262                                             tfnskey,
3263                                             var);
3264                         }
3265                         #endif
3266 
3267                         msr->tcache_items++;
3268 
3269                         apr_table_setn(cachetab, tfnskey, (void *)crec);
3270                     }
3271                 }
3272 
3273                 if (msr->txcfg->debuglog_level >= 9) {
3274                     msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, metadata->name,
3275                         log_escape_nq_ex(mptmp, var->value, var->value_len));
3276                 }
3277             }
3278         }
3279 
3280         /* Execute operator if multi-matching is not enabled,
3281          * or if it is and we need to process the result of the
3282          * last transformation.
3283          */
3284         if (!multi_match || tfnchanged) {
3285             invocations++;
3286 
3287             #if defined(PERFORMANCE_MEASUREMENT)
3288             {
3289                 apr_time_t t1 = apr_time_now();
3290                 rule->trans_time += (t1 - time_before_trans);
3291             }
3292             #else
3293             if (msr->txcfg->debuglog_level >= 4) {
3294                 apr_time_t t1 = apr_time_now();
3295 
3296                 msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
3297                     (t1 - time_before_trans));
3298             }
3299             #endif
3300 
3301             rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
3302 
3303             if (rc < 0) {
3304                 return -1;
3305             }
3306 
3307             if (rc == RULE_MATCH) {
3308                 match_count++;
3309 
3310                 /* Return straight away if the transaction
3311                  * was intercepted - no need to process the remaining
3312                  * targets.
3313                  */
3314                 if (msr->rule_was_intercepted) {
3315                     return RULE_MATCH;
3316                 }
3317             }
3318         }
3319     }
3320 
3321 
3322     return (match_count ? RULE_MATCH : RULE_NO_MATCH);
3323 }
3324 
3325 #if defined(WITH_LUA)
3326 /**
3327  *
3328  */
3329 static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
3330     msre_actionset *acting_actionset = NULL;
3331     char *my_error_msg = NULL;
3332     int rc;
3333 
3334     /* Choose the correct metadata/disruptive action actionset. */
3335     acting_actionset = rule->actionset;
3336     if (rule->chain_starter != NULL) {
3337         acting_actionset = rule->chain_starter->actionset;
3338     }
3339 
3340     rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg);
3341     if (rc < 0) {
3342         msr_log(msr, 1, "%s", my_error_msg);
3343         return -1;
3344     }
3345 
3346     /* A non-NULL error message means the rule matched. */
3347     if (my_error_msg != NULL) {
3348         /* Perform non-disruptive actions. */
3349         msre_perform_nondisruptive_actions(msr, rule, rule->actionset, msr->msc_rule_mptmp);
3350 
3351         /* Perform disruptive actions, but only if
3352          * this rule is not part of a chain.
3353          */
3354         if (rule->actionset->is_chained == 0) {
3355             msre_perform_disruptive_actions(msr, rule, acting_actionset, msr->msc_rule_mptmp, my_error_msg);
3356         }
3357     }
3358 
3359     return rc;
3360 }
3361 #endif
3362 
3363 /**
3364  *
3365  */
3366 static apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) {
3367     /* Use a fresh memory sub-pool for processing each rule */
3368     if (msr->msc_rule_mptmp == NULL) {
3369         if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) {
3370             return -1;
3371         }
3372     } else {
3373         apr_pool_clear(msr->msc_rule_mptmp);
3374     }
3375 
3376     #if defined(WITH_LUA)
3377     if (rule->type == RULE_TYPE_LUA) {
3378         return msre_rule_process_lua(rule, msr);
3379     }
3380     #endif
3381 
3382     return msre_rule_process_normal(rule, msr);
3383 }
3384 
3385 /**
3386  * Checks whether the given rule ID is in the given range.
3387  */
3388 int rule_id_in_range(int ruleid, const char *range) {
3389     char *p = NULL, *saveptr = NULL;
3390     char *data = NULL;
3391 
3392     if (range == NULL) return 0;
3393     data = strdup(range);
3394     if (data == NULL) return 0;
3395 
3396     p = apr_strtok(data, ",", &saveptr);
3397     while(p != NULL) {
3398         char *s = strstr(p, "-");
3399         if (s == NULL) {
3400             if (ruleid == atoi(p)) {
3401                 free(data);
3402                 return 1;
3403             }
3404         } else {
3405             int start = atoi(p);
3406             int end = atoi(s + 1);
3407             if ((ruleid >= start)&&(ruleid <= end)) {
3408                 free(data);
3409                 return 1;
3410             }
3411         }
3412         p = apr_strtok(NULL, ",", &saveptr);
3413     }
3414 
3415     free(data);
3416 
3417     return 0;
3418 }
3419