curl plugin: Fixed a segfault when using <Match> without an Instance.
[collectd.git] / src / curl.c
1 /**
2  * collectd - src/curl.c
3  * Copyright (C) 2006-2009  Florian octo Forster
4  * Copyright (C) 2009       Aman Gupta
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  *   Aman Gupta <aman at tmm1.net>
22  **/
23
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"
27 #include "configfile.h"
28 #include "utils_match.h"
29
30 #include <curl/curl.h>
31
32 /*
33  * Data types
34  */
35 struct web_match_s;
36 typedef struct web_match_s web_match_t;
37 struct web_match_s /* {{{ */
38 {
39   char *regex;
40   char *exclude_regex;
41   int dstype;
42   char *type;
43   char *instance;
44
45   cu_match_t *match;
46
47   web_match_t *next;
48 }; /* }}} */
49
50 struct web_page_s;
51 typedef struct web_page_s web_page_t;
52 struct web_page_s /* {{{ */
53 {
54   char *instance;
55
56   char *url;
57   char *user;
58   char *pass;
59   char *credentials;
60   int   verify_peer;
61   int   verify_host;
62   char *cacert;
63   struct curl_slist *headers;
64   char *post_body;
65   int   response_time;
66
67   CURL *curl;
68   char curl_errbuf[CURL_ERROR_SIZE];
69   char *buffer;
70   size_t buffer_size;
71   size_t buffer_fill;
72
73   web_match_t *matches;
74
75   web_page_t *next;
76 }; /* }}} */
77
78 /*
79  * Global variables;
80  */
81 /* static CURLM *curl = NULL; */
82 static web_page_t *pages_g = NULL;
83
84 /*
85  * Private functions
86  */
87 static size_t cc_curl_callback (void *buf, /* {{{ */
88     size_t size, size_t nmemb, void *user_data)
89 {
90   web_page_t *wp;
91   size_t len;
92   
93   len = size * nmemb;
94   if (len <= 0)
95     return (len);
96
97   wp = user_data;
98   if (wp == NULL)
99     return (0);
100
101   if ((wp->buffer_fill + len) >= wp->buffer_size)
102   {
103     char *temp;
104     size_t temp_size;
105
106     temp_size = wp->buffer_fill + len + 1;
107     temp = (char *) realloc (wp->buffer, temp_size);
108     if (temp == NULL)
109     {
110       ERROR ("curl plugin: realloc failed.");
111       return (0);
112     }
113     wp->buffer = temp;
114     wp->buffer_size = temp_size;
115   }
116
117   memcpy (wp->buffer + wp->buffer_fill, (char *) buf, len);
118   wp->buffer_fill += len;
119   wp->buffer[wp->buffer_fill] = 0;
120
121   return (len);
122 } /* }}} size_t cc_curl_callback */
123
124 static void cc_web_match_free (web_match_t *wm) /* {{{ */
125 {
126   if (wm == NULL)
127     return;
128
129   sfree (wm->regex);
130   sfree (wm->type);
131   sfree (wm->instance);
132   match_destroy (wm->match);
133   cc_web_match_free (wm->next);
134   sfree (wm);
135 } /* }}} void cc_web_match_free */
136
137 static void cc_web_page_free (web_page_t *wp) /* {{{ */
138 {
139   if (wp == NULL)
140     return;
141
142   if (wp->curl != NULL)
143     curl_easy_cleanup (wp->curl);
144   wp->curl = NULL;
145
146   sfree (wp->instance);
147
148   sfree (wp->url);
149   sfree (wp->user);
150   sfree (wp->pass);
151   sfree (wp->credentials);
152   sfree (wp->cacert);
153   sfree (wp->post_body);
154   curl_slist_free_all (wp->headers);
155
156   sfree (wp->buffer);
157
158   cc_web_match_free (wp->matches);
159   cc_web_page_free (wp->next);
160   sfree (wp);
161 } /* }}} void cc_web_page_free */
162
163 static int cc_config_add_string (const char *name, char **dest, /* {{{ */
164     oconfig_item_t *ci)
165 {
166   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
167   {
168     WARNING ("curl plugin: `%s' needs exactly one string argument.", name);
169     return (-1);
170   }
171
172   sfree (*dest);
173   *dest = strdup (ci->values[0].value.string);
174   if (*dest == NULL)
175     return (-1);
176
177   return (0);
178 } /* }}} int cc_config_add_string */
179
180 static int cc_config_append_string (const char *name, struct curl_slist **dest, /* {{{ */
181     oconfig_item_t *ci)
182 {
183   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
184   {
185     WARNING ("curl plugin: `%s' needs exactly one string argument.", name);
186     return (-1);
187   }
188
189   *dest = curl_slist_append(*dest, ci->values[0].value.string);
190   if (*dest == NULL)
191     return (-1);
192
193   return (0);
194 } /* }}} int cc_config_append_string */
195
196
197 static int cc_config_set_boolean (const char *name, int *dest, /* {{{ */
198     oconfig_item_t *ci)
199 {
200   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
201   {
202     WARNING ("curl plugin: `%s' needs exactly one boolean argument.", name);
203     return (-1);
204   }
205
206   *dest = ci->values[0].value.boolean ? 1 : 0;
207
208   return (0);
209 } /* }}} int cc_config_set_boolean */
210
211 static int cc_config_add_match_dstype (int *dstype_ret, /* {{{ */
212     oconfig_item_t *ci)
213 {
214   int dstype;
215
216   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
217   {
218     WARNING ("curl plugin: `DSType' needs exactly one string argument.");
219     return (-1);
220   }
221
222   if (strncasecmp ("Gauge", ci->values[0].value.string,
223         strlen ("Gauge")) == 0)
224   {
225     dstype = UTILS_MATCH_DS_TYPE_GAUGE;
226     if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
227       dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
228     else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
229       dstype |= UTILS_MATCH_CF_GAUGE_MIN;
230     else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
231       dstype |= UTILS_MATCH_CF_GAUGE_MAX;
232     else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
233       dstype |= UTILS_MATCH_CF_GAUGE_LAST;
234     else
235       dstype = 0;
236   }
237   else if (strncasecmp ("Counter", ci->values[0].value.string,
238         strlen ("Counter")) == 0)
239   {
240     dstype = UTILS_MATCH_DS_TYPE_COUNTER;
241     if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
242       dstype |= UTILS_MATCH_CF_COUNTER_SET;
243     else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
244       dstype |= UTILS_MATCH_CF_COUNTER_ADD;
245     else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
246       dstype |= UTILS_MATCH_CF_COUNTER_INC;
247     else
248       dstype = 0;
249   }
250 else if (strncasecmp ("Derive", ci->values[0].value.string,
251         strlen ("Derive")) == 0)
252   {
253     dstype = UTILS_MATCH_DS_TYPE_DERIVE;
254     if (strcasecmp ("DeriveSet", ci->values[0].value.string) == 0)
255       dstype |= UTILS_MATCH_CF_DERIVE_SET;
256     else if (strcasecmp ("DeriveAdd", ci->values[0].value.string) == 0)
257       dstype |= UTILS_MATCH_CF_DERIVE_ADD;
258     else if (strcasecmp ("DeriveInc", ci->values[0].value.string) == 0)
259       dstype |= UTILS_MATCH_CF_DERIVE_INC;
260     else
261       dstype = 0;
262   }
263 else if (strncasecmp ("Absolute", ci->values[0].value.string,
264         strlen ("Absolute")) == 0)
265   {
266     dstype = UTILS_MATCH_DS_TYPE_ABSOLUTE;
267     if (strcasecmp ("AbsoluteSet", ci->values[0].value.string) == 0) /* Absolute DS is reset-on-read so no sense doin anything else but set */
268       dstype |= UTILS_MATCH_CF_ABSOLUTE_SET;
269     else
270       dstype = 0;
271   }
272
273   else
274   {
275     dstype = 0;
276   }
277
278   if (dstype == 0)
279   {
280     WARNING ("curl plugin: `%s' is not a valid argument to `DSType'.",
281         ci->values[0].value.string);
282     return (-1);
283   }
284
285   *dstype_ret = dstype;
286   return (0);
287 } /* }}} int cc_config_add_match_dstype */
288
289 static int cc_config_add_match (web_page_t *page, /* {{{ */
290     oconfig_item_t *ci)
291 {
292   web_match_t *match;
293   int status;
294   int i;
295
296   if (ci->values_num != 0)
297   {
298     WARNING ("curl plugin: Ignoring arguments for the `Match' block.");
299   }
300
301   match = (web_match_t *) malloc (sizeof (*match));
302   if (match == NULL)
303   {
304     ERROR ("curl plugin: malloc failed.");
305     return (-1);
306   }
307   memset (match, 0, sizeof (*match));
308
309   status = 0;
310   for (i = 0; i < ci->children_num; i++)
311   {
312     oconfig_item_t *child = ci->children + i;
313
314     if (strcasecmp ("Regex", child->key) == 0)
315       status = cc_config_add_string ("Regex", &match->regex, child);
316     else if (strcasecmp ("ExcludeRegex", child->key) == 0)
317       status = cc_config_add_string ("ExcludeRegex", &match->exclude_regex, child);
318     else if (strcasecmp ("DSType", child->key) == 0)
319       status = cc_config_add_match_dstype (&match->dstype, child);
320     else if (strcasecmp ("Type", child->key) == 0)
321       status = cc_config_add_string ("Type", &match->type, child);
322     else if (strcasecmp ("Instance", child->key) == 0)
323       status = cc_config_add_string ("Instance", &match->instance, child);
324     else
325     {
326       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
327       status = -1;
328     }
329
330     if (status != 0)
331       break;
332   } /* for (i = 0; i < ci->children_num; i++) */
333
334   while (status == 0)
335   {
336     if (match->regex == NULL)
337     {
338       WARNING ("curl plugin: `Regex' missing in `Match' block.");
339       status = -1;
340     }
341
342     if (match->type == NULL)
343     {
344       WARNING ("curl plugin: `Type' missing in `Match' block.");
345       status = -1;
346     }
347
348     if (match->dstype == 0)
349     {
350       WARNING ("curl plugin: `DSType' missing in `Match' block.");
351       status = -1;
352     }
353
354     break;
355   } /* while (status == 0) */
356
357   if (status != 0)
358     return (status);
359
360   match->match = match_create_simple (match->regex, match->exclude_regex,
361       match->dstype);
362   if (match->match == NULL)
363   {
364     ERROR ("curl plugin: tail_match_add_match_simple failed.");
365     cc_web_match_free (match);
366     return (-1);
367   }
368   else
369   {
370     web_match_t *prev;
371
372     prev = page->matches;
373     while ((prev != NULL) && (prev->next != NULL))
374       prev = prev->next;
375
376     if (prev == NULL)
377       page->matches = match;
378     else
379       prev->next = match;
380   }
381
382   return (0);
383 } /* }}} int cc_config_add_match */
384
385 static int cc_page_init_curl (web_page_t *wp) /* {{{ */
386 {
387   wp->curl = curl_easy_init ();
388   if (wp->curl == NULL)
389   {
390     ERROR ("curl plugin: curl_easy_init failed.");
391     return (-1);
392   }
393
394   curl_easy_setopt (wp->curl, CURLOPT_NOSIGNAL, 1L);
395   curl_easy_setopt (wp->curl, CURLOPT_WRITEFUNCTION, cc_curl_callback);
396   curl_easy_setopt (wp->curl, CURLOPT_WRITEDATA, wp);
397   curl_easy_setopt (wp->curl, CURLOPT_USERAGENT,
398       PACKAGE_NAME"/"PACKAGE_VERSION);
399   curl_easy_setopt (wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
400   curl_easy_setopt (wp->curl, CURLOPT_URL, wp->url);
401   curl_easy_setopt (wp->curl, CURLOPT_FOLLOWLOCATION, 1L);
402   curl_easy_setopt (wp->curl, CURLOPT_MAXREDIRS, 50L);
403
404   if (wp->user != NULL)
405   {
406     size_t credentials_size;
407
408     credentials_size = strlen (wp->user) + 2;
409     if (wp->pass != NULL)
410       credentials_size += strlen (wp->pass);
411
412     wp->credentials = (char *) malloc (credentials_size);
413     if (wp->credentials == NULL)
414     {
415       ERROR ("curl plugin: malloc failed.");
416       return (-1);
417     }
418
419     ssnprintf (wp->credentials, credentials_size, "%s:%s",
420         wp->user, (wp->pass == NULL) ? "" : wp->pass);
421     curl_easy_setopt (wp->curl, CURLOPT_USERPWD, wp->credentials);
422   }
423
424   curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYPEER, (long) wp->verify_peer);
425   curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYHOST,
426       wp->verify_host ? 2L : 0L);
427   if (wp->cacert != NULL)
428     curl_easy_setopt (wp->curl, CURLOPT_CAINFO, wp->cacert);
429   if (wp->headers != NULL)
430     curl_easy_setopt (wp->curl, CURLOPT_HTTPHEADER, wp->headers);
431   if (wp->post_body != NULL)
432     curl_easy_setopt (wp->curl, CURLOPT_POSTFIELDS, wp->post_body);
433
434   return (0);
435 } /* }}} int cc_page_init_curl */
436
437 static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
438 {
439   web_page_t *page;
440   int status;
441   int i;
442
443   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
444   {
445     WARNING ("curl plugin: `Page' blocks need exactly one string argument.");
446     return (-1);
447   }
448
449   page = (web_page_t *) malloc (sizeof (*page));
450   if (page == NULL)
451   {
452     ERROR ("curl plugin: malloc failed.");
453     return (-1);
454   }
455   memset (page, 0, sizeof (*page));
456   page->url = NULL;
457   page->user = NULL;
458   page->pass = NULL;
459   page->verify_peer = 1;
460   page->verify_host = 1;
461   page->response_time = 0;
462
463   page->instance = strdup (ci->values[0].value.string);
464   if (page->instance == NULL)
465   {
466     ERROR ("curl plugin: strdup failed.");
467     sfree (page);
468     return (-1);
469   }
470
471   /* Process all children */
472   status = 0;
473   for (i = 0; i < ci->children_num; i++)
474   {
475     oconfig_item_t *child = ci->children + i;
476
477     if (strcasecmp ("URL", child->key) == 0)
478       status = cc_config_add_string ("URL", &page->url, child);
479     else if (strcasecmp ("User", child->key) == 0)
480       status = cc_config_add_string ("User", &page->user, child);
481     else if (strcasecmp ("Password", child->key) == 0)
482       status = cc_config_add_string ("Password", &page->pass, child);
483     else if (strcasecmp ("VerifyPeer", child->key) == 0)
484       status = cc_config_set_boolean ("VerifyPeer", &page->verify_peer, child);
485     else if (strcasecmp ("VerifyHost", child->key) == 0)
486       status = cc_config_set_boolean ("VerifyHost", &page->verify_host, child);
487     else if (strcasecmp ("MeasureResponseTime", child->key) == 0)
488       status = cc_config_set_boolean (child->key, &page->response_time, child);
489     else if (strcasecmp ("CACert", child->key) == 0)
490       status = cc_config_add_string ("CACert", &page->cacert, child);
491     else if (strcasecmp ("Match", child->key) == 0)
492       /* Be liberal with failing matches => don't set `status'. */
493       cc_config_add_match (page, child);
494     else if (strcasecmp ("Header", child->key) == 0)
495       status = cc_config_append_string ("Header", &page->headers, child);
496     else if (strcasecmp ("Post", child->key) == 0)
497       status = cc_config_add_string ("Post", &page->post_body, child);
498     else
499     {
500       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
501       status = -1;
502     }
503
504     if (status != 0)
505       break;
506   } /* for (i = 0; i < ci->children_num; i++) */
507
508   /* Additionial sanity checks and libCURL initialization. */
509   while (status == 0)
510   {
511     if (page->url == NULL)
512     {
513       WARNING ("curl plugin: `URL' missing in `Page' block.");
514       status = -1;
515     }
516
517     if (page->matches == NULL && !page->response_time)
518     {
519       assert (page->instance != NULL);
520       WARNING ("curl plugin: No (valid) `Match' block "
521           "or MeasureResponseTime within `Page' block `%s'.", page->instance);
522       status = -1;
523     }
524
525     if (status == 0)
526       status = cc_page_init_curl (page);
527
528     break;
529   } /* while (status == 0) */
530
531   if (status != 0)
532   {
533     cc_web_page_free (page);
534     return (status);
535   }
536
537   /* Add the new page to the linked list */
538   if (pages_g == NULL)
539     pages_g = page;
540   else
541   {
542     web_page_t *prev;
543
544     prev = pages_g;
545     while ((prev != NULL) && (prev->next != NULL))
546       prev = prev->next;
547     prev->next = page;
548   }
549
550   return (0);
551 } /* }}} int cc_config_add_page */
552
553 static int cc_config (oconfig_item_t *ci) /* {{{ */
554 {
555   int success;
556   int errors;
557   int status;
558   int i;
559
560   success = 0;
561   errors = 0;
562
563   for (i = 0; i < ci->children_num; i++)
564   {
565     oconfig_item_t *child = ci->children + i;
566
567     if (strcasecmp ("Page", child->key) == 0)
568     {
569       status = cc_config_add_page (child);
570       if (status == 0)
571         success++;
572       else
573         errors++;
574     }
575     else
576     {
577       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
578       errors++;
579     }
580   }
581
582   if ((success == 0) && (errors > 0))
583   {
584     ERROR ("curl plugin: All statements failed.");
585     return (-1);
586   }
587
588   return (0);
589 } /* }}} int cc_config */
590
591 static int cc_init (void) /* {{{ */
592 {
593   if (pages_g == NULL)
594   {
595     INFO ("curl plugin: No pages have been defined.");
596     return (-1);
597   }
598   return (0);
599 } /* }}} int cc_init */
600
601 static void cc_submit (const web_page_t *wp, const web_match_t *wm, /* {{{ */
602     const cu_match_value_t *mv)
603 {
604   value_t values[1];
605   value_list_t vl = VALUE_LIST_INIT;
606
607   values[0] = mv->value;
608
609   vl.values = values;
610   vl.values_len = 1;
611   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
612   sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
613   sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
614   sstrncpy (vl.type, wm->type, sizeof (vl.type));
615   if (wm->instance != NULL)
616     sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
617
618   plugin_dispatch_values (&vl);
619 } /* }}} void cc_submit */
620
621 static void cc_submit_response_time (const web_page_t *wp, double seconds) /* {{{ */
622 {
623   value_t values[1];
624   value_list_t vl = VALUE_LIST_INIT;
625
626   values[0].gauge = seconds;
627
628   vl.values = values;
629   vl.values_len = 1;
630   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
631   sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
632   sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
633   sstrncpy (vl.type, "response_time", sizeof (vl.type));
634
635   plugin_dispatch_values (&vl);
636 } /* }}} void cc_submit_response_time */
637
638 static int cc_read_page (web_page_t *wp) /* {{{ */
639 {
640   web_match_t *wm;
641   int status;
642   struct timeval start, end;
643
644   if (wp->response_time)
645     gettimeofday (&start, NULL);
646
647   wp->buffer_fill = 0;
648   status = curl_easy_perform (wp->curl);
649   if (status != CURLE_OK)
650   {
651     ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
652         status, wp->curl_errbuf);
653     return (-1);
654   }
655
656   if (wp->response_time)
657   {
658     double secs = 0;
659     gettimeofday (&end, NULL);
660     secs += end.tv_sec - start.tv_sec;
661     secs += (end.tv_usec - start.tv_usec) / 1000000.0;
662     cc_submit_response_time (wp, secs);
663   }
664
665   for (wm = wp->matches; wm != NULL; wm = wm->next)
666   {
667     cu_match_value_t *mv;
668
669     status = match_apply (wm->match, wp->buffer);
670     if (status != 0)
671     {
672       WARNING ("curl plugin: match_apply failed.");
673       continue;
674     }
675
676     mv = match_get_user_data (wm->match);
677     if (mv == NULL)
678     {
679       WARNING ("curl plugin: match_get_user_data returned NULL.");
680       continue;
681     }
682
683     cc_submit (wp, wm, mv);
684   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
685
686   return (0);
687 } /* }}} int cc_read_page */
688
689 static int cc_read (void) /* {{{ */
690 {
691   web_page_t *wp;
692
693   for (wp = pages_g; wp != NULL; wp = wp->next)
694     cc_read_page (wp);
695
696   return (0);
697 } /* }}} int cc_read */
698
699 static int cc_shutdown (void) /* {{{ */
700 {
701   cc_web_page_free (pages_g);
702   pages_g = NULL;
703
704   return (0);
705 } /* }}} int cc_shutdown */
706
707 void module_register (void)
708 {
709   plugin_register_complex_config ("curl", cc_config);
710   plugin_register_init ("curl", cc_init);
711   plugin_register_read ("curl", cc_read);
712   plugin_register_shutdown ("curl", cc_shutdown);
713 } /* void module_register */
714
715 /* vim: set sw=2 sts=2 et fdm=marker : */