root/src/noit_check_rest.c

Revision 7010bc5b07e615c0314ff1c0ebb27bdf8420cca3, 18.4 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

robust input validation, refs #171

  • 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 #include <assert.h>
35 #include <errno.h>
36 #include <libxml/parser.h>
37 #include <libxml/tree.h>
38 #include <libxml/xpath.h>
39 #include "noit_listener.h"
40 #include "noit_http.h"
41 #include "noit_rest.h"
42 #include "noit_check.h"
43 #include "noit_check_tools.h"
44 #include "noit_conf.h"
45 #include "noit_conf_private.h"
46
47 #define UUID_REGEX "[0-9a-fA-F]{4}(?:[0-9a-fA-F]{4}-){4}[0-9a-fA-F]{12}"
48
49 struct rest_xml_payload {
50   char *buffer;
51   int len;
52   int allocd;
53   int complete;
54 };
55
56 static int
57 rest_show_check(noit_http_rest_closure_t *restc,
58                 int npats, char **pats) {
59   noit_http_session_ctx *ctx = restc->http_ctx;
60   xmlXPathObjectPtr pobj = NULL;
61   xmlXPathContextPtr xpath_ctxt = NULL;
62   xmlDocPtr doc = NULL;
63   xmlNodePtr node, root, attr, config, state, tmp, anode, metrics;
64   uuid_t checkid;
65   noit_check_t *check;
66   char xpath[1024], *uuid_conf, *module, *value;
67   int rv, cnt;
68   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
69   const char *k;
70   int klen;
71   void *data;
72   noit_hash_table *configh;
73
74   if(npats != 2) goto error;
75
76   rv = noit_check_xpath(xpath, sizeof(xpath), pats[0], pats[1]);
77   if(rv == 0) goto not_found;
78   if(rv < 0) goto error;
79
80   noit_conf_xml_xpath(NULL, &xpath_ctxt);
81   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
82   if(!pobj || pobj->type != XPATH_NODESET ||
83      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto not_found;
84   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
85   if(cnt != 1) goto error;
86
87   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
88   uuid_conf = (char *)xmlGetProp(node, (xmlChar *)"uuid");
89   if(!uuid_conf || uuid_parse(uuid_conf, checkid)) goto error;
90
91   doc = xmlNewDoc((xmlChar *)"1.0");
92   root = xmlNewDocNode(doc, NULL, (xmlChar *)"check", NULL);
93   xmlDocSetRootElement(doc, root);
94
95 #define MYATTR(node,a,n,b) _noit_conf_get_string(node, &(n), "@" #a, &(b))
96 #define INHERIT(node,a,n,b) \
97   _noit_conf_get_string(node, &(n), "ancestor-or-self::node()/@" #a, &(b))
98 #define SHOW_ATTR(parent, node, a) do { \
99   xmlNodePtr anode = NULL; \
100   char *value = NULL; \
101   INHERIT(node, a, anode, value); \
102   if(value != NULL) { \
103     int clen, plen;\
104     const char *cpath, *apath; \
105     xmlNodePtr child; \
106     cpath = node ? (char *)xmlGetNodePath(node) : ""; \
107     apath = anode ? (char *)xmlGetNodePath(anode) : ""; \
108     clen = strlen(cpath); \
109     plen = strlen("/noit/checks"); \
110     child = xmlNewNode(NULL, (xmlChar *)#a); \
111     xmlNodeAddContent(child, (xmlChar *)value); \
112     if(!strncmp(cpath, apath, clen) && apath[clen] == '/') { \
113     } \
114     else { \
115       xmlSetProp(child, (xmlChar *)"inherited", (xmlChar *)apath+plen); \
116     } \
117     xmlAddChild(parent, child); \
118   } \
119 } while(0)
120 #define NODE_CONTENT(parent, k, v) do { \
121   xmlNodePtr tmp; \
122   if(v) { \
123     tmp = xmlNewNode(NULL, (xmlChar *)(k)); \
124     xmlNodeAddContent(tmp, (xmlChar *)(v)); \
125     xmlAddChild(parent, tmp); \
126   } \
127 } while(0)
128
129   attr = xmlNewNode(NULL, (xmlChar *)"attributes");
130   xmlAddChild(root, attr);
131
132   /* Name is odd, it falls back transparently to module */
133   if(!INHERIT(node, module, tmp, module)) module = NULL;
134   xmlAddChild(attr, (tmp = xmlNewNode(NULL, (xmlChar *)"name")));
135   if(MYATTR(node, name, anode, value))
136     xmlNodeAddContent(tmp, (xmlChar *)value);
137   else if(module)
138     xmlNodeAddContent(tmp, (xmlChar *)module);
139
140   SHOW_ATTR(attr,node,module);
141   SHOW_ATTR(attr,node,target);
142   SHOW_ATTR(attr,node,period);
143   SHOW_ATTR(attr,node,timeout);
144   SHOW_ATTR(attr,node,oncheck);
145   SHOW_ATTR(attr,node,filterset);
146   SHOW_ATTR(attr,node,disable);
147
148   /* Add the config */
149   config = xmlNewNode(NULL, (xmlChar *)"config");
150   configh = noit_conf_get_hash(node, "config");
151   while(noit_hash_next(configh, &iter, &k, &klen, &data))
152     NODE_CONTENT(config, k, data);
153   noit_hash_destroy(configh, free, free);
154   free(configh);
155   xmlAddChild(root, config);
156
157   /* Add the state */
158   xmlAddChild(root, (state = xmlNewNode(NULL, (xmlChar *)"state")));
159   check = noit_poller_lookup(checkid);
160   if(!check)
161     xmlSetProp(state, (xmlChar *)"error", (xmlChar *)"true");
162   else {
163     stats_t *c = &check->stats.current;
164     NODE_CONTENT(state, "running", NOIT_CHECK_RUNNING(check)?"true":"false");
165     NODE_CONTENT(state, "killed", NOIT_CHECK_KILLED(check)?"true":"false");
166     NODE_CONTENT(state, "configured",
167                  NOIT_CHECK_CONFIGURED(check)?"true":"false");
168     NODE_CONTENT(state, "disabled", NOIT_CHECK_DISABLED(check)?"true":"false");
169     xmlAddChild(state, (tmp = xmlNewNode(NULL, (xmlChar *)"last_run")));
170     if(check->stats.current.whence.tv_sec) {
171       struct timeval f = check->stats.current.whence;
172       struct timeval n;
173       char timestr[20];
174       gettimeofday(&n, NULL);
175       snprintf(timestr, sizeof(timestr), "%0.3f",
176                n.tv_sec + (n.tv_usec / 1000000.0));
177       xmlSetProp(tmp, (xmlChar *)"now", (xmlChar *)timestr);
178       snprintf(timestr, sizeof(timestr), "%0.3f",
179                f.tv_sec + (f.tv_usec / 1000000.0));
180       xmlNodeAddContent(tmp, (xmlChar *)timestr);
181     }
182     if(c->available) { /* truth here means the check has been run */
183       char buff[20];
184       snprintf(buff, sizeof(buff), "%0.3f", (float)c->duration/1000.0);
185       NODE_CONTENT(state, "runtime", buff);
186     }
187     NODE_CONTENT(state, "availability",
188                  noit_check_available_string(c->available));
189     NODE_CONTENT(state, "state", noit_check_state_string(c->state));
190     NODE_CONTENT(state, "status", c->status ? c->status : "");
191     memset(&iter, 0, sizeof(iter));
192     xmlAddChild(state, (metrics = xmlNewNode(NULL, (xmlChar *)"metrics")));
193     while(noit_hash_next(&c->metrics, &iter, &k, &klen, &data)) {
194       char buff[256];
195       metric_t *m = (metric_t *)data;
196       xmlAddChild(metrics, (tmp = xmlNewNode(NULL, (xmlChar *)m->metric_name)));
197       buff[0] = m->metric_type; buff[1] = '\0';
198       xmlSetProp(tmp, (xmlChar *)"type", (xmlChar *)buff);
199       if(m->metric_value.s) {
200         int rv;
201         rv = noit_stats_snprint_metric_value(buff, sizeof(buff), m);
202         if(rv < 0)
203           xmlSetProp(tmp, (xmlChar *)"error", (xmlChar *)"unknown type");
204         else
205           xmlNodeAddContent(tmp, (xmlChar *)buff);
206       }
207     }
208
209   }
210   noit_http_response_ok(ctx, "text/xml");
211   noit_http_response_xml(ctx, doc);
212   noit_http_response_end(ctx);
213   goto cleanup;
214
215  not_found:
216   noit_http_response_not_found(ctx, "text/html");
217   noit_http_response_end(ctx);
218   goto cleanup;
219
220  error:
221   noit_http_response_server_error(ctx, "text/html");
222   noit_http_response_end(ctx);
223   goto cleanup;
224
225  cleanup:
226   if(pobj) xmlXPathFreeObject(pobj);
227   if(doc) xmlFreeDoc(doc);
228   return 0;
229 }
230
231 static void
232 rest_xml_payload_free(void *f) {
233   struct rest_xml_payload *xmlin = f;
234   if(xmlin->buffer) free(xmlin->buffer);
235 }
236
237 static int
238 validate_check_post(xmlDocPtr doc, xmlNodePtr *a, xmlNodePtr *c,
239                     const char **error) {
240   xmlNodePtr root, tl, an;
241   int name=0, module=0, target=0, period=0, timeout=0, filterset=0;
242   *a = *c = NULL;
243   root = xmlDocGetRootElement(doc);
244   if(!root || strcmp((char *)root->name, "check")) return 0;
245   for(tl = root->children; tl; tl = tl->next) {
246     if(!strcmp((char *)tl->name, "attributes")) {
247       *a = tl->children;
248       for(an = tl->children; an; an = an->next) {
249 #define CHECK_N_SET(a) if(!strcmp((char *)an->name, #a))
250         CHECK_N_SET(name) {
251           xmlChar *tmp;
252           pcre *valid_name = noit_conf_get_valid_name_checker();
253           int ovector[30], valid;
254           tmp = xmlNodeGetContent(an);
255           valid = (pcre_exec(valid_name, NULL,
256                              (char *)tmp, strlen((char *)tmp), 0, 0,
257                              ovector, sizeof(ovector)/sizeof(*ovector)) > 0);
258           xmlFree(tmp);
259           if(!valid) { *error = "invalid name"; return 0; }
260           name = 1;
261         }
262         else CHECK_N_SET(module) module = 1; /* This is validated by called */
263         else CHECK_N_SET(target) {
264           int valid;
265           xmlChar *tmp;
266           tmp = xmlNodeGetContent(an);
267           valid = noit_check_is_valid_target((char *)tmp);
268           xmlFree(tmp);
269           if(!valid) { *error = "invalid target"; return 0; }
270           target = 1;
271         }
272         else CHECK_N_SET(period) {
273           int pint;
274           xmlChar *tmp;
275           tmp = xmlNodeGetContent(an);
276           pint = noit_conf_string_to_int((char *)tmp);
277           xmlFree(tmp);
278           if(pint < 5000 || pint > 300000) {
279             *error = "invalid period";
280             return 0;
281           }
282           period = 1;
283         }
284         else CHECK_N_SET(timeout) {
285           int pint;
286           xmlChar *tmp;
287           tmp = xmlNodeGetContent(an);
288           pint = noit_conf_string_to_int((char *)tmp);
289           xmlFree(tmp);
290           if(pint < 0 || pint > 300000) {
291             *error = "invalid timeout";
292             return 0;
293           }
294           timeout = 1;
295         }
296         else CHECK_N_SET(filterset) filterset = 1;
297         else CHECK_N_SET(disable) { /* not required */
298           int valid;
299           xmlChar *tmp;
300           tmp = xmlNodeGetContent(an);
301           valid = (!strcasecmp((char *)tmp, "true") ||
302                    !strcasecmp((char *)tmp, "on") ||
303                    !strcasecmp((char *)tmp, "false") ||
304                    !strcasecmp((char *)tmp, "off"));
305           xmlFree(tmp);
306           if(!valid) { *error = "bad disable parameter"; return 0; }
307           target = 1;
308         }
309         else return 0;
310       }
311     }
312     else if(!strcmp((char *)tl->name, "config")) {
313       *c = tl->children;
314       /* Noop, anything goes */
315     }
316     else return 0;
317   }
318   if(name && module && target && period && timeout && filterset) return 1;
319   *error = "insufficient information";
320   return 0;
321 }
322 static void
323 configure_xml_check(xmlNodePtr check, xmlNodePtr a, xmlNodePtr c) {
324   xmlNodePtr n, config, oldconfig;
325   for(n = a; n; n = n->next) {
326 #define ATTR2PROP(attr) do { \
327   if(!strcmp((char *)n->name, #attr)) { \
328     xmlChar *v = xmlNodeGetContent(n); \
329     if(v) xmlSetProp(check, n->name, v); \
330     else xmlUnsetProp(check, n->name); \
331     if(v) xmlFree(v); \
332   } \
333 } while(0)
334     ATTR2PROP(name);
335     ATTR2PROP(target);
336     ATTR2PROP(module);
337     ATTR2PROP(period);
338     ATTR2PROP(timeout);
339     ATTR2PROP(disable);
340     ATTR2PROP(filter);
341   }
342   for(oldconfig = check->children; oldconfig; oldconfig = oldconfig->next)
343     if(!strcmp((char *)oldconfig->name, "config")) break;
344   config = xmlNewNode(NULL, (xmlChar *)"config");
345   for(n = c; n; n = n->next) {
346     xmlNodePtr co = xmlNewNode(NULL, n->name);
347     xmlNodeAddContent(co, XML_GET_CONTENT(n));
348     xmlAddChild(config, co);
349   }
350   if(oldconfig) {
351     xmlReplaceNode(oldconfig, config);
352     xmlFreeNode(oldconfig);
353   }
354   else xmlAddChild(check, config);
355 }
356 static xmlNodePtr
357 make_conf_path(char *path) {
358   xmlNodePtr start, tmp;
359   char fullpath[1024], *tok, *brk;
360   if(!path || strlen(path) < 1) return NULL;
361   snprintf(fullpath, sizeof(fullpath), "%s", path+1);
362   fullpath[strlen(fullpath)-1] = '\0';
363   start = noit_conf_get_section(NULL, "/noit/checks");
364   if(!start) return NULL;
365   for (tok = strtok_r(fullpath, "/", &brk);
366        tok;
367        tok = strtok_r(NULL, "/", &brk)) {
368     if(!xmlValidateNameValue((xmlChar *)tok)) return NULL;
369     if(!strcmp(tok, "check")) return NULL;  /* These two paths */
370     if(!strcmp(tok, "config")) return NULL; /* are off limits. */
371     for (tmp = start->children; tmp; tmp = tmp->next) {
372       if(!strcmp((char *)tmp->name, tok)) break;
373     }
374     if(!tmp) {
375       tmp = xmlNewNode(NULL, (xmlChar *)tok);
376       xmlAddChild(start, tmp);
377     }
378     start = tmp;
379   }
380   return start;
381 }
382 static int
383 rest_set_check(noit_http_rest_closure_t *restc,
384                int npats, char **pats) {
385   noit_http_session_ctx *ctx = restc->http_ctx;
386   xmlXPathObjectPtr pobj = NULL;
387   xmlXPathContextPtr xpath_ctxt = NULL;
388   xmlDocPtr doc = NULL, indoc = NULL;
389   xmlNodePtr node, root, attr, config, parent;
390   uuid_t checkid;
391   noit_check_t *check;
392   char xpath[1024], *uuid_conf;
393   int rv, cnt;
394   const char *error = "internal error";
395   noit_boolean exists = noit_false;
396   struct rest_xml_payload *rxc;
397
398   if(npats != 2) goto error;
399
400 #define FAIL(a) do { error = (a); goto error; } while(0)
401
402   if(restc->call_closure == NULL) {
403     rxc = restc->call_closure = calloc(1, sizeof(*rxc));
404     restc->call_closure_free = rest_xml_payload_free;
405   }
406   rxc = restc->call_closure;
407   while(!rxc->complete) {
408     int len, mask;
409     if(rxc->len == rxc->allocd) {
410       char *b;
411       rxc->allocd += 32768;
412       b = rxc->buffer ? realloc(rxc->buffer, rxc->allocd) :
413                         malloc(rxc->allocd);
414       if(!b) FAIL("alloc failed");
415       rxc->buffer = b;
416     }
417     len = noit_http_session_req_consume(restc->http_ctx,
418                                         rxc->buffer + rxc->len,
419                                         rxc->allocd - rxc->len,
420                                         &mask);
421     if(len > 0) rxc->len += len;
422     if(len < 0 && errno == EAGAIN) return mask;
423     if(rxc->len == restc->http_ctx->req.content_length) rxc->complete = 1;
424   }
425
426   indoc = xmlParseMemory(rxc->buffer, rxc->len);
427   if(indoc == NULL) FAIL("xml parse error");
428   if(!validate_check_post(indoc, &attr, &config, &error)) goto error;
429
430   if(uuid_parse(pats[1], checkid)) goto error;
431   check = noit_poller_lookup(checkid);
432   if(check)
433     exists = noit_true;
434
435   rv = noit_check_xpath(xpath, sizeof(xpath), pats[0], pats[1]);
436   if(rv == 0) FAIL("uuid not valid");
437   if(rv < 0) FAIL("Tricky McTrickster... No");
438
439   noit_conf_xml_xpath(NULL, &xpath_ctxt);
440   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
441   if(!pobj || pobj->type != XPATH_NODESET ||
442      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
443     if(exists) FAIL("uuid not yours");
444     else {
445       char *target = NULL, *name = NULL, *module = NULL;
446       noit_module_t *m;
447       xmlNodePtr newcheck, a;
448       /* make sure this isn't a dup */
449       for(a = attr; a; a = a->next) {
450         if(!strcmp((char *)a->name, "target"))
451           target = (char *)xmlNodeGetContent(a);
452         if(!strcmp((char *)a->name, "name"))
453           name = (char *)xmlNodeGetContent(a);
454         if(!strcmp((char *)a->name, "module"))
455           module = (char *)xmlNodeGetContent(a);
456       }
457       exists = (noit_poller_lookup_by_name(target, name) != NULL);
458       m = noit_module_lookup(module);
459       xmlFree(target);
460       xmlFree(name);
461       xmlFree(module);
462       if(exists) FAIL("target`name already registered");
463       if(!m) FAIL("module does not exist");
464       /* create a check here */
465       newcheck = xmlNewNode(NULL, (xmlChar *)"check");
466       xmlSetProp(newcheck, (xmlChar *)"uuid", (xmlChar *)pats[1]);
467       configure_xml_check(newcheck, attr, config);
468       parent = make_conf_path(pats[0]);
469       if(!parent) FAIL("invalid path");
470       xmlAddChild(parent, newcheck);
471     }
472   }
473   if(exists) {
474     int module_change;
475     char *target, *name, *module;
476     xmlNodePtr a;
477     noit_check_t *ocheck;
478     cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
479     if(cnt != 1) FAIL("internal error, |checkid| > 1");
480     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
481     uuid_conf = (char *)xmlGetProp(node, (xmlChar *)"uuid");
482     if(!uuid_conf || strcasecmp(uuid_conf, pats[1]))
483       FAIL("internal error uuid");
484     /* update check here */
485
486     /* make sure this isn't a dup */
487     for(a = attr; a; a = a->next) {
488       if(!strcmp((char *)a->name, "target"))
489         target = (char *)xmlNodeGetContent(a);
490       if(!strcmp((char *)a->name, "name"))
491         name = (char *)xmlNodeGetContent(a);
492       if(!strcmp((char *)a->name, "module"))
493         module = (char *)xmlNodeGetContent(a);
494     }
495     ocheck = noit_poller_lookup_by_name(target, name);
496     module_change = strcmp(check->module, module);
497     xmlFree(target);
498     xmlFree(name);
499     xmlFree(module);
500     if(ocheck && ocheck != check) FAIL("new target`name would collide");
501     if(module_change) FAIL("cannot change module");
502     configure_xml_check(node, attr, config);
503     parent = make_conf_path(pats[0]);
504     if(!parent) FAIL("invalid path");
505     xmlUnlinkNode(node);
506     xmlAddChild(parent, node);
507   }
508
509   noit_conf_mark_changed();
510   noit_poller_reload(xpath);
511   if(restc->call_closure_free) restc->call_closure_free(restc->call_closure);
512   restc->call_closure_free = NULL;
513   restc->call_closure = NULL;
514   if(pobj) xmlXPathFreeObject(pobj);
515   if(doc) xmlFreeDoc(doc);
516   if(indoc) xmlFreeDoc(indoc);
517   restc->fastpath = rest_show_check;
518   return restc->fastpath(restc, restc->nparams, restc->params);
519
520  error:
521   noit_http_response_server_error(ctx, "text/xml");
522   doc = xmlNewDoc((xmlChar *)"1.0");
523   root = xmlNewDocNode(doc, NULL, (xmlChar *)"error", NULL);
524   xmlDocSetRootElement(doc, root);
525   xmlNodeAddContent(root, (xmlChar *)error);
526   noit_http_response_xml(ctx, doc);
527   noit_http_response_end(ctx);
528   goto cleanup;
529
530  cleanup:
531   if(pobj) xmlXPathFreeObject(pobj);
532   if(doc) xmlFreeDoc(doc);
533   if(indoc) xmlFreeDoc(indoc);
534   return 0;
535 }
536
537 void
538 noit_check_rest_init() {
539   assert(noit_http_rest_register(
540     "GET",
541     "/checks/",
542     "^show(/.*)(?<=/)(" UUID_REGEX ")$",
543     rest_show_check
544   ) == 0);
545   assert(noit_http_rest_register(
546     "POST",
547     "/checks/",
548     "^set(/.*)(?<=/)(" UUID_REGEX ")$",
549     rest_set_check
550   ) == 0);
551 }
552
Note: See TracBrowser for help on using the browser.