a533e147b1b6e2af1dcf00ff7634667b20bf4784
[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_WRITEFUNCTION, cc_curl_callback);
374   curl_easy_setopt (wp->curl, CURLOPT_WRITEDATA, wp);
375   curl_easy_setopt (wp->curl, CURLOPT_USERAGENT,
376       PACKAGE_NAME"/"PACKAGE_VERSION);
377   curl_easy_setopt (wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
378   curl_easy_setopt (wp->curl, CURLOPT_URL, wp->url);
379   curl_easy_setopt (wp->curl, CURLOPT_FOLLOWLOCATION, 1);
380
381   if (wp->user != NULL)
382   {
383     size_t credentials_size;
384
385     credentials_size = strlen (wp->user) + 2;
386     if (wp->pass != NULL)
387       credentials_size += strlen (wp->pass);
388
389     wp->credentials = (char *) malloc (credentials_size);
390     if (wp->credentials == NULL)
391     {
392       ERROR ("curl plugin: malloc failed.");
393       return (-1);
394     }
395
396     ssnprintf (wp->credentials, credentials_size, "%s:%s",
397         wp->user, (wp->pass == NULL) ? "" : wp->pass);
398     curl_easy_setopt (wp->curl, CURLOPT_USERPWD, wp->credentials);
399   }
400
401   curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYPEER, wp->verify_peer);
402   curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYHOST,
403       wp->verify_host ? 2 : 0);
404   if (wp->cacert != NULL)
405     curl_easy_setopt (wp->curl, CURLOPT_CAINFO, wp->cacert);
406
407   return (0);
408 } /* }}} int cc_page_init_curl */
409
410 static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
411 {
412   web_page_t *page;
413   int status;
414   int i;
415
416   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
417   {
418     WARNING ("curl plugin: `Page' blocks need exactly one string argument.");
419     return (-1);
420   }
421
422   page = (web_page_t *) malloc (sizeof (*page));
423   if (page == NULL)
424   {
425     ERROR ("curl plugin: malloc failed.");
426     return (-1);
427   }
428   memset (page, 0, sizeof (*page));
429   page->url = NULL;
430   page->user = NULL;
431   page->pass = NULL;
432   page->verify_peer = 1;
433   page->verify_host = 1;
434   page->response_time = 0;
435
436   page->instance = strdup (ci->values[0].value.string);
437   if (page->instance == NULL)
438   {
439     ERROR ("curl plugin: strdup failed.");
440     sfree (page);
441     return (-1);
442   }
443
444   /* Process all children */
445   status = 0;
446   for (i = 0; i < ci->children_num; i++)
447   {
448     oconfig_item_t *child = ci->children + i;
449
450     if (strcasecmp ("URL", child->key) == 0)
451       status = cc_config_add_string ("URL", &page->url, child);
452     else if (strcasecmp ("User", child->key) == 0)
453       status = cc_config_add_string ("User", &page->user, child);
454     else if (strcasecmp ("Password", child->key) == 0)
455       status = cc_config_add_string ("Password", &page->pass, child);
456     else if (strcasecmp ("VerifyPeer", child->key) == 0)
457       status = cc_config_set_boolean ("VerifyPeer", &page->verify_peer, child);
458     else if (strcasecmp ("VerifyHost", child->key) == 0)
459       status = cc_config_set_boolean ("VerifyHost", &page->verify_host, child);
460     else if (strcasecmp ("MeasureResponseTime", child->key) == 0)
461       status = cc_config_set_boolean (child->key, &page->response_time, child);
462     else if (strcasecmp ("CACert", child->key) == 0)
463       status = cc_config_add_string ("CACert", &page->cacert, child);
464     else if (strcasecmp ("Match", child->key) == 0)
465       /* Be liberal with failing matches => don't set `status'. */
466       cc_config_add_match (page, child);
467     else
468     {
469       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
470       status = -1;
471     }
472
473     if (status != 0)
474       break;
475   } /* for (i = 0; i < ci->children_num; i++) */
476
477   /* Additionial sanity checks and libCURL initialization. */
478   while (status == 0)
479   {
480     if (page->url == NULL)
481     {
482       WARNING ("curl plugin: `URL' missing in `Page' block.");
483       status = -1;
484     }
485
486     if (page->matches == NULL && !page->response_time)
487     {
488       assert (page->instance != NULL);
489       WARNING ("curl plugin: No (valid) `Match' block "
490           "or MeasureResponseTime within `Page' block `%s'.", page->instance);
491       status = -1;
492     }
493
494     if (status == 0)
495       status = cc_page_init_curl (page);
496
497     break;
498   } /* while (status == 0) */
499
500   if (status != 0)
501   {
502     cc_web_page_free (page);
503     return (status);
504   }
505
506   /* Add the new page to the linked list */
507   if (pages_g == NULL)
508     pages_g = page;
509   else
510   {
511     web_page_t *prev;
512
513     prev = pages_g;
514     while ((prev != NULL) && (prev->next != NULL))
515       prev = prev->next;
516     prev->next = page;
517   }
518
519   return (0);
520 } /* }}} int cc_config_add_page */
521
522 static int cc_config (oconfig_item_t *ci) /* {{{ */
523 {
524   int success;
525   int errors;
526   int status;
527   int i;
528
529   success = 0;
530   errors = 0;
531
532   for (i = 0; i < ci->children_num; i++)
533   {
534     oconfig_item_t *child = ci->children + i;
535
536     if (strcasecmp ("Page", child->key) == 0)
537     {
538       status = cc_config_add_page (child);
539       if (status == 0)
540         success++;
541       else
542         errors++;
543     }
544     else
545     {
546       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
547       errors++;
548     }
549   }
550
551   if ((success == 0) && (errors > 0))
552   {
553     ERROR ("curl plugin: All statements failed.");
554     return (-1);
555   }
556
557   return (0);
558 } /* }}} int cc_config */
559
560 static int cc_init (void) /* {{{ */
561 {
562   if (pages_g == NULL)
563   {
564     INFO ("curl plugin: No pages have been defined.");
565     return (-1);
566   }
567   return (0);
568 } /* }}} int cc_init */
569
570 static void cc_submit (const web_page_t *wp, const web_match_t *wm, /* {{{ */
571     const cu_match_value_t *mv)
572 {
573   value_t values[1];
574   value_list_t vl = VALUE_LIST_INIT;
575
576   values[0] = mv->value;
577
578   vl.values = values;
579   vl.values_len = 1;
580   vl.time = time (NULL);
581   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
582   sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
583   sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
584   sstrncpy (vl.type, wm->type, sizeof (vl.type));
585   sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
586
587   plugin_dispatch_values (&vl);
588 } /* }}} void cc_submit */
589
590 static void cc_submit_response_time (const web_page_t *wp, double seconds) /* {{{ */
591 {
592   value_t values[1];
593   value_list_t vl = VALUE_LIST_INIT;
594
595   values[0].gauge = seconds;
596
597   vl.values = values;
598   vl.values_len = 1;
599   vl.time = time (NULL);
600   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
601   sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
602   sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
603   sstrncpy (vl.type, "response_time", sizeof (vl.type));
604
605   plugin_dispatch_values (&vl);
606 } /* }}} void cc_submit_response_time */
607
608 static int cc_read_page (web_page_t *wp) /* {{{ */
609 {
610   web_match_t *wm;
611   int status;
612   struct timeval start, end;
613
614   if (wp->response_time)
615     gettimeofday (&start, NULL);
616
617   wp->buffer_fill = 0;
618   status = curl_easy_perform (wp->curl);
619   if (status != 0)
620   {
621     ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
622         status, wp->curl_errbuf);
623     return (-1);
624   }
625
626   if (wp->response_time)
627   {
628     double secs = 0;
629     gettimeofday (&end, NULL);
630     secs += end.tv_sec - start.tv_sec;
631     secs += (end.tv_usec - start.tv_usec) / 1000000.0;
632     cc_submit_response_time (wp, secs);
633   }
634
635   for (wm = wp->matches; wm != NULL; wm = wm->next)
636   {
637     cu_match_value_t *mv;
638
639     status = match_apply (wm->match, wp->buffer);
640     if (status != 0)
641     {
642       WARNING ("curl plugin: match_apply failed.");
643       continue;
644     }
645
646     mv = match_get_user_data (wm->match);
647     if (mv == NULL)
648     {
649       WARNING ("curl plugin: match_get_user_data returned NULL.");
650       continue;
651     }
652
653     cc_submit (wp, wm, mv);
654   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
655
656   return (0);
657 } /* }}} int cc_read_page */
658
659 static int cc_read (void) /* {{{ */
660 {
661   web_page_t *wp;
662
663   for (wp = pages_g; wp != NULL; wp = wp->next)
664     cc_read_page (wp);
665
666   return (0);
667 } /* }}} int cc_read */
668
669 static int cc_shutdown (void) /* {{{ */
670 {
671   cc_web_page_free (pages_g);
672   pages_g = NULL;
673
674   return (0);
675 } /* }}} int cc_shutdown */
676
677 void module_register (void)
678 {
679   plugin_register_complex_config ("curl", cc_config);
680   plugin_register_init ("curl", cc_init);
681   plugin_register_read ("curl", cc_read);
682   plugin_register_shutdown ("curl", cc_shutdown);
683 } /* void module_register */
684
685 /* vim: set sw=2 sts=2 et fdm=marker : */