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