Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / re_operators.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
5  * This product is released under the terms of the General Public Licence,
6  * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7  * distribution) which contains the complete text of the licence.
8  *
9  * There are special exceptions to the terms and conditions of the GPL
10  * as it is applied to this software. View the full text of the exception in
11  * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
12  * distribution.
13  *
14  * If any of the files related to licensing are missing or if you have any
15  * other questions related to licensing please contact Breach Security, Inc.
16  * directly using the email address support@breach.com.
17  *
18  */
19 #include "re.h"
20 #include "msc_pcre.h"
21 #include "msc_geo.h"
22 #include "apr_lib.h"
23 #include "apr_strmatch.h"
24 #include "acmp.h"
25
26 /**
27  *
28  */
29 void msre_engine_op_register(msre_engine *engine, const char *name,
30     fn_op_param_init_t fn1, fn_op_execute_t fn2)
31 {
32     msre_op_metadata *metadata = (msre_op_metadata *)apr_pcalloc(engine->mp,
33         sizeof(msre_op_metadata));
34     if (metadata == NULL) return;
35
36     metadata->name = name;
37     metadata->param_init = fn1;
38     metadata->execute = fn2;
39     apr_table_setn(engine->operators, name, (void *)metadata);
40 }
41
42 /**
43  *
44  */
45 msre_op_metadata *msre_engine_op_resolve(msre_engine *engine, const char *name) {
46     return (msre_op_metadata *)apr_table_get(engine->operators, name);
47 }
48
49
50
51 /* -- Operators -- */
52
53 /* unconditionalMatch */
54
55 static int msre_op_unconditionalmatch_execute(modsec_rec *msr, msre_rule *rule,
56     msre_var *var, char **error_msg)
57 {
58     *error_msg = "Unconditional match in SecAction.";
59
60     /* Always match. */
61     return 1;
62 }
63
64 /* noMatch */
65
66 static int msre_op_nomatch_execute(modsec_rec *msr, msre_rule *rule,
67     msre_var *var, char **error_msg)
68 {
69     *error_msg = "No match.";
70
71     /* Never match. */
72     return 0;
73 }
74
75 /* rx */
76
77 static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) {
78     const char *errptr = NULL;
79     int erroffset;
80     msc_regex_t *regex;
81     const char *pattern = rule->op_param;
82
83     if (error_msg == NULL) return -1;
84     *error_msg = NULL;
85
86     /* Compile pattern */
87     regex = msc_pregcomp(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset);
88     if (regex == NULL) {
89         *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
90             erroffset, errptr);
91         return 0;
92     }
93
94     rule->op_param_data = regex;
95
96     return 1; /* OK */
97 }
98
99 static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
100     msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
101     const char *target;
102     unsigned int target_length;
103     char *my_error_msg = NULL;
104     int ovector[33];
105     int capture = 0;
106     int rc;
107
108     if (error_msg == NULL) return -1;
109     *error_msg = NULL;
110
111     if (regex == NULL) {
112         *error_msg = "Internal Error: regex data is null.";
113         return -1;
114     }
115
116     /* If the given target is null run against an empty
117      * string. This is a behaviour consistent with previous
118      * releases.
119      */
120     if (var->value == NULL) {
121         target = "";
122         target_length = 0;
123     } else {
124         target = var->value;
125         target_length = var->value_len;
126     }
127
128     /* Are we supposed to capture subexpressions? */
129     capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
130
131     /* Show when the regex captures but "capture" is not set */
132     if (msr->txcfg->debuglog_level >= 6) {
133         int capcount = 0;
134         rc = msc_fullinfo(regex, PCRE_INFO_CAPTURECOUNT, &capcount);
135         if ((capture == 0) && (capcount > 0)) {
136             msr_log(msr, 6, "Ignoring regex captures since \"capture\" action is not enabled.");
137         }
138     }
139
140     /* We always use capture so that ovector can be used as working space
141      * and no memory has to be allocated for any backreferences.
142      */
143     rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg);
144     if (rc < -1) {
145         *error_msg = apr_psprintf(msr->mp, "Regex execution failed: %s", my_error_msg);
146         return -1;
147     }
148
149     /* Handle captured subexpressions. */
150     if (capture && rc > 0) {
151         int i;
152
153         /* Use the available captures. */
154         for(i = 0; i < rc; i++) {
155             msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
156             if (s == NULL) return -1;
157             s->name = apr_psprintf(msr->mp, "%d", i);
158             s->value = apr_pstrmemdup(msr->mp,
159                 target + ovector[2*i], ovector[2*i + 1] - ovector[2*i]);
160             s->value_len = (ovector[2*i + 1] - ovector[2*i]);
161             if ((s->name == NULL)||(s->value == NULL)) return -1;
162             apr_table_setn(msr->tx_vars, s->name, (void *)s);
163             if (msr->txcfg->debuglog_level >= 9) {
164                 msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
165                     log_escape_nq_ex(msr->mp, s->value, s->value_len));
166             }
167         }
168
169         /* Unset the remaining ones (from previous invocations). */
170         for(i = rc; i <= 9; i++) {
171             char buf[24];
172             apr_snprintf(buf, sizeof(buf), "%d", i);
173             apr_table_unset(msr->tx_vars, buf);
174         }
175     }
176
177     if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
178         /* We no longer escape the pattern here as it is done when logging */
179         char *pattern = apr_pstrdup(msr->mp, regex->pattern);
180
181         /* This message will be logged. */
182         if (strlen(pattern) > 252) {
183             *error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.",
184                 pattern, var->name);
185         } else {
186             *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
187                 pattern, var->name);
188         }
189
190         return 1;
191     }
192
193     /* No match. */
194     return 0;
195 }
196
197 /* pm */
198
199 static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) {
200     ACMP *p;
201     const char *phrase;
202     const char *next;
203
204     if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
205         *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'.");
206         return 0; /* ERROR */
207     }
208
209     p = acmp_create(0, rule->ruleset->mp);
210     if (p == NULL) return 0;
211
212     phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param);
213
214     /* Loop through phrases */
215     /* ENH: Need to allow quoted phrases w/space */
216     for (;;) {
217         while((apr_isspace(*phrase) != 0) && (*phrase != '\0')) phrase++;
218         if (*phrase == '\0') break;
219         next = phrase;
220         while((apr_isspace(*next) == 0) && (*next != 0)) next++;
221         acmp_add_pattern(p, phrase, NULL, NULL, next - phrase);
222         phrase = next;
223     }
224     acmp_prepare(p);
225     rule->op_param_data = p;
226     return 1;
227 }
228
229 /* pmFromFile */
230
231 static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) {
232     char errstr[1024];
233     char buf[HUGE_STRING_LEN + 1];
234     char *fn;
235     char *next;
236     char *ptr;
237     const char *rulefile_path;
238     apr_status_t rc;
239     apr_file_t *fd;
240     ACMP *p;
241
242     if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
243         *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pmFromFile'.");
244         return 0; /* ERROR */
245     }
246
247     p = acmp_create(0, rule->ruleset->mp);
248     if (p == NULL) return 0;
249
250     fn = apr_pstrdup(rule->ruleset->mp, rule->op_param);
251
252     /* Get the path of the rule filename to use as a base */
253     rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename)));
254
255     #ifdef DEBUG_CONF
256     fprintf(stderr, "Rulefile path: \"%s\"\n", rulefile_path);
257     #endif
258
259     /* Loop through filenames */
260     /* ENH: Need to allow quoted filenames w/space */
261     for (;;) {
262         const char *rootpath = NULL;
263         const char *filepath = NULL;
264         int line = 0;
265
266         /* Trim whitespace */
267         while((apr_isspace(*fn) != 0) && (*fn != '\0')) fn++;
268         if (*fn == '\0') break;
269         next = fn;
270         while((apr_isspace(*next) == 0) && (*next != '\0')) next++;
271         while((apr_isspace(*next) != 0) && (*next != '\0')) *(next++) = '\0';
272
273         /* Add path of the rule filename for a relative phrase filename */
274         filepath = fn;
275         if (apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, rule->ruleset->mp) != APR_SUCCESS) {
276             /* We are not an absolute path.  It could mean an error, but
277              * let that pass through to the open call for a better error */
278             apr_filepath_merge(&fn, rulefile_path, fn, APR_FILEPATH_TRUENAME, rule->ruleset->mp);
279         }
280
281         /* Open file and read */
282         rc = apr_file_open(&fd, fn, APR_READ | APR_FILE_NOCLEANUP, 0, rule->ruleset->mp);
283         if (rc != APR_SUCCESS) {
284             *error_msg = apr_psprintf(rule->ruleset->mp, "Could not open phrase file \"%s\": %s", fn, apr_strerror(rc, errstr, 1024));
285             return 0;
286         }
287
288         #ifdef DEBUG_CONF
289         fprintf(stderr, "Loading phrase file: \"%s\"\n", fn);
290         #endif
291
292         /* Read one pattern per line skipping empty/commented */
293         for(;;) {
294             line++;
295             rc = apr_file_gets(buf, HUGE_STRING_LEN, fd);
296             if (rc == APR_EOF) break;
297             if (rc != APR_SUCCESS) {
298                 *error_msg = apr_psprintf(rule->ruleset->mp, "Could not read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024));
299                 return 0;
300             }
301
302             /* Remove newline */
303             ptr = buf;
304             while(*ptr != '\0') ptr++;
305             if ((ptr > buf) && (*(ptr - 1) == '\n')) *(ptr - 1) = '\0';
306
307             /* Ignore empty lines and comments */
308             ptr = buf;
309             while((*ptr != '\0') && apr_isspace(*ptr)) ptr++;
310             if ((*ptr == '\0') || (*ptr == '#')) continue;
311
312             #ifdef DEBUG_CONF
313             fprintf(stderr, "Adding phrase file pattern: \"%s\"\n", buf);
314             #endif
315
316             acmp_add_pattern(p, buf, NULL, NULL, strlen(buf));
317         }
318         fn = next;
319     }
320     if (fd != NULL) apr_file_close(fd);
321     acmp_prepare(p);
322     rule->op_param_data = p;
323     return 1;
324 }
325
326 static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
327     const char *match = NULL;
328     apr_status_t rc = 0;
329     int capture;
330     ACMPT pt;
331
332     /* Nothing to read */
333     if ((var->value == NULL) || (var->value_len == 0)) return 0;
334
335     /* Are we supposed to capture subexpressions? */
336     capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
337
338     pt.parser = (ACMP *)rule->op_param_data;
339     pt.ptr = NULL;
340
341     rc = acmp_process_quick(&pt, &match, var->value, var->value_len);
342     if (rc) {
343         char *match_escaped = log_escape(msr->mp, match ? match : "<Unknown Match>");
344
345         /* This message will be logged. */
346         if (strlen(match_escaped) > 252) {
347             *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%.252s ...\" at %s.",
348                 match_escaped, var->name);
349         } else {
350             *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%s\" at %s.",
351                 match_escaped, var->name);
352         }
353
354         /* Handle capture as tx.0=match */
355         if (capture) {
356             int i;
357             msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
358
359             if (s == NULL) return -1;
360
361             s->name = "0";
362             s->value = apr_pstrdup(msr->mp, match);
363             if (s->value == NULL) return -1;
364             s->value_len = strlen(s->value);
365             apr_table_setn(msr->tx_vars, s->name, (void *)s);
366
367             if (msr->txcfg->debuglog_level >= 9) {
368                 msr_log(msr, 9, "Added phrase match to TX.0: %s",
369                     log_escape_nq_ex(msr->mp, s->value, s->value_len));
370             }
371
372             /* Unset the remaining ones (from previous invocations). */
373             for(i = rc; i <= 9; i++) {
374                 char buf[2];
375                 apr_snprintf(buf, sizeof(buf), "%d", i);
376                 apr_table_unset(msr->tx_vars, buf);
377             }
378         }
379
380         return 1;
381     }
382     return rc;
383 }
384
385 /* within */
386
387 static int msre_op_within_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
388     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
389     const char *match = NULL;
390     const char *target;
391     unsigned int match_length;
392     unsigned int target_length = 0;
393     unsigned int i, i_max;
394
395     str->value = (char *)rule->op_param;
396     str->value_len = strlen(str->value);
397
398     if (error_msg == NULL) return -1;
399     *error_msg = NULL;
400
401     if (str->value == NULL) {
402         *error_msg = "Internal Error: match string is null.";
403         return -1;
404     }
405
406     expand_macros(msr, str, rule, msr->mp);
407
408     match = (const char *)str->value;
409     match_length = str->value_len;
410
411     /* If the given target is null we give up without a match */
412     if (var->value == NULL) {
413         /* No match. */
414         return 0;
415     }
416
417     target = var->value;
418     target_length = var->value_len;
419
420     /* The empty string always matches */
421     if (target_length == 0) {
422         /* Match. */
423         *error_msg = apr_psprintf(msr->mp, "String match within \"\" at %s.",
424                         var->name);
425         return 1;
426     }
427
428     /* This is impossible to match */
429     if (target_length > match_length) {
430         /* No match. */
431         return 0;
432     }
433
434     /* scan for first character, then compare from there until we
435      * have a match or there is no room left in the target
436      */
437     i_max = match_length - target_length;
438     for (i = 0; i <= i_max; i++) {
439         if (match[i] == target[0]) {
440             if (memcmp((target + 1), (match + i + 1), (target_length - 1)) == 0) {
441                 /* match. */
442                 *error_msg = apr_psprintf(msr->mp, "String match within \"%s\" at %s.",
443                                 log_escape_ex(msr->mp, match, match_length),
444                                 var->name);
445                 return 1;
446             }
447         }
448     }
449
450     /* No match. */
451     return 0;
452 }
453
454 /* contains */
455
456 static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
457     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
458     const char *match = NULL;
459     const char *target;
460     unsigned int match_length;
461     unsigned int target_length = 0;
462     unsigned int i, i_max;
463
464     str->value = (char *)rule->op_param;
465     str->value_len = strlen(str->value);
466
467     if (error_msg == NULL) return -1;
468     *error_msg = NULL;
469
470     if (str->value == NULL) {
471         *error_msg = "Internal Error: match string is null.";
472         return -1;
473     }
474
475     expand_macros(msr, str, rule, msr->mp);
476
477     match = (const char *)str->value;
478     match_length = str->value_len;
479
480     /* If the given target is null run against an empty
481      * string. This is a behaviour consistent with previous
482      * releases.
483      */
484     if (var->value == NULL) {
485         target = "";
486         target_length = 0;
487     } else {
488         target = var->value;
489         target_length = var->value_len;
490     }
491
492     /* The empty string always matches */
493     if (match_length == 0) {
494         /* Match. */
495         *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
496         return 1;
497     }
498
499     /* This is impossible to match */
500     if (match_length > target_length) {
501         /* No match. */
502         return 0;
503     }
504
505     /* scan for first character, then compare from there until we
506      * have a match or there is no room left in the target
507      */
508     i_max = target_length - match_length;
509     for (i = 0; i <= i_max; i++) {
510         /* First character matched - avoid func call */
511         if (target[i] == match[0]) {
512             /* See if remaining matches */
513             if (   (match_length == 1)
514                 || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0))
515             {
516                 /* Match. */
517                 *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
518                                 log_escape_ex(msr->mp, match, match_length),
519                                 var->name);
520                 return 1;
521             }
522         }
523     }
524
525     /* No match. */
526     return 0;
527 }
528
529 /* containsWord */
530
531 static int msre_op_containsWord_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
532     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
533     const char *match = NULL;
534     const char *target;
535     unsigned int match_length;
536     unsigned int target_length = 0;
537     unsigned int i, i_max;
538     int rc = 0;
539
540     str->value = (char *)rule->op_param;
541     str->value_len = strlen(str->value);
542
543     if (error_msg == NULL) return -1;
544     *error_msg = NULL;
545
546     if (str->value == NULL) {
547         *error_msg = "Internal Error: match string is null.";
548         return -1;
549     }
550
551     expand_macros(msr, str, rule, msr->mp);
552
553     match = (const char *)str->value;
554     match_length = str->value_len;
555
556     /* If the given target is null run against an empty
557      * string. This is a behaviour consistent with previous
558      * releases.
559      */
560     if (var->value == NULL) {
561         target = "";
562         target_length = 0;
563     } else {
564         target = var->value;
565         target_length = var->value_len;
566     }
567
568     /* The empty string always matches */
569     if (match_length == 0) {
570         /* Match. */
571         *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
572         return 1;
573     }
574
575     /* This is impossible to match */
576     if (match_length > target_length) {
577         /* No match. */
578         return 0;
579     }
580
581     /* scan for first character, then compare from there until we
582      * have a match or there is no room left in the target
583      */
584     i_max = target_length - match_length;
585     for (i = 0; i <= i_max; i++) {
586
587         /* Previous char must have been a start or non-word */
588         if ((i > 0) && (apr_isalnum(target[i-1])||(target[i-1] == '_')))
589             continue;
590
591         /* First character matched - avoid func call */
592         if (target[i] == match[0]) {
593             /* See if remaining matches */
594             if (   (match_length == 1)
595                 || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0))
596             {
597                 /* check boundaries */
598                 if (i == i_max) {
599                     /* exact/end word match */
600                     rc = 1;
601                 }
602                 else if (!(apr_isalnum(target[i + match_length])||(target[i + match_length] == '_'))) {
603                     /* start/mid word match */
604                     rc = 1;
605                 }
606             }
607         }
608     }
609
610     if (rc == 1) {
611         /* Maybe a match. */
612         *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
613                         log_escape_ex(msr->mp, match, match_length),
614                         var->name);
615         return 1;
616     }
617
618     /* No match. */
619     *error_msg = NULL;
620     return 0;
621 }
622
623 /* streq */
624
625 static int msre_op_streq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
626     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
627     const char *match = NULL;
628     const char *target;
629     unsigned int match_length;
630     unsigned int target_length;
631
632     str->value = (char *)rule->op_param;
633     str->value_len = strlen(str->value);
634
635     if (error_msg == NULL) return -1;
636     *error_msg = NULL;
637
638     if (str->value == NULL) {
639         *error_msg = "Internal Error: match string is null.";
640         return -1;
641     }
642
643     expand_macros(msr, str, rule, msr->mp);
644
645     match = (const char *)str->value;
646     match_length = str->value_len;
647
648     /* If the given target is null run against an empty
649      * string. This is a behaviour consistent with previous
650      * releases.
651      */
652     if (var->value == NULL) {
653         target = "";
654         target_length = 0;
655     } else {
656         target = var->value;
657         target_length = var->value_len;
658     }
659
660     /* These are impossible to match */
661     if (match_length != target_length) {
662         /* No match. */
663         return 0;
664     }
665
666     if (memcmp(match, target, target_length) == 0) {
667         /* Match. */
668         *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
669                         log_escape_ex(msr->mp, match, match_length),
670                         var->name);
671         return 1;
672     }
673
674     /* No match. */
675     return 0;
676 }
677
678 /* beginsWith */
679
680 static int msre_op_beginsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
681     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
682     const char *match = NULL;
683     const char *target;
684     unsigned int match_length;
685     unsigned int target_length;
686
687     str->value = (char *)rule->op_param;
688     str->value_len = strlen(str->value);
689
690     if (error_msg == NULL) return -1;
691     *error_msg = NULL;
692
693     if (str->value == NULL) {
694         *error_msg = "Internal Error: match string is null.";
695         return -1;
696     }
697
698     expand_macros(msr, str, rule, msr->mp);
699
700     match = (const char *)str->value;
701     match_length = str->value_len;
702
703     /* If the given target is null run against an empty
704      * string. This is a behaviour consistent with previous
705      * releases.
706      */
707     if (var->value == NULL) {
708         target = "";
709         target_length = 0;
710     } else {
711         target = var->value;
712         target_length = var->value_len;
713     }
714
715     /* The empty string always matches */
716     if (match_length == 0) {
717         /* Match. */
718         *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
719         return 1;
720     }
721
722     /* This is impossible to match */
723     if (match_length > target_length) {
724         /* No match. */
725         return 0;
726     }
727
728     if (memcmp(match, target, match_length) == 0) {
729         /* Match. */
730         *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
731                         log_escape_ex(msr->mp, match, match_length),
732                         var->name);
733         return 1;
734     }
735
736     /* No match. */
737     return 0;
738 }
739
740 /* endsWith */
741
742 static int msre_op_endsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
743     msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
744     const char *match = NULL;
745     const char *target;
746     unsigned int match_length;
747     unsigned int target_length;
748
749     str->value = (char *)rule->op_param;
750     str->value_len = strlen(str->value);
751
752     if (error_msg == NULL) return -1;
753     *error_msg = NULL;
754
755     if (str->value == NULL) {
756         *error_msg = "Internal Error: match string is null.";
757         return -1;
758     }
759
760     expand_macros(msr, str, rule, msr->mp);
761
762     match = (const char *)str->value;
763     match_length = str->value_len;
764
765     /* If the given target is null run against an empty
766      * string. This is a behaviour consistent with previous
767      * releases.
768      */
769     if (var->value == NULL) {
770         target = "";
771         target_length = 0;
772     } else {
773         target = var->value;
774         target_length = var->value_len;
775     }
776
777     /* The empty string always matches */
778     if (match_length == 0) {
779         /* Match. */
780         *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
781         return 1;
782     }
783
784     /* This is impossible to match */
785     if (match_length > target_length) {
786         /* No match. */
787         return 0;
788     }
789
790     if (memcmp(match, (target + (target_length - match_length)), match_length) == 0) {
791         /* Match. */
792         *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.",
793                         log_escape_ex(msr->mp, match, match_length),
794                         var->name);
795         return 1;
796     }
797
798     /* No match. */
799     return 0;
800 }
801
802 /* m */
803
804 static int msre_op_m_param_init(msre_rule *rule, char **error_msg) {
805     const apr_strmatch_pattern *compiled_pattern;
806     const char *pattern = rule->op_param;
807
808     if (error_msg == NULL) return -1;
809     *error_msg = NULL;
810
811     /* Compile pattern */
812     compiled_pattern = apr_strmatch_precompile(rule->ruleset->mp, pattern, 1);
813     if (compiled_pattern == NULL) {
814         *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern: %s", pattern);
815         return 0;
816     }
817
818     rule->op_param_data = (void *)compiled_pattern;
819
820     return 1; /* OK */
821 }
822
823 static int msre_op_m_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
824     apr_strmatch_pattern *compiled_pattern = (apr_strmatch_pattern *)rule->op_param_data;
825     const char *target;
826     unsigned int target_length;
827     const char *rc;
828
829     if (error_msg == NULL) return -1;
830     *error_msg = NULL;
831
832     if (compiled_pattern == NULL) {
833         *error_msg = "Internal Error: strnmatch data is null.";
834         return -1;
835     }
836
837     /* If the given target is null run against an empty
838      * string. This is a behaviour consistent with previous
839      * releases.
840      */
841     if (var->value == NULL) {
842         target = "";
843         target_length = 0;
844     } else {
845         target = var->value;
846         target_length = var->value_len;
847     }
848
849     rc = apr_strmatch(compiled_pattern, target, target_length);
850     if (rc == NULL) {
851         /* No match. */
852         return 0;
853     }
854
855     *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
856         log_escape(msr->mp, rule->op_param), var->name);
857
858     /* Match. */
859     return 1;
860 }
861
862 /* validateDTD */
863
864 static int msre_op_validateDTD_init(msre_rule *rule, char **error_msg) {
865     /* ENH Verify here the file actually exists. */
866     return 1;
867 }
868
869 static int msre_op_validateDTD_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
870     char **error_msg)
871 {
872     xmlValidCtxtPtr cvp;
873     xmlDtdPtr dtd;
874
875     if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
876         *error_msg = apr_psprintf(msr->mp,
877             "XML document tree could not be found for DTD validation.");
878         return -1;
879     }
880
881     if (msr->xml->well_formed != 1) {
882         *error_msg = apr_psprintf(msr->mp,
883             "XML: DTD validation failed because content is not well formed.");
884         return 1;
885     }
886
887     /* Make sure there were no other generic processing errors */
888     if (msr->msc_reqbody_error) {
889         *error_msg = apr_psprintf(msr->mp,
890             "XML: DTD validation could not proceed due to previous"
891             " processing errors.");
892         return 1;
893     }
894
895     dtd = xmlParseDTD(NULL, (const xmlChar *)rule->op_param); /* EHN support relative filenames */
896     if (dtd == NULL) {
897         *error_msg = apr_psprintf(msr->mp, "XML: Failed to load DTD: %s", rule->op_param);
898         return -1;
899     }
900
901     cvp = xmlNewValidCtxt();
902     if (cvp == NULL) {
903         *error_msg = "XML: Failed to create a validation context.";
904         xmlFreeDtd(dtd);
905         return -1;
906     }
907
908     /* Send validator errors/warnings to msr_log */
909     /* NOTE: No xmlDtdSetValidErrors()? */
910     cvp->error = (xmlSchemaValidityErrorFunc)msr_log_error;
911     cvp->warning = (xmlSchemaValidityErrorFunc)msr_log_warn;
912     cvp->userData = msr;
913
914     if (!xmlValidateDtd(cvp, msr->xml->doc, dtd)) {
915         *error_msg = "XML: DTD validation failed.";
916         xmlFreeValidCtxt(cvp);
917         xmlFreeDtd(dtd);
918         return 1; /* No match. */
919     }
920
921     msr_log(msr, 4, "XML: Successfully validated payload against DTD: %s", rule->op_param);
922
923     xmlFreeValidCtxt(cvp);
924     xmlFreeDtd(dtd);
925
926     /* Match. */
927     return 0;
928 }
929
930 /* validateSchema */
931
932 static int msre_op_validateSchema_init(msre_rule *rule, char **error_msg) {
933     /* ENH Verify here the file actually exists. */
934     return 1;
935 }
936
937 static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
938     char **error_msg)
939 {
940     xmlSchemaParserCtxtPtr parserCtx;
941     xmlSchemaValidCtxtPtr validCtx;
942     xmlSchemaPtr schema;
943     int rc;
944
945     if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
946         *error_msg = apr_psprintf(msr->mp,
947             "XML document tree could not be found for schema validation.");
948         return -1;
949     }
950
951     if (msr->xml->well_formed != 1) {
952         *error_msg = apr_psprintf(msr->mp,
953             "XML: Schema validation failed because content is not well formed.");
954         return 1;
955     }
956
957     /* Make sure there were no other generic processing errors */
958     if (msr->msc_reqbody_error) {
959         *error_msg = apr_psprintf(msr->mp,
960             "XML: Schema validation could not proceed due to previous"
961             " processing errors.");
962         return 1;
963     }
964
965     parserCtx = xmlSchemaNewParserCtxt(rule->op_param); /* ENH support relative filenames */
966     if (parserCtx == NULL) {
967         *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema from file: %s",
968             rule->op_param);
969         return -1;
970     }
971
972     /* Send parser errors/warnings to msr_log */
973     xmlSchemaSetParserErrors(parserCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);
974
975     schema = xmlSchemaParse(parserCtx);
976     if (schema == NULL) {
977         *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema: %s", rule->op_param);
978         xmlSchemaFreeParserCtxt(parserCtx);
979         return -1;
980     }
981
982     validCtx = xmlSchemaNewValidCtxt(schema);
983     if (validCtx == NULL) {
984         *error_msg = "XML: Failed to create validation context.";
985         xmlSchemaFree(schema);
986         xmlSchemaFreeParserCtxt(parserCtx);
987         return -1;
988     }
989
990     /* Send validator errors/warnings to msr_log */
991     xmlSchemaSetValidErrors(validCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);
992
993     rc = xmlSchemaValidateDoc(validCtx, msr->xml->doc);
994     if (rc != 0) {
995         *error_msg = "XML: Schema validation failed.";
996         xmlSchemaFree(schema);
997         xmlSchemaFreeParserCtxt(parserCtx);
998         return 1; /* No match. */
999     }
1000
1001     msr_log(msr, 4, "XML: Successfully validated payload against Schema: %s", rule->op_param);
1002
1003     xmlSchemaFree(schema);
1004     xmlSchemaFreeValidCtxt(validCtx);
1005
1006     return 0;
1007 }
1008
1009 /* verifyCC */
1010
1011 /**
1012  * Luhn Mod-10 Method (ISO 2894/ANSI 4.13)
1013  */
1014 static int luhn_verify(const char *ccnumber, int len) {
1015     int sum[2] = { 0, 0 };
1016     int odd = 0;
1017     int digits = 0;
1018     int i;
1019
1020     /* Weighted lookup table which is just a precalculated (i = index):
1021      *   i*2 + (( (i*2) > 9 ) ? -9 : 0)
1022      */
1023     static int wtable[10] = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9}; /* weight lookup table */
1024
1025     /* Add up only digits (weighted digits via lookup table)
1026      * for both odd and even CC numbers to avoid 2 passes.
1027      */
1028     for (i = 0; i < len; i++) {
1029         if (apr_isdigit(ccnumber[i])) {
1030             sum[0] += (!odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
1031             sum[1] += (odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
1032             odd = 1 - odd; /* alternate weights */
1033             digits++;
1034         }
1035     }
1036
1037     /* No digits extracted */
1038     if (digits == 0) return 0;
1039
1040     /* Do a mod 10 on the sum */
1041     sum[odd] %= 10;
1042
1043     /* If the result is a zero the card is valid. */
1044     return sum[odd] ? 0 : 1;
1045 }
1046
1047 static int msre_op_verifyCC_init(msre_rule *rule, char **error_msg) {
1048     const char *errptr = NULL;
1049     int erroffset;
1050     msc_regex_t *regex;
1051
1052     if (error_msg == NULL) return -1;
1053     *error_msg = NULL;
1054
1055     /* Compile rule->op_param */
1056     regex = msc_pregcomp(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset);
1057     if (regex == NULL) {
1058         *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
1059             erroffset, errptr);
1060         return 0;
1061     }
1062
1063     rule->op_param_data = regex;
1064
1065     return 1; /* OK */
1066 }
1067
1068 static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
1069     msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
1070     const char *target;
1071     unsigned int target_length;
1072     char *my_error_msg = NULL;
1073     int ovector[33];
1074     int rc;
1075     int is_cc = 0;
1076     int offset;
1077
1078     if (error_msg == NULL) return -1;
1079     *error_msg = NULL;
1080
1081     if (regex == NULL) {
1082         *error_msg = "Internal Error: regex data is null.";
1083         return -1;
1084     }
1085
1086     memset(ovector, 0, sizeof(ovector));
1087
1088     /* If the given target is null run against an empty
1089      * string. This is a behaviour consistent with previous
1090      * releases.
1091      */
1092     if (var->value == NULL) {
1093         target = "";
1094         target_length = 0;
1095     } else {
1096         target = var->value;
1097         target_length = var->value_len;
1098     }
1099
1100     for (offset = 0; ((unsigned int)offset < target_length) && (is_cc == 0); offset++) {
1101         if (msr->txcfg->debuglog_level >= 9) {
1102             if (offset > 0) {
1103                 msr_log(msr, 9, "Continuing CC# search at target offset %d.", offset);
1104             }
1105         }
1106
1107         rc = msc_regexec_ex(regex, target, target_length, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg);
1108
1109         /* If there was no match, then we are done. */
1110         if (rc == PCRE_ERROR_NOMATCH) {
1111             break;
1112         }
1113
1114         if (rc < -1) {
1115             *error_msg = apr_psprintf(msr->mp, "CC# regex execution failed: %s", my_error_msg);
1116             return -1;
1117         }
1118
1119         /* Verify a match. */
1120         if (rc > 0) {
1121             const char *match = target + ovector[0];
1122             int length = ovector[1] - ovector[0];
1123             int i = 0;
1124
1125             offset = ovector[2*i];
1126
1127             /* Check the Luhn using the match string */
1128             is_cc = luhn_verify(match, length);
1129
1130             /* Not a CC number, then try another match where we left off. */
1131             if (!is_cc) {
1132                 if (msr->txcfg->debuglog_level >= 9) {
1133                     msr_log(msr, 9, "CC# Luhn check failed at target offset %d: \"%.*s\"", offset, length, match);
1134                 }
1135
1136                 continue;
1137             }
1138
1139             /* We have a potential CC number and need to set any captures
1140              * and we are done.
1141              */
1142
1143             if (apr_table_get(rule->actionset->actions, "capture")) {
1144                 for(; i < rc; i++) {
1145                     msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1146                     if (s == NULL) return -1;
1147                     s->name = apr_psprintf(msr->mp, "%d", i);
1148                     s->value = apr_pstrmemdup(msr->mp, match, length);
1149                     s->value_len = length;
1150                     if ((s->name == NULL)||(s->value == NULL)) return -1;
1151
1152                     apr_table_setn(msr->tx_vars, s->name, (void *)s);
1153
1154                     if (msr->txcfg->debuglog_level >= 9) {
1155                         msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
1156                             log_escape_nq_ex(msr->mp, s->value, s->value_len));
1157                     }
1158                 }
1159             }
1160
1161             /* Unset the remaining TX vars (from previous invocations). */
1162             for(; i <= 9; i++) {
1163                 char buf[24];
1164                 apr_snprintf(buf, sizeof(buf), "%i", i);
1165                 apr_table_unset(msr->tx_vars, buf);
1166             }
1167
1168             break;
1169         }
1170     }
1171
1172     if (is_cc) {
1173         /* Match. */
1174
1175         /* This message will be logged. */
1176         *error_msg = apr_psprintf(msr->mp, "CC# match \"%s\" at %s. [offset \"%d\"]",
1177             regex->pattern, var->name, offset);
1178
1179         return 1;
1180     }
1181
1182     /* No match. */
1183     return 0;
1184 }
1185
1186
1187 /**
1188  * Perform geograpical lookups on an IP/Host.
1189  */
1190 static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1191     char **error_msg)
1192 {
1193     geo_rec rec;
1194     geo_db *geo = msr->txcfg->geo;
1195     const char *geo_host = var->value;
1196     msc_string *s = NULL;
1197     int rc;
1198
1199     *error_msg = NULL;
1200
1201     if (geo == NULL) {
1202         msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database.  Set SecGeoLookupDB.", log_escape(msr->mp, geo_host));
1203         return 0;
1204     }
1205
1206
1207     rc = geo_lookup(msr, &rec, geo_host, error_msg);
1208     if (rc <= 0) {
1209         if (! *error_msg) {
1210             *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed at %s.", log_escape_nq(msr->mp, geo_host), var->name);
1211         }
1212         apr_table_clear(msr->geo_vars);
1213         return rc;
1214     }
1215     if (! *error_msg) {
1216         *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded at %s.",
1217             log_escape_nq(msr->mp, geo_host), var->name);
1218     }
1219
1220     if (msr->txcfg->debuglog_level >= 9) {
1221         msr_log(msr, 9, "GEO: %s={country_code=%s, country_code3=%s, country_name=%s, country_continent=%s, region=%s, city=%s, postal_code=%s, latitude=%f, longitude=%f, dma_code=%d, area_code=%d}",
1222                 geo_host,
1223                 rec.country_code,
1224                 rec.country_code3,
1225                 rec.country_name,
1226                 rec.country_continent,
1227                 rec.region,
1228                 rec.city,
1229                 rec.postal_code,
1230                 rec.latitude,
1231                 rec.longitude,
1232                 rec.dma_code,
1233                 rec.area_code);
1234     }
1235
1236     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1237     s->name = apr_pstrdup(msr->mp, "COUNTRY_CODE");
1238     s->name_len = strlen(s->name);
1239     s->value = apr_pstrdup(msr->mp, rec.country_code ? rec.country_code : "");
1240     s->value_len = strlen(s->value);
1241     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1242
1243     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1244     s->name = apr_pstrdup(msr->mp, "COUNTRY_CODE3");
1245     s->name_len = strlen(s->name);
1246     s->value = apr_pstrdup(msr->mp, rec.country_code3 ? rec.country_code3 : "");
1247     s->value_len = strlen(s->value);
1248     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1249
1250     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1251     s->name = apr_pstrdup(msr->mp, "COUNTRY_NAME");
1252     s->name_len = strlen(s->name);
1253     s->value = apr_pstrdup(msr->mp, rec.country_name ? rec.country_name : "");
1254     s->value_len = strlen(s->value);
1255     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1256
1257     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1258     s->name = apr_pstrdup(msr->mp, "COUNTRY_CONTINENT");
1259     s->name_len = strlen(s->name);
1260     s->value = apr_pstrdup(msr->mp, rec.country_continent ? rec.country_continent : "");
1261     s->value_len = strlen(s->value);
1262     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1263
1264     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1265     s->name = apr_pstrdup(msr->mp, "REGION");
1266     s->name_len = strlen(s->name);
1267     s->value = apr_pstrdup(msr->mp, rec.region ? rec.region : "");
1268     s->value_len = strlen(s->value);
1269     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1270
1271     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1272     s->name = apr_pstrdup(msr->mp, "CITY");
1273     s->name_len = strlen(s->name);
1274     s->value = apr_pstrdup(msr->mp, rec.city ? rec.city : "");
1275     s->value_len = strlen(s->value);
1276     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1277
1278     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1279     s->name = apr_pstrdup(msr->mp, "POSTAL_CODE");
1280     s->name_len = strlen(s->name);
1281     s->value = apr_pstrdup(msr->mp, rec.postal_code ? rec.postal_code : "");
1282     s->value_len = strlen(s->value);
1283     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1284
1285     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1286     s->name = apr_pstrdup(msr->mp, "LATITUDE");
1287     s->name_len = strlen(s->name);
1288     s->value = apr_psprintf(msr->mp, "%f", rec.latitude);
1289     s->value_len = strlen(s->value);
1290     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1291
1292     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1293     s->name = apr_pstrdup(msr->mp, "LONGITUDE");
1294     s->name_len = strlen(s->name);
1295     s->value = apr_psprintf(msr->mp, "%f", rec.longitude);
1296     s->value_len = strlen(s->value);
1297     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1298
1299     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1300     s->name = apr_pstrdup(msr->mp, "DMA_CODE");
1301     s->name_len = strlen(s->name);
1302     s->value = apr_psprintf(msr->mp, "%d", rec.dma_code);
1303     s->value_len = strlen(s->value);
1304     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1305
1306     s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1307     s->name = apr_pstrdup(msr->mp, "AREA_CODE");
1308     s->name_len = strlen(s->name);
1309     s->value = apr_psprintf(msr->mp, "%d", rec.area_code);
1310     s->value_len = strlen(s->value);
1311     apr_table_setn(msr->geo_vars, s->name, (void *)s);
1312
1313     return 1;
1314 }
1315
1316 /* rbl */
1317
1318 static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
1319     unsigned int h0, h1, h2, h3;
1320     char *name_to_check = NULL;
1321     char *target = NULL;
1322     apr_sockaddr_t *sa = NULL;
1323     apr_status_t rc;
1324
1325     if (error_msg == NULL) return -1;
1326     *error_msg = NULL;
1327
1328     /* ENH Add IPv6 support. */
1329
1330     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1331     if (target == NULL) return -1;
1332
1333     /* Construct the host name we want to resolve. */
1334     if (sscanf(target, "%d.%d.%d.%d", &h0, &h1, &h2, &h3) == 4) {
1335         /* IPv4 address */
1336         name_to_check = apr_psprintf(msr->mp, "%d.%d.%d.%d.%s", h3, h2, h1, h0, rule->op_param);
1337     } else {
1338         /* Assume the input is a domain name. */
1339         name_to_check = apr_psprintf(msr->mp, "%s.%s", target, rule->op_param);
1340     }
1341
1342     if (name_to_check == NULL) return -1;
1343
1344     rc = apr_sockaddr_info_get(&sa, name_to_check,
1345         APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp);
1346     if (rc == APR_SUCCESS) {
1347         *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.",
1348             log_escape_nq(msr->mp, name_to_check), var->name);
1349         return 1; /* Match. */
1350     }
1351
1352     msr_log(msr, 5, "RBL lookup of %s failed at %s.", log_escape_nq(msr->mp, name_to_check), var->name);
1353
1354     /* No match. */
1355     return 0;
1356 }
1357
1358 /* inspectFile */
1359
1360 static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
1361     char *filename = (char *)rule->op_param;
1362
1363     if (error_msg == NULL) return -1;
1364     *error_msg = NULL;
1365
1366     if ((filename == NULL)||(is_empty_string(filename))) {
1367         *error_msg = apr_psprintf(rule->ruleset->mp, "Operator @inspectFile requires parameter.");
1368         return -1;
1369     }
1370
1371     filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename);
1372
1373     #if defined(WITH_LUA)
1374     /* ENH Write & use string_ends(s, e). */
1375     if (strlen(rule->op_param) > 4) {
1376         char *p = filename + strlen(filename) - 4;
1377         if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a'))
1378         {
1379             msc_script *script = NULL;
1380
1381             /* Compile script. */
1382             *error_msg = lua_compile(&script, filename, rule->ruleset->mp);
1383             if (*error_msg != NULL) return -1;
1384
1385             rule->op_param_data = script;
1386         }
1387     }
1388     #endif
1389
1390     if (rule->op_param_data == NULL) {
1391         /* ENH Verify the script exists and that we have
1392          * the rights to execute it.
1393          */
1394     }
1395
1396     return 1;
1397 }
1398
1399 static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1400     char **error_msg)
1401 {
1402     if (error_msg == NULL) return -1;
1403     *error_msg = NULL;
1404
1405     if (rule->op_param_data == NULL) {
1406         /* Execute externally, as native binary/shell script. */
1407         char *script_output = NULL;
1408         char const *argv[5];
1409         const char *approver_script = rule->op_param;
1410         const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1411
1412         msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file);
1413
1414         argv[0] = approver_script;
1415         argv[1] = target_file;
1416         argv[2] = NULL;
1417
1418         if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) {
1419             *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).",
1420                 log_escape(msr->mp, approver_script));
1421             return -1;
1422         }
1423
1424         if (script_output == NULL) {
1425             *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).",
1426                 log_escape(msr->mp, approver_script));
1427             return -1;
1428         }
1429
1430         if (script_output[0] != '1') {
1431             *error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s",
1432                 log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script),
1433                 log_escape_nq(msr->mp,  script_output));
1434             return 1; /* Match. */
1435         }
1436     }
1437     #if defined(WITH_LUA)
1438     else {
1439         /* Execute internally, as Lua script. */
1440         char *target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1441         msc_script *script = (msc_script *)rule->op_param_data;
1442         int rc;
1443
1444         rc = lua_execute(script, target, msr, rule, error_msg);
1445         if (rc < 0) {
1446             /* Error. */
1447             return -1;
1448         }
1449
1450         return rc;
1451     }
1452     #endif
1453
1454     /* No match. */
1455     return 0;
1456 }
1457
1458 /* validateByteRange */
1459
1460 static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) {
1461     char *p = NULL, *saveptr = NULL;
1462     char *table = NULL, *data = NULL;
1463
1464     if (error_msg == NULL) return -1;
1465     *error_msg = NULL;
1466
1467     if (rule->op_param == NULL) {
1468         *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for validateByteRange.");
1469         return -1;
1470     }
1471
1472     /* Initialise. */
1473     data = apr_pstrdup(rule->ruleset->mp, rule->op_param);
1474     rule->op_param_data = apr_pcalloc(rule->ruleset->mp, 32);
1475     if ((data == NULL)||(rule->op_param_data == NULL)) return -1;
1476     table = rule->op_param_data;
1477
1478     /* Extract parameters and update table. */
1479     p = apr_strtok(data, ",", &saveptr);
1480     while(p != NULL) {
1481         char *s = strstr(p, "-");
1482         if (s == NULL) {
1483             /* Single value. */
1484             int x = atoi(p);
1485             if ((x < 0)||(x > 255)) {
1486                 *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %d", x);
1487                 return 0;
1488             }
1489             table[x>>3] = (table[x>>3] | (1 << (x & 0x7)));
1490         } else {
1491             /* Range. */
1492             int start = atoi(p);
1493             int end = atoi(s + 1);
1494
1495             if ((start < 0)||(start > 255)) {
1496                 *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %d",
1497                     start);
1498                 return 0;
1499             }
1500             if ((end < 0)||(end > 255)) {
1501                 *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %d", end);
1502                 return 0;
1503             }
1504             if (start > end) {
1505                 *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %d-%d", start, end);
1506                 return 0;
1507             }
1508
1509             while(start <= end) {
1510                 table[start >> 3] = (table[start >> 3] | (1 << (start & 0x7)));
1511                 start++;
1512             }
1513         }
1514
1515         p = apr_strtok(NULL, ",", &saveptr);
1516     }
1517
1518     return 1;
1519 }
1520
1521 static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1522     char **error_msg)
1523 {
1524     char *table = rule->op_param_data;
1525     unsigned int i, count;
1526
1527     if (error_msg == NULL) return -1;
1528     *error_msg = NULL;
1529
1530     if (table == NULL) {
1531         *error_msg = apr_psprintf(msr->mp, "Internal Error: validateByteRange table not "
1532             "initialised.");
1533         return -1;
1534     }
1535
1536     /* Check every byte of the target to detect characters that are not allowed. */
1537
1538     count = 0;
1539     for(i = 0; i < var->value_len; i++) {
1540         int x = ((unsigned char *)var->value)[i];
1541         if (!(table[x >> 3] & (1 << (x & 0x7)))) {
1542             if (msr->txcfg->debuglog_level >= 9) {
1543                 msr_log(msr, 9, "Value %d in %s outside range: %s", x, var->name, rule->op_param);
1544             }
1545             count++;
1546         }
1547     }
1548
1549     if (count == 0) return 0; /* Valid - no match. */
1550
1551     *error_msg = apr_psprintf(msr->mp, "Found %d byte(s) in %s outside range: %s.",
1552         count, var->name, rule->op_param);
1553
1554     return 1; /* Invalid - match.*/
1555 }
1556
1557 /* validateUrlEncoding */
1558
1559 static int validate_url_encoding(const char *input, long int input_length) {
1560     int i;
1561
1562     if ((input == NULL)||(input_length < 0)) return -1;
1563
1564     i = 0;
1565     while (i < input_length) {
1566         if (input[i] == '%') {
1567             if (i + 2 >= input_length) {
1568                 /* Not enough bytes. */
1569                 return -3;
1570             }
1571             else {
1572                 /* Here we only decode a %xx combination if it is valid,
1573                  * leaving it as is otherwise.
1574                  */
1575                 char c1 = input[i + 1];
1576                 char c2 = input[i + 2];
1577
1578                 if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F')))
1579                     && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) )
1580                 {
1581                     i += 3;
1582                 } else {
1583                     /* Non-hexadecimal characters used in encoding. */
1584                     return -2;
1585                 }
1586             }
1587         } else {
1588             i++;
1589         }
1590     }
1591
1592     return 1;
1593 }
1594
1595 static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1596     char **error_msg)
1597 {
1598     int rc = validate_url_encoding(var->value, var->value_len);
1599     switch(rc) {
1600         case 1 :
1601             /* Encoding is valid */
1602             *error_msg = apr_psprintf(msr->mp, "Valid URL Encoding at %s.", var->name);
1603             break;
1604         case -2 :
1605             *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal "
1606                 "digits used at %s.", var->name);
1607             return 1; /* Invalid match. */
1608             break;
1609         case -3 :
1610             *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters "
1611                 "at the end of input at %s.", var->name);
1612             return 1; /* Invalid match. */
1613             break;
1614         case -1 :
1615         default :
1616             *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %d) at %s", rc, var->name);
1617             return -1;
1618             break;
1619
1620     }
1621
1622     /* No match. */
1623     return 0;
1624 }
1625
1626 /* validateUtf8Encoding */
1627
1628 #define UNICODE_ERROR_CHARACTERS_MISSING    -1
1629 #define UNICODE_ERROR_INVALID_ENCODING      -2
1630 #define UNICODE_ERROR_OVERLONG_CHARACTER    -3
1631 #define UNICODE_ERROR_RESTRICTED_CHARACTER  -4
1632 #define UNICODE_ERROR_DECODING_ERROR        -5
1633
1634 /* NOTE: This is over-commented for ease of verification */
1635 static int detect_utf8_character(const unsigned char *p_read, unsigned int length) {
1636     int unicode_len = 0;
1637     unsigned int d = 0;
1638     unsigned char c;
1639
1640     if (p_read == NULL) return UNICODE_ERROR_DECODING_ERROR;
1641     c = *p_read;
1642
1643     /* If first byte begins with binary 0 it is single byte encoding */
1644     if ((c & 0x80) == 0) {
1645         /* single byte unicode (7 bit ASCII equivilent) has no validation */
1646         return 1;
1647     }
1648     /* If first byte begins with binary 110 it is two byte encoding*/
1649     else if ((c & 0xE0) == 0xC0) {
1650         /* check we have at least two bytes */
1651         if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
1652         /* check second byte starts with binary 10 */
1653         else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1654         else {
1655             unicode_len = 2;
1656             /* compute character number */
1657             d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F);
1658         }
1659     }
1660     /* If first byte begins with binary 1110 it is three byte encoding */
1661     else if ((c & 0xF0) == 0xE0) {
1662         /* check we have at least three bytes */
1663         if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
1664         /* check second byte starts with binary 10 */
1665         else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1666         /* check third byte starts with binary 10 */
1667         else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1668         else {
1669             unicode_len = 3;
1670             /* compute character number */
1671             d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F);
1672         }
1673     }
1674     /* If first byte begins with binary 11110 it is four byte encoding */
1675     else if ((c & 0xF8) == 0xF0) {
1676         /* restrict characters to UTF-8 range (U+0000 - U+10FFFF)*/
1677         if (c >= 0xF5) {
1678             return UNICODE_ERROR_RESTRICTED_CHARACTER;
1679         }
1680         /* check we have at least four bytes */
1681         if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
1682         /* check second byte starts with binary 10 */
1683         else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1684         /* check third byte starts with binary 10 */
1685         else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1686         /* check forth byte starts with binary 10 */
1687         else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
1688         else {
1689             unicode_len = 4;
1690             /* compute character number */
1691             d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F);
1692         }
1693     }
1694     /* any other first byte is invalid (RFC 3629) */
1695     else {
1696         return UNICODE_ERROR_INVALID_ENCODING;
1697     }
1698
1699     /* invalid UTF-8 character number range (RFC 3629) */
1700     if ((d >= 0xD800) && (d <= 0xDFFF)) {
1701         return UNICODE_ERROR_RESTRICTED_CHARACTER;
1702     }
1703
1704     /* check for overlong */
1705     if ((unicode_len == 4) && (d < 0x010000)) {
1706         /* four byte could be represented with less bytes */
1707         return UNICODE_ERROR_OVERLONG_CHARACTER;
1708     }
1709     else if ((unicode_len == 3) && (d < 0x0800)) {
1710         /* three byte could be represented with less bytes */
1711         return UNICODE_ERROR_OVERLONG_CHARACTER;
1712     }
1713     else if ((unicode_len == 2) && (d < 0x80)) {
1714         /* two byte could be represented with less bytes */
1715         return UNICODE_ERROR_OVERLONG_CHARACTER;
1716     }
1717
1718     return unicode_len;
1719 }
1720
1721 static int msre_op_validateUtf8Encoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1722     char **error_msg)
1723 {
1724     unsigned int i, bytes_left;
1725
1726     bytes_left = var->value_len;
1727
1728     for(i = 0; i < var->value_len;) {
1729         int rc = detect_utf8_character((unsigned char *)&var->value[i], bytes_left);
1730
1731         switch(rc) {
1732             case UNICODE_ERROR_CHARACTERS_MISSING :
1733                 *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
1734                     "not enough bytes in character "
1735                     "at %s. [offset \"%d\"]", var->name, i);
1736                 return 1;
1737                 break;
1738             case UNICODE_ERROR_INVALID_ENCODING :
1739                 *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
1740                     "invalid byte value in character "
1741                     "at %s. [offset \"%d\"]", var->name, i);
1742                 return 1;
1743                 break;
1744             case UNICODE_ERROR_OVERLONG_CHARACTER :
1745                 *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
1746                     "overlong character detected "
1747                     "at %s. [offset \"%d\"]", var->name, i);
1748                 return 1;
1749                 break;
1750             case UNICODE_ERROR_RESTRICTED_CHARACTER :
1751                 *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
1752                     "use of restricted character "
1753                     "at %s. [offset \"%d\"]", var->name, i);
1754                 return 1;
1755                 break;
1756             case UNICODE_ERROR_DECODING_ERROR :
1757                 *error_msg = apr_psprintf(msr->mp, "Error validating UTF-8 decoding "
1758                     "at %s. [offset \"%d\"]", var->name, i);
1759                 return 1;
1760                 break;
1761         }
1762
1763         if (rc <= 0) {
1764             *error_msg = apr_psprintf(msr->mp, "Internal error during UTF-8 validation "
1765                 "at %s.", var->name);
1766             return 1;
1767         }
1768
1769         i += rc;
1770         bytes_left -= rc;
1771     }
1772
1773     return 0;
1774 }
1775
1776 /* eq */
1777
1778 static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1779     char **error_msg)
1780 {
1781     int left, right;
1782     char *target = NULL;
1783
1784     if ((var->value == NULL)||(rule->op_param == NULL)) {
1785         /* NULL values do not match anything. */
1786         return 0;
1787     }
1788
1789     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1790     if (target == NULL) return -1;
1791     left = atoi(target);
1792     right = atoi(rule->op_param);
1793
1794     if (left != right) {
1795         /* No match. */
1796         return 0;
1797     }
1798     else {
1799         *error_msg = apr_psprintf(msr->mp, "Operator EQ matched %d at %s.", right, var->name);
1800         /* Match. */
1801         return 1;
1802     }
1803 }
1804
1805 /* gt */
1806
1807 static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1808     char **error_msg)
1809 {
1810     int left, right;
1811     char *target = NULL;
1812
1813     if ((var->value == NULL)||(rule->op_param == NULL)) {
1814         /* NULL values do not match anything. */
1815         return 0;
1816     }
1817
1818     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1819     if (target == NULL) return -1;
1820     left = atoi(target);
1821     right = atoi(rule->op_param);
1822
1823     if (left <= right) {
1824         /* No match. */
1825         return 0;
1826     }
1827     else {
1828         *error_msg = apr_psprintf(msr->mp, "Operator GT matched %d at %s.", right, var->name);
1829         /* Match. */
1830         return 1;
1831     }
1832 }
1833
1834 /* lt */
1835
1836 static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1837     char **error_msg)
1838 {
1839     int left, right;
1840     char *target = NULL;
1841
1842     if ((var->value == NULL)||(rule->op_param == NULL)) {
1843         /* NULL values do not match anything. */
1844         return 0;
1845     }
1846
1847     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1848     if (target == NULL) return -1;
1849     left = atoi(target);
1850     right = atoi(rule->op_param);
1851
1852     if (left >= right) {
1853         /* No match. */
1854         return 0;
1855     }
1856     else {
1857         *error_msg = apr_psprintf(msr->mp, "Operator LT matched %d at %s.", right, var->name);
1858         /* Match. */
1859         return 1;
1860     }
1861 }
1862
1863 /* ge */
1864
1865 static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1866     char **error_msg)
1867 {
1868     int left, right;
1869     char *target = NULL;
1870
1871     if ((var->value == NULL)||(rule->op_param == NULL)) {
1872         /* NULL values do not match anything. */
1873         return 0;
1874     }
1875
1876     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1877     if (target == NULL) return -1;
1878     left = atoi(target);
1879     right = atoi(rule->op_param);
1880
1881     if (left < right) {
1882         /* No match. */
1883         return 0;
1884     }
1885     else {
1886         *error_msg = apr_psprintf(msr->mp, "Operator GE matched %d at %s.", right, var->name);
1887         /* Match. */
1888         return 1;
1889     }
1890 }
1891
1892 /* le */
1893
1894 static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
1895     char **error_msg)
1896 {
1897     int left, right;
1898     char *target = NULL;
1899
1900     if ((var->value == NULL)||(rule->op_param == NULL)) {
1901         /* NULL values do not match anything. */
1902         return 0;
1903     }
1904
1905     target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
1906     if (target == NULL) return -1;
1907     left = atoi(target);
1908     right = atoi(rule->op_param);
1909
1910     if (left > right) {
1911         /* No match. */
1912         return 0;
1913     }
1914     else {
1915         *error_msg = apr_psprintf(msr->mp, "Operator LE matched %d at %s.", right, var->name);
1916         /* Match. */
1917         return 1;
1918     }
1919 }
1920
1921 /* ------------------------------------------------------------------------------- */
1922
1923 /**
1924  *
1925  */
1926 void msre_engine_register_default_operators(msre_engine *engine) {
1927     /* unconditionalMatch */
1928     msre_engine_op_register(engine,
1929         "unconditionalMatch",
1930         NULL,
1931         msre_op_unconditionalmatch_execute
1932     );
1933
1934     /* noMatch */
1935     msre_engine_op_register(engine,
1936         "noMatch",
1937         NULL,
1938         msre_op_nomatch_execute
1939     );
1940
1941     /* rx */
1942     msre_engine_op_register(engine,
1943         "rx",
1944         msre_op_rx_param_init,
1945         msre_op_rx_execute
1946     );
1947
1948     /* pm */
1949     msre_engine_op_register(engine,
1950         "pm",
1951         msre_op_pm_param_init,
1952         msre_op_pm_execute
1953     );
1954
1955     /* pmFromFile */
1956     msre_engine_op_register(engine,
1957         "pmFromFile",
1958         msre_op_pmFromFile_param_init,
1959         msre_op_pm_execute
1960     );
1961
1962     /* within */
1963     msre_engine_op_register(engine,
1964         "within",
1965         NULL, /* ENH init function to flag var substitution */
1966         msre_op_within_execute
1967     );
1968
1969     /* contains */
1970     msre_engine_op_register(engine,
1971         "contains",
1972         NULL, /* ENH init function to flag var substitution */
1973         msre_op_contains_execute
1974     );
1975
1976     /* containsWord */
1977     msre_engine_op_register(engine,
1978         "containsWord",
1979         NULL, /* ENH init function to flag var substitution */
1980         msre_op_containsWord_execute
1981     );
1982
1983     /* is */
1984     msre_engine_op_register(engine,
1985         "streq",
1986         NULL, /* ENH init function to flag var substitution */
1987         msre_op_streq_execute
1988     );
1989
1990     /* beginsWith */
1991     msre_engine_op_register(engine,
1992         "beginsWith",
1993         NULL, /* ENH init function to flag var substitution */
1994         msre_op_beginsWith_execute
1995     );
1996
1997     /* endsWith */
1998     msre_engine_op_register(engine,
1999         "endsWith",
2000         NULL, /* ENH init function to flag var substitution */
2001         msre_op_endsWith_execute
2002     );
2003
2004     /* m */
2005     msre_engine_op_register(engine,
2006         "m",
2007         msre_op_m_param_init,
2008         msre_op_m_execute
2009     );
2010
2011     /* validateDTD */
2012     msre_engine_op_register(engine,
2013         "validateDTD",
2014         msre_op_validateDTD_init,
2015         msre_op_validateDTD_execute
2016     );
2017
2018     /* validateSchema */
2019     msre_engine_op_register(engine,
2020         "validateSchema",
2021         msre_op_validateSchema_init,
2022         msre_op_validateSchema_execute
2023     );
2024
2025     /* verifyCC */
2026     msre_engine_op_register(engine,
2027         "verifyCC",
2028         msre_op_verifyCC_init,
2029         msre_op_verifyCC_execute
2030     );
2031
2032     /* geoLookup */
2033     msre_engine_op_register(engine,
2034         "geoLookup",
2035         NULL,
2036         msre_op_geoLookup_execute
2037     );
2038
2039     /* rbl */
2040     msre_engine_op_register(engine,
2041         "rbl",
2042         NULL, /* ENH init function to validate DNS server */
2043         msre_op_rbl_execute
2044     );
2045
2046     /* inspectFile */
2047     msre_engine_op_register(engine,
2048         "inspectFile",
2049         msre_op_inspectFile_init,
2050         msre_op_inspectFile_execute
2051     );
2052
2053     /* validateByteRange */
2054     msre_engine_op_register(engine,
2055         "validateByteRange",
2056         msre_op_validateByteRange_init,
2057         msre_op_validateByteRange_execute
2058     );
2059
2060     /* validateUrlEncoding */
2061     msre_engine_op_register(engine,
2062         "validateUrlEncoding",
2063         NULL,
2064         msre_op_validateUrlEncoding_execute
2065     );
2066
2067     /* validateUtf8Encoding */
2068     msre_engine_op_register(engine,
2069         "validateUtf8Encoding",
2070         NULL,
2071         msre_op_validateUtf8Encoding_execute
2072     );
2073
2074     /* eq */
2075     msre_engine_op_register(engine,
2076         "eq",
2077         NULL,
2078         msre_op_eq_execute
2079     );
2080
2081     /* gt */
2082     msre_engine_op_register(engine,
2083         "gt",
2084         NULL,
2085         msre_op_gt_execute
2086     );
2087
2088     /* lt */
2089     msre_engine_op_register(engine,
2090         "lt",
2091         NULL,
2092         msre_op_lt_execute
2093     );
2094
2095     /* le */
2096     msre_engine_op_register(engine,
2097         "le",
2098         NULL,
2099         msre_op_le_execute
2100     );
2101
2102     /* ge */
2103     msre_engine_op_register(engine,
2104         "ge",
2105         NULL,
2106         msre_op_ge_execute
2107     );
2108 }