Code

rrdtool plugin: Use cdrand_range() for the random variation.
[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  **/
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"
27 #include "configfile.h"
28 #include "utils_match.h"
30 #include <curl/curl.h>
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;
45   cu_match_t *match;
47   web_match_t *next;
48 }; /* }}} */
50 struct web_page_s;
51 typedef struct web_page_s web_page_t;
52 struct web_page_s /* {{{ */
53 {
54   char *instance;
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;
67   CURL *curl;
68   char curl_errbuf[CURL_ERROR_SIZE];
69   char *buffer;
70   size_t buffer_size;
71   size_t buffer_fill;
73   web_match_t *matches;
75   web_page_t *next;
76 }; /* }}} */
78 /*
79  * Global variables;
80  */
81 /* static CURLM *curl = NULL; */
82 static web_page_t *pages_g = NULL;
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);
97   wp = user_data;
98   if (wp == NULL)
99     return (0);
101   if ((wp->buffer_fill + len) >= wp->buffer_size)
102   {
103     char *temp;
104     size_t temp_size;
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   }
117   memcpy (wp->buffer + wp->buffer_fill, (char *) buf, len);
118   wp->buffer_fill += len;
119   wp->buffer[wp->buffer_fill] = 0;
121   return (len);
122 } /* }}} size_t cc_curl_callback */
124 static void cc_web_match_free (web_match_t *wm) /* {{{ */
126   if (wm == NULL)
127     return;
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 */
137 static void cc_web_page_free (web_page_t *wp) /* {{{ */
139   if (wp == NULL)
140     return;
142   if (wp->curl != NULL)
143     curl_easy_cleanup (wp->curl);
144   wp->curl = NULL;
146   sfree (wp->instance);
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);
156   sfree (wp->buffer);
158   cc_web_match_free (wp->matches);
159   cc_web_page_free (wp->next);
160   sfree (wp);
161 } /* }}} void cc_web_page_free */
163 static int cc_config_add_string (const char *name, char **dest, /* {{{ */
164     oconfig_item_t *ci)
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   }
172   sfree (*dest);
173   *dest = strdup (ci->values[0].value.string);
174   if (*dest == NULL)
175     return (-1);
177   return (0);
178 } /* }}} int cc_config_add_string */
180 static int cc_config_append_string (const char *name, struct curl_slist **dest, /* {{{ */
181     oconfig_item_t *ci)
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   }
189   *dest = curl_slist_append(*dest, ci->values[0].value.string);
190   if (*dest == NULL)
191     return (-1);
193   return (0);
194 } /* }}} int cc_config_append_string */
197 static int cc_config_set_boolean (const char *name, int *dest, /* {{{ */
198     oconfig_item_t *ci)
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   }
206   *dest = ci->values[0].value.boolean ? 1 : 0;
208   return (0);
209 } /* }}} int cc_config_set_boolean */
211 static int cc_config_add_match_dstype (int *dstype_ret, /* {{{ */
212     oconfig_item_t *ci)
214   int dstype;
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   }
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   }
273   else
274   {
275     dstype = 0;
276   }
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   }
285   *dstype_ret = dstype;
286   return (0);
287 } /* }}} int cc_config_add_match_dstype */
289 static int cc_config_add_match (web_page_t *page, /* {{{ */
290     oconfig_item_t *ci)
292   web_match_t *match;
293   int status;
294   int i;
296   if (ci->values_num != 0)
297   {
298     WARNING ("curl plugin: Ignoring arguments for the `Match' block.");
299   }
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));
309   status = 0;
310   for (i = 0; i < ci->children_num; i++)
311   {
312     oconfig_item_t *child = ci->children + i;
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     }
330     if (status != 0)
331       break;
332   } /* for (i = 0; i < ci->children_num; i++) */
334   while (status == 0)
335   {
336     if (match->regex == NULL)
337     {
338       WARNING ("curl plugin: `Regex' missing in `Match' block.");
339       status = -1;
340     }
342     if (match->type == NULL)
343     {
344       WARNING ("curl plugin: `Type' missing in `Match' block.");
345       status = -1;
346     }
348     if (match->dstype == 0)
349     {
350       WARNING ("curl plugin: `DSType' missing in `Match' block.");
351       status = -1;
352     }
354     break;
355   } /* while (status == 0) */
357   if (status != 0)
358     return (status);
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;
372     prev = page->matches;
373     while ((prev != NULL) && (prev->next != NULL))
374       prev = prev->next;
376     if (prev == NULL)
377       page->matches = match;
378     else
379       prev->next = match;
380   }
382   return (0);
383 } /* }}} int cc_config_add_match */
385 static int cc_page_init_curl (web_page_t *wp) /* {{{ */
387   wp->curl = curl_easy_init ();
388   if (wp->curl == NULL)
389   {
390     ERROR ("curl plugin: curl_easy_init failed.");
391     return (-1);
392   }
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);
404   if (wp->user != NULL)
405   {
406     size_t credentials_size;
408     credentials_size = strlen (wp->user) + 2;
409     if (wp->pass != NULL)
410       credentials_size += strlen (wp->pass);
412     wp->credentials = (char *) malloc (credentials_size);
413     if (wp->credentials == NULL)
414     {
415       ERROR ("curl plugin: malloc failed.");
416       return (-1);
417     }
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   }
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);
434   return (0);
435 } /* }}} int cc_page_init_curl */
437 static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
439   web_page_t *page;
440   int status;
441   int i;
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   }
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;
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   }
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;
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     }
504     if (status != 0)
505       break;
506   } /* for (i = 0; i < ci->children_num; i++) */
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     }
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     }
525     if (status == 0)
526       status = cc_page_init_curl (page);
528     break;
529   } /* while (status == 0) */
531   if (status != 0)
532   {
533     cc_web_page_free (page);
534     return (status);
535   }
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;
544     prev = pages_g;
545     while ((prev != NULL) && (prev->next != NULL))
546       prev = prev->next;
547     prev->next = page;
548   }
550   return (0);
551 } /* }}} int cc_config_add_page */
553 static int cc_config (oconfig_item_t *ci) /* {{{ */
555   int success;
556   int errors;
557   int status;
558   int i;
560   success = 0;
561   errors = 0;
563   for (i = 0; i < ci->children_num; i++)
564   {
565     oconfig_item_t *child = ci->children + i;
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   }
582   if ((success == 0) && (errors > 0))
583   {
584     ERROR ("curl plugin: All statements failed.");
585     return (-1);
586   }
588   return (0);
589 } /* }}} int cc_config */
591 static int cc_init (void) /* {{{ */
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 */
601 static void cc_submit (const web_page_t *wp, const web_match_t *wm, /* {{{ */
602     const cu_match_value_t *mv)
604   value_t values[1];
605   value_list_t vl = VALUE_LIST_INIT;
607   values[0] = mv->value;
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   sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
617   plugin_dispatch_values (&vl);
618 } /* }}} void cc_submit */
620 static void cc_submit_response_time (const web_page_t *wp, double seconds) /* {{{ */
622   value_t values[1];
623   value_list_t vl = VALUE_LIST_INIT;
625   values[0].gauge = seconds;
627   vl.values = values;
628   vl.values_len = 1;
629   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
630   sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
631   sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
632   sstrncpy (vl.type, "response_time", sizeof (vl.type));
634   plugin_dispatch_values (&vl);
635 } /* }}} void cc_submit_response_time */
637 static int cc_read_page (web_page_t *wp) /* {{{ */
639   web_match_t *wm;
640   int status;
641   struct timeval start, end;
643   if (wp->response_time)
644     gettimeofday (&start, NULL);
646   wp->buffer_fill = 0;
647   status = curl_easy_perform (wp->curl);
648   if (status != CURLE_OK)
649   {
650     ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
651         status, wp->curl_errbuf);
652     return (-1);
653   }
655   if (wp->response_time)
656   {
657     double secs = 0;
658     gettimeofday (&end, NULL);
659     secs += end.tv_sec - start.tv_sec;
660     secs += (end.tv_usec - start.tv_usec) / 1000000.0;
661     cc_submit_response_time (wp, secs);
662   }
664   for (wm = wp->matches; wm != NULL; wm = wm->next)
665   {
666     cu_match_value_t *mv;
668     status = match_apply (wm->match, wp->buffer);
669     if (status != 0)
670     {
671       WARNING ("curl plugin: match_apply failed.");
672       continue;
673     }
675     mv = match_get_user_data (wm->match);
676     if (mv == NULL)
677     {
678       WARNING ("curl plugin: match_get_user_data returned NULL.");
679       continue;
680     }
682     cc_submit (wp, wm, mv);
683   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
685   return (0);
686 } /* }}} int cc_read_page */
688 static int cc_read (void) /* {{{ */
690   web_page_t *wp;
692   for (wp = pages_g; wp != NULL; wp = wp->next)
693     cc_read_page (wp);
695   return (0);
696 } /* }}} int cc_read */
698 static int cc_shutdown (void) /* {{{ */
700   cc_web_page_free (pages_g);
701   pages_g = NULL;
703   return (0);
704 } /* }}} int cc_shutdown */
706 void module_register (void)
708   plugin_register_complex_config ("curl", cc_config);
709   plugin_register_init ("curl", cc_init);
710   plugin_register_read ("curl", cc_read);
711   plugin_register_shutdown ("curl", cc_shutdown);
712 } /* void module_register */
714 /* vim: set sw=2 sts=2 et fdm=marker : */