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