root/src/noit_filters.c

Revision 647ef348e1bcdee83786760f7616926b52a6bac1, 17.7 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 9 months ago)

Various code cleanliness fixes.

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include "utils/noit_hash.h"
36 #include "utils/noit_atomic.h"
37 #include "noit_conf.h"
38 #include "noit_check.h"
39 #include "noit_conf_checks.h"
40 #include "noit_filters.h"
41 #include "noit_capabilities_listener.h"
42
43 #include <pcre.h>
44 #include <assert.h>
45 #include <libxml/tree.h>
46
47 static noit_hash_table *filtersets = NULL;
48 static pthread_mutex_t filterset_lock;
49 static pcre *fallback_no_match = NULL;
50 #define LOCKFS() pthread_mutex_lock(&filterset_lock)
51 #define UNLOCKFS() pthread_mutex_unlock(&filterset_lock)
52
53 typedef enum { NOIT_FILTER_ACCEPT, NOIT_FILTER_DENY } noit_ruletype_t;
54 typedef struct _filterrule {
55   noit_ruletype_t type;
56   pcre *target_override;
57   pcre *target;
58   pcre_extra *target_e;
59   noit_hash_table *target_ht;
60   pcre *module_override;
61   pcre *module;
62   pcre_extra *module_e;
63   noit_hash_table *module_ht;
64   pcre *name_override;
65   pcre *name;
66   pcre_extra *name_e;
67   noit_hash_table *name_ht;
68   pcre *metric_override;
69   pcre *metric;
70   pcre_extra *metric_e;
71   noit_hash_table *metric_ht;
72   struct _filterrule *next;
73 } filterrule_t;
74
75 typedef struct {
76   noit_atomic32_t ref_cnt;
77   char *name;
78   filterrule_t *rules;
79 } filterset_t;
80
81 #define FRF(r,a) do { \
82   if(r->a) pcre_free(r->a); \
83   if(r->a##_e) pcre_free(r->a##_e); \
84   if(r->a##_ht) { \
85     noit_hash_destroy(r->a##_ht, free, NULL); \
86     free(r->a##_ht); \
87   } \
88 } while(0)
89
90 static void
91 filterrule_free(void *vp) {
92   filterrule_t *r = vp;
93   FRF(r,target);
94   FRF(r,module);
95   FRF(r,name);
96   FRF(r,metric);
97   free(r);
98 }
99 static void
100 filterset_free(void *vp) {
101   filterset_t *fs = vp;
102   filterrule_t *r;
103   if(noit_atomic_dec32(&fs->ref_cnt) != 0) return;
104   noitL(noit_debug, "Freeing filterset [%d]: %s\n", fs->ref_cnt, fs->name);
105   while(fs->rules) {
106     r = fs->rules->next;
107     filterrule_free(fs->rules);
108     fs->rules = r;
109   }
110   if(fs->name) free(fs->name);
111   free(fs);
112 }
113 void
114 noit_filter_compile_add(noit_conf_section_t setinfo) {
115   noit_conf_section_t *rules;
116   int j, fcnt;
117   char filterset_name[256];
118   filterset_t *set;
119   if(!noit_conf_get_stringbuf(setinfo, "@name",
120                               filterset_name, sizeof(filterset_name))) {
121     noitL(noit_error,
122           "filterset with no name, skipping as it cannot be referenced.\n");
123     return;
124   }
125   set = calloc(1, sizeof(*set));
126   set->ref_cnt = 1;
127   set->name = strdup(filterset_name);
128
129   rules = noit_conf_get_sections(setinfo, "rule", &fcnt);
130   /* Here we will work through the list backwards pushing the rules on
131    * the front of the list.  That way we can simply walk them in order
132    * for the application process.
133    */
134   noitL(noit_debug, "Compiling filterset '%s'\n", set->name);
135   for(j=fcnt-1; j>=0; j--) {
136     filterrule_t *rule;
137     char buffer[256];
138     if(!noit_conf_get_stringbuf(rules[j], "@type", buffer, sizeof(buffer)) ||
139        (strcmp(buffer, "accept") && strcmp(buffer, "allow") && strcmp(buffer, "deny"))) {
140       noitL(noit_error, "rule must have type 'accept' or 'allow' or 'deny'\n");
141       continue;
142     }
143     noitL(noit_debug, "Prepending %s into %s\n", buffer, set->name);
144     rule = calloc(1, sizeof(*rule));
145     rule->type = (!strcmp(buffer, "accept") || !strcmp(buffer, "allow")) ?
146                    NOIT_FILTER_ACCEPT : NOIT_FILTER_DENY;
147
148     /* Compile any hash tables, should they exist */
149 #define HT_COMPILE(rname) do { \
150     noit_conf_section_t *htentries; \
151     int hte_cnt; \
152     htentries = noit_conf_get_sections(rules[j], #rname, &hte_cnt); \
153     if(hte_cnt) { \
154       int hti; \
155       char *htstr; \
156       rule->rname##_ht = calloc(1, sizeof(*(rule->rname##_ht))); \
157       noit_hash_init_size(rule->rname##_ht, 8); /* small as we have lots */ \
158       for(hti=0; hti<hte_cnt; hti++) { \
159         if(!noit_conf_get_string(htentries[hti], "self::node()", &htstr)) \
160           noitL(noit_error, "Error fetching text content from filter match.\n"); \
161         else \
162           noit_hash_replace(rule->rname##_ht, htstr, strlen(htstr), NULL, free, NULL); \
163       } \
164     } \
165     free(htentries); \
166 } while(0);
167     HT_COMPILE(target);
168     HT_COMPILE(module);
169     HT_COMPILE(name);
170     HT_COMPILE(metric);
171    
172     /* Compile our rules */
173 #define RULE_COMPILE(rname) do { \
174   char *longre = NULL; \
175   if(noit_conf_get_string(rules[j], "@" #rname, &longre)) { \
176     const char *error; \
177     int erroffset; \
178     rule->rname = pcre_compile(longre, 0, &error, &erroffset, NULL); \
179     if(!rule->rname) { \
180       noitL(noit_debug, "set '%s' rule '%s: %s' compile failed: %s\n", \
181             set->name, #rname, longre, error ? error : "???"); \
182       rule->rname##_override = fallback_no_match; \
183     } \
184     else { \
185       rule->rname##_e = pcre_study(rule->rname, 0, &error); \
186     } \
187     free(longre); \
188   } \
189 } while(0)
190
191     RULE_COMPILE(target);
192     RULE_COMPILE(module);
193     RULE_COMPILE(name);
194     RULE_COMPILE(metric);
195     rule->next = set->rules;
196     set->rules = rule;
197   }
198   free(rules);
199   LOCKFS();
200   noit_hash_replace(filtersets, set->name, strlen(set->name), (void *)set,
201                     NULL, filterset_free);
202   UNLOCKFS();
203 }
204 int
205 noit_filter_exists(const char *name) {
206   int exists;
207   void *v;
208   LOCKFS();
209   exists = noit_hash_retrieve(filtersets, name, strlen(name), &v);
210   UNLOCKFS();
211   return exists;
212 }
213 int
214 noit_filter_remove(noit_conf_section_t vnode) {
215   int removed;
216   char *name = (char *)xmlGetProp(vnode, (xmlChar *)"name");
217   if(!name) return 0;
218   LOCKFS();
219   removed = noit_hash_delete(filtersets, name, strlen(name),
220                              NULL, filterset_free);
221   UNLOCKFS();
222   return removed;
223 }
224 void
225 noit_filters_from_conf() {
226   noit_conf_section_t *sets;
227   int i, cnt;
228
229   sets = noit_conf_get_sections(NULL, "/noit/filtersets//filterset", &cnt);
230   for(i=0; i<cnt; i++) {
231     noit_filter_compile_add(sets[i]);
232   }
233   free(sets);
234 }
235
236 void
237 noit_refresh_filtersets(noit_console_closure_t ncct,
238                         noit_conf_t_userdata_t *info) {
239   noit_filters_from_conf();
240   nc_printf(ncct, "Reloaded %d filtersets.\n",
241             filtersets ? filtersets->size : 0);
242 }
243
244 static noit_boolean
245 noit_apply_filterrule(noit_hash_table *m,
246                       pcre *p, pcre_extra *pe, const char *subj) {
247   int rc, ovector[30];
248   if(m) {
249     void *vptr;
250     return noit_hash_retrieve(m, subj, strlen(subj), &vptr);
251   }
252   if(!p) return noit_true;
253   rc = pcre_exec(p, pe, subj, strlen(subj), 0, 0, ovector, 30);
254   if(rc >= 0) return noit_true;
255   return noit_false;
256 }
257 noit_boolean
258 noit_apply_filterset(const char *filterset,
259                      noit_check_t *check,
260                      metric_t *metric) {
261   /* We pass in filterset here just in case someone wants to apply
262    * a filterset other than check->filterset.. You never know.
263    */
264   void *vfs;
265   if(!filterset) return noit_true;   /* No filter */
266   if(!filtersets) return noit_false; /* Couldn't possibly match */
267
268   LOCKFS();
269   if(noit_hash_retrieve(filtersets, filterset, strlen(filterset), &vfs)) {
270     filterset_t *fs = (filterset_t *)vfs;
271     filterrule_t *r;
272     noit_atomic_inc32(&fs->ref_cnt);
273     UNLOCKFS();
274 #define MATCHES(rname, value) noit_apply_filterrule(r->rname##_ht, r->rname ? r->rname : r->rname##_override, r->rname ? r->rname##_e : NULL, value)
275     for(r = fs->rules; r; r = r->next) {
276       if(MATCHES(target, check->target) &&
277          MATCHES(module, check->module) &&
278          MATCHES(name, check->name) &&
279          MATCHES(metric, metric->metric_name))
280         return (r->type == NOIT_FILTER_ACCEPT) ? noit_true : noit_false;
281     }
282     filterset_free(fs);
283     return noit_false;
284   }
285   UNLOCKFS();
286   return noit_false;
287 }
288
289 static char *
290 conf_t_filterset_prompt(EditLine *el) {
291   noit_console_closure_t ncct;
292   noit_conf_t_userdata_t *info;
293   static char *tl = "noit(conf)# ";
294   static char *pfmt = "noit(conf:filterset:%.*s%s)# ";
295   int max_space, namelen;
296   el_get(el, EL_USERDATA, (void *)&ncct);
297   if(!ncct) return tl;
298   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
299   if(!info) return tl;
300   max_space = sizeof(info->prompt) - strlen(pfmt) + 6 - 1;
301   namelen = strlen(info->filter_name);
302   if(namelen > max_space)
303     snprintf(info->prompt, sizeof(info->prompt), pfmt,
304              max_space - 3, info->filter_name, "...");
305   else
306     snprintf(info->prompt, sizeof(info->prompt), pfmt,
307              namelen, info->filter_name, "");
308   return info->prompt;
309 }
310
311 static int
312 noit_console_filter_show(noit_console_closure_t ncct,
313                          int argc, char **argv,
314                          noit_console_state_t *state,
315                          void *closure) {
316   noit_conf_t_userdata_t *info;
317   char xpath[1024];
318   xmlNodePtr fsnode;
319   noit_conf_section_t *rules;
320   int i, rulecnt;
321
322   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
323   snprintf(xpath, sizeof(xpath), "/%s",
324            info->path);
325   fsnode = noit_conf_get_section(NULL, xpath);
326   if(!fsnode) {
327     nc_printf(ncct, "internal error\n");
328     return -1;
329   }
330   rules = noit_conf_get_sections(fsnode, "rule", &rulecnt);
331   for(i=0; i<rulecnt; i++) {
332     char val[256];
333     val[0] = '\0';
334     noit_conf_get_stringbuf(rules[i], "@type", val, sizeof(val));
335     nc_printf(ncct, "Rule %d [%s]:\n", i+1, val);
336 #define DUMP_ATTR(a) do { \
337   char *vstr; \
338   noit_conf_section_t ht; \
339   int cnt; \
340   ht = noit_conf_get_sections(rules[i], #a, &cnt); \
341   if(ht && cnt) { \
342     nc_printf(ncct, "\t%s: hash match of %d items\n", #a, cnt); \
343   } \
344   else if(noit_conf_get_string(rules[i], "@" #a, &vstr)) { \
345     nc_printf(ncct, "\t%s: /%s/\n", #a, val); \
346     free(vstr); \
347   } \
348   free(ht); \
349 } while(0)
350     DUMP_ATTR(target);
351     DUMP_ATTR(module);
352     DUMP_ATTR(name);
353     DUMP_ATTR(metric);
354   }
355   if(rules) free(rules);
356   return 0;
357 }
358 static int
359 noit_console_rule_configure(noit_console_closure_t ncct,
360                             int argc, char **argv,
361                             noit_console_state_t *state,
362                             void *closure) {
363   xmlNodePtr fsnode = NULL;
364   noit_conf_t_userdata_t *info;
365   char xpath[1024];
366
367   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
368   snprintf(xpath, sizeof(xpath), "/%s",
369            info->path);
370   fsnode = noit_conf_get_section(NULL, xpath);
371   if(!fsnode) {
372     nc_printf(ncct, "internal error");
373     return -1;
374   }
375   if(closure) {
376     int rulenum;
377     xmlNodePtr byebye;
378     /* removing a rule */
379     if(argc != 1) {
380       nc_printf(ncct, "requires one argument\n");
381       return -1;
382     }
383     rulenum = atoi(argv[0]);
384     snprintf(xpath, sizeof(xpath), "rule[%d]", rulenum);
385     byebye = noit_conf_get_section(fsnode, xpath);
386     if(!byebye) {
387       nc_printf(ncct, "cannot find rule\n");
388       return -1;
389     }
390     xmlUnlinkNode(byebye);
391     xmlFreeNode(byebye);
392     nc_printf(ncct, "removed\n");
393   }
394   else {
395     xmlNodePtr (*add_func)(xmlNodePtr, xmlNodePtr);
396     xmlNodePtr add_arg, new_rule;
397     int i, needs_type = 1;
398     if(argc < 1 || argc % 2) {
399       nc_printf(ncct, "even number of arguments required\n");
400       return -1;
401     }
402     if(!strcmp(argv[0], "before") || !strcmp(argv[0], "after")) {
403       int rulenum = atoi(argv[1]);
404       snprintf(xpath, sizeof(xpath), "rule[%d]", rulenum);
405       add_arg = noit_conf_get_section(fsnode, xpath);
406       if(!add_arg) {
407         nc_printf(ncct, "%s rule not found\n", xpath);
408         return -1;
409       }
410       if(*argv[0] == 'b') add_func = xmlAddPrevSibling;
411       else add_func = xmlAddNextSibling;
412       argc -= 2;
413       argv += 2;
414     }
415     else {
416       add_func = xmlAddChild;
417       add_arg = fsnode;
418     }
419     for(i=0;i<argc;i+=2) {
420       if(!strcmp(argv[i], "type")) needs_type = 0;
421       else if(strcmp(argv[i], "target") && strcmp(argv[i], "module") &&
422               strcmp(argv[i], "name") && strcmp(argv[i], "metric")) {
423         nc_printf(ncct, "unknown attribute '%s'\n", argv[i]);
424         return -1;
425       }
426     }
427     if(needs_type) {
428       nc_printf(ncct, "type <allow|deny> is required\n");
429       return -1;
430     }
431     new_rule = xmlNewNode(NULL, (xmlChar *)"rule");
432     for(i=0;i<argc;i+=2)
433       xmlSetProp(new_rule, (xmlChar *)argv[i], (xmlChar *)argv[i+1]);
434     add_func(add_arg, new_rule);
435     noit_filter_compile_add((noit_conf_section_t *)fsnode);
436   }
437   return 0;
438 }
439 static int
440 noit_console_filter_configure(noit_console_closure_t ncct,
441                               int argc, char **argv,
442                               noit_console_state_t *state,
443                               void *closure) {
444   xmlNodePtr parent, fsnode = NULL;
445   int rv = -1;
446   noit_conf_t_userdata_t *info;
447   char xpath[1024];
448
449   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
450   if(!info) {
451     nc_printf(ncct, "internal error\n");
452     goto cleanup;
453   }
454   if(strncmp(info->path, "/filtersets/", strlen("/filtersets/")) &&
455      strcmp(info->path, "/filtersets")) {
456     nc_printf(ncct, "filterset only allows inside /filtersets (not %s)\n",
457               info->path);
458     goto cleanup;
459   }
460   if(argc != 1) {
461     nc_printf(ncct, "filterset requires one argument\n");
462     goto cleanup;
463   }
464   snprintf(xpath, sizeof(xpath), "/%s", info->path);
465   parent = noit_conf_get_section(NULL, xpath);
466   if(!parent) {
467     nc_printf(ncct, "internal error, can't final current working path\n");
468     goto cleanup;
469   }
470   snprintf(xpath, sizeof(xpath), "filterset[@name=\"%s\"]", argv[0]);
471   fsnode = noit_conf_get_section(parent, xpath);
472   if(closure) {
473     int removed;
474     removed = noit_filter_remove(fsnode);
475     nc_printf(ncct, "%sremoved filterset '%s'\n",
476               removed ? "" : "failed to ", argv[0]);
477     if(removed) {
478       xmlUnlinkNode(fsnode);
479       xmlFreeNode(fsnode);
480     }
481     rv = !removed;
482     goto cleanup;
483   }
484   if(!fsnode) {
485     void *vfs;
486     nc_printf(ncct, "Cannot find filterset '%s'\n", argv[0]);
487     LOCKFS();
488     if(noit_hash_retrieve(filtersets, argv[0], strlen(argv[0]), &vfs)) {
489       UNLOCKFS();
490       nc_printf(ncct, "filter of the same name already exists\n");
491       goto cleanup;
492     }
493     UNLOCKFS();
494     /* Fine the parent path */
495     fsnode = xmlNewNode(NULL, (xmlChar *)"filterset");
496     xmlSetProp(fsnode, (xmlChar *)"name", (xmlChar *)argv[0]);
497     xmlAddChild(parent, fsnode);
498     nc_printf(ncct, "created new filterset\n");
499   }
500
501   if(info) {
502     char *xmlpath = NULL;
503     free(info->path);
504     xmlpath = (char *)xmlGetNodePath(fsnode);
505     info->path = strdup(xmlpath + strlen("/noit"));
506     free(xmlpath);
507     strlcpy(info->filter_name, argv[0], sizeof(info->filter_name));
508     if(state) {
509       noit_console_state_push_state(ncct, state);
510       noit_console_state_init(ncct);
511     }
512   }
513  cleanup:
514   return rv;
515 }
516
517 static void
518 register_console_filter_commands() {
519   noit_console_state_t *tl, *filterset_state, *nostate;
520   cmd_info_t *confcmd, *conf_t_cmd, *no_cmd;
521
522   tl = noit_console_state_initial();
523   confcmd = noit_console_state_get_cmd(tl, "configure");
524   assert(confcmd && confcmd->dstate);
525
526   conf_t_cmd = noit_console_state_get_cmd(confcmd->dstate, "terminal");
527   assert(conf_t_cmd && conf_t_cmd->dstate);
528
529   nostate = noit_console_state_alloc();
530   noit_console_state_add_cmd(nostate,
531     NCSCMD("rule", noit_console_rule_configure, NULL, NULL, (void *)1));
532
533   filterset_state = noit_console_state_alloc();
534   noit_console_state_add_cmd(filterset_state,
535     NCSCMD("exit", noit_console_config_cd, NULL, NULL, ".."));
536   noit_console_state_add_cmd(filterset_state,
537     NCSCMD("status", noit_console_filter_show, NULL, NULL, NULL));
538   noit_console_state_add_cmd(filterset_state,
539     NCSCMD("rule", noit_console_rule_configure, NULL, NULL, NULL));
540   noit_console_state_add_cmd(filterset_state,
541     NCSCMD("no", noit_console_state_delegate, noit_console_opt_delegate,
542            nostate, NULL));
543
544   filterset_state->console_prompt_function = conf_t_filterset_prompt;
545
546   noit_console_state_add_cmd(conf_t_cmd->dstate,
547     NCSCMD("filterset", noit_console_filter_configure,
548            NULL, filterset_state, NULL));
549
550   no_cmd = noit_console_state_get_cmd(conf_t_cmd->dstate, "no");
551   assert(no_cmd && no_cmd->dstate);
552   noit_console_state_add_cmd(no_cmd->dstate,
553     NCSCMD("filterset", noit_console_filter_configure, NULL, NULL, (void *)1));
554 }
555
556 void
557 noit_filters_init() {
558   const char *error;
559   int erroffset;
560   pthread_mutex_init(&filterset_lock, NULL);
561   filtersets = calloc(1, sizeof(noit_hash_table));
562   fallback_no_match = pcre_compile("^(?=a)b", 0, &error, &erroffset, NULL);
563   if(!fallback_no_match) {
564     noitL(noit_error, "Filter initialization failed (nomatch filter)\n");
565     exit(-1);
566   }
567   noit_capabilities_add_feature("filterset:hash", NULL);
568   register_console_filter_commands();
569   noit_filters_from_conf();
570 }
Note: See TracBrowser for help on using the browser.