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