curl_xml: Add ability to set custom plugin name in collected data.
[collectd.git] / src / curl_xml.c
1 /**
2  * collectd - src/curl_xml.c
3  * Copyright (C) 2009,2010       Amit Gupta
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Amit Gupta <amit.gupta221 at gmail.com>
20  **/
21
22 #include "collectd.h"
23
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_curl_stats.h"
27 #include "utils_llist.h"
28
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31 #include <libxml/xpath.h>
32 #include <libxml/xpathInternals.h>
33
34 #include <curl/curl.h>
35
36 #define CX_DEFAULT_HOST "localhost"
37
38 /*
39  * Private data structures
40  */
41 struct cx_values_s /* {{{ */
42 {
43   char path[DATA_MAX_NAME_LEN];
44   size_t path_len;
45 };
46 typedef struct cx_values_s cx_values_t;
47 /* }}} */
48
49 struct cx_xpath_s /* {{{ */
50 {
51   char *path;
52   char *type;
53   cx_values_t *values;
54   size_t values_len;
55   char *instance_prefix;
56   char *instance;
57   int is_table;
58   unsigned long magic;
59 };
60 typedef struct cx_xpath_s cx_xpath_t;
61 /* }}} */
62
63 struct cx_namespace_s /* {{{ */
64 {
65   char *prefix;
66   char *url;
67 };
68 typedef struct cx_namespace_s cx_namespace_t;
69 /* }}} */
70
71 struct cx_s /* {{{ */
72 {
73   char *instance;
74   char *plugin_name;
75   char *host;
76
77   char *url;
78   char *user;
79   char *pass;
80   char *credentials;
81   _Bool digest;
82   _Bool verify_peer;
83   _Bool verify_host;
84   char *cacert;
85   char *post_body;
86   int timeout;
87   struct curl_slist *headers;
88   curl_stats_t *stats;
89
90   cx_namespace_t *namespaces;
91   size_t namespaces_num;
92
93   CURL *curl;
94   char curl_errbuf[CURL_ERROR_SIZE];
95   char *buffer;
96   size_t buffer_size;
97   size_t buffer_fill;
98
99   llist_t *list; /* list of xpath blocks */
100 };
101 typedef struct cx_s cx_t; /* }}} */
102
103 /*
104  * Private functions
105  */
106 static size_t cx_curl_callback(void *buf, /* {{{ */
107                                size_t size, size_t nmemb, void *user_data) {
108   size_t len = size * nmemb;
109   cx_t *db;
110
111   db = user_data;
112   if (db == NULL) {
113     ERROR("curl_xml plugin: cx_curl_callback: "
114           "user_data pointer is NULL.");
115     return 0;
116   }
117
118   if (len == 0)
119     return len;
120
121   if ((db->buffer_fill + len) >= db->buffer_size) {
122     char *temp;
123
124     temp = realloc(db->buffer, db->buffer_fill + len + 1);
125     if (temp == NULL) {
126       ERROR("curl_xml plugin: realloc failed.");
127       return 0;
128     }
129     db->buffer = temp;
130     db->buffer_size = db->buffer_fill + len + 1;
131   }
132
133   memcpy(db->buffer + db->buffer_fill, (char *)buf, len);
134   db->buffer_fill += len;
135   db->buffer[db->buffer_fill] = 0;
136
137   return len;
138 } /* }}} size_t cx_curl_callback */
139
140 static void cx_xpath_free(cx_xpath_t *xpath) /* {{{ */
141 {
142   if (xpath == NULL)
143     return;
144
145   sfree(xpath->path);
146   sfree(xpath->type);
147   sfree(xpath->instance_prefix);
148   sfree(xpath->instance);
149   sfree(xpath->values);
150   sfree(xpath);
151 } /* }}} void cx_xpath_free */
152
153 static void cx_list_free(llist_t *list) /* {{{ */
154 {
155   llentry_t *le;
156
157   le = llist_head(list);
158   while (le != NULL) {
159     llentry_t *le_next;
160
161     le_next = le->next;
162
163     sfree(le->key);
164     cx_xpath_free(le->value);
165
166     le = le_next;
167   }
168
169   llist_destroy(list);
170 } /* }}} void cx_list_free */
171
172 static void cx_free(void *arg) /* {{{ */
173 {
174   cx_t *db;
175
176   DEBUG("curl_xml plugin: cx_free (arg = %p);", arg);
177
178   db = (cx_t *)arg;
179
180   if (db == NULL)
181     return;
182
183   if (db->curl != NULL)
184     curl_easy_cleanup(db->curl);
185   db->curl = NULL;
186
187   if (db->list != NULL)
188     cx_list_free(db->list);
189
190   sfree(db->buffer);
191   sfree(db->instance);
192   sfree(db->plugin_name);
193   sfree(db->host);
194
195   sfree(db->url);
196   sfree(db->user);
197   sfree(db->pass);
198   sfree(db->credentials);
199   sfree(db->cacert);
200   sfree(db->post_body);
201   curl_slist_free_all(db->headers);
202   curl_stats_destroy(db->stats);
203
204   for (size_t i = 0; i < db->namespaces_num; i++) {
205     sfree(db->namespaces[i].prefix);
206     sfree(db->namespaces[i].url);
207   }
208   sfree(db->namespaces);
209
210   sfree(db);
211 } /* }}} void cx_free */
212
213 static const char *cx_host(const cx_t *db) /* {{{ */
214 {
215   if (db->host == NULL)
216     return hostname_g;
217   return db->host;
218 } /* }}} cx_host */
219
220 static int cx_config_append_string(const char *name,
221                                    struct curl_slist **dest, /* {{{ */
222                                    oconfig_item_t *ci) {
223   struct curl_slist *temp = NULL;
224   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
225     WARNING("curl_xml plugin: `%s' needs exactly one string argument.", name);
226     return -1;
227   }
228
229   temp = curl_slist_append(*dest, ci->values[0].value.string);
230   if (temp == NULL)
231     return -1;
232
233   *dest = temp;
234
235   return 0;
236 } /* }}} int cx_config_append_string */
237
238 static int cx_check_type(const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */
239 {
240   if (!ds) {
241     WARNING("curl_xml plugin: DataSet `%s' not defined.", xpath->type);
242     return -1;
243   }
244
245   if (ds->ds_num != xpath->values_len) {
246     WARNING("curl_xml plugin: DataSet `%s' requires %zu values, but config "
247             "talks about %zu",
248             xpath->type, ds->ds_num, xpath->values_len);
249     return -1;
250   }
251
252   return 0;
253 } /* }}} cx_check_type */
254
255 static xmlXPathObjectPtr
256 cx_evaluate_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
257                   xmlChar *expr) {
258   xmlXPathObjectPtr xpath_obj;
259
260   /* XXX: When to free this? */
261   xpath_obj = xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx);
262   if (xpath_obj == NULL) {
263     WARNING("curl_xml plugin: "
264             "Error unable to evaluate xpath expression \"%s\". Skipping...",
265             expr);
266     return NULL;
267   }
268
269   return xpath_obj;
270 } /* }}} cx_evaluate_xpath */
271
272 static int cx_if_not_text_node(xmlNodePtr node) /* {{{ */
273 {
274   if (node->type == XML_TEXT_NODE || node->type == XML_ATTRIBUTE_NODE ||
275       node->type == XML_ELEMENT_NODE)
276     return 0;
277
278   WARNING("curl_xml plugin: "
279           "Node \"%s\" doesn't seem to be a text node. Skipping...",
280           node->name);
281   return -1;
282 } /* }}} cx_if_not_text_node */
283
284 static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
285                                         cx_xpath_t *xpath, const data_set_t *ds,
286                                         value_list_t *vl, int index) {
287   xmlXPathObjectPtr values_node_obj;
288   xmlNodeSetPtr values_node;
289   int tmp_size;
290   char *node_value;
291
292   values_node_obj =
293       cx_evaluate_xpath(xpath_ctx, BAD_CAST xpath->values[index].path);
294   if (values_node_obj == NULL)
295     return -1; /* Error already logged. */
296
297   values_node = values_node_obj->nodesetval;
298   tmp_size = (values_node) ? values_node->nodeNr : 0;
299
300   if (tmp_size == 0) {
301     WARNING("curl_xml plugin: "
302             "relative xpath expression \"%s\" doesn't match any of the nodes. "
303             "Skipping...",
304             xpath->values[index].path);
305     xmlXPathFreeObject(values_node_obj);
306     return -1;
307   }
308
309   if (tmp_size > 1) {
310     WARNING("curl_xml plugin: "
311             "relative xpath expression \"%s\" is expected to return "
312             "only one node. Skipping...",
313             xpath->values[index].path);
314     xmlXPathFreeObject(values_node_obj);
315     return -1;
316   }
317
318   /* ignoring the element if other than textnode/attribute*/
319   if (cx_if_not_text_node(values_node->nodeTab[0])) {
320     WARNING("curl_xml plugin: "
321             "relative xpath expression \"%s\" is expected to return "
322             "only text/attribute node which is not the case. Skipping...",
323             xpath->values[index].path);
324     xmlXPathFreeObject(values_node_obj);
325     return -1;
326   }
327
328   node_value = (char *)xmlNodeGetContent(values_node->nodeTab[0]);
329   switch (ds->ds[index].type) {
330   case DS_TYPE_COUNTER:
331     vl->values[index].counter =
332         (counter_t)strtoull(node_value,
333                             /* endptr = */ NULL, /* base = */ 0);
334     break;
335   case DS_TYPE_DERIVE:
336     vl->values[index].derive =
337         (derive_t)strtoll(node_value,
338                           /* endptr = */ NULL, /* base = */ 0);
339     break;
340   case DS_TYPE_ABSOLUTE:
341     vl->values[index].absolute =
342         (absolute_t)strtoull(node_value,
343                              /* endptr = */ NULL, /* base = */ 0);
344     break;
345   case DS_TYPE_GAUGE:
346     vl->values[index].gauge = (gauge_t)strtod(node_value,
347                                               /* endptr = */ NULL);
348   }
349
350   /* free up object */
351   xmlXPathFreeObject(values_node_obj);
352   sfree(node_value);
353
354   /* We have reached here which means that
355    * we have got something to work */
356   return 0;
357 } /* }}} int cx_handle_single_value_xpath */
358
359 static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */
360                                       cx_xpath_t *xpath, const data_set_t *ds,
361                                       value_list_t *vl) {
362   value_t values[xpath->values_len];
363   int status;
364
365   assert(xpath->values_len > 0);
366   assert(xpath->values_len == vl->values_len);
367   assert(xpath->values_len == ds->ds_num);
368   vl->values = values;
369
370   for (size_t i = 0; i < xpath->values_len; i++) {
371     status = cx_handle_single_value_xpath(xpath_ctx, xpath, ds, vl, i);
372     if (status != 0)
373       return -1; /* An error has been printed. */
374   }              /* for (i = 0; i < xpath->values_len; i++) */
375
376   plugin_dispatch_values(vl);
377   vl->values = NULL;
378
379   return 0;
380 } /* }}} int cx_handle_all_value_xpaths */
381
382 static int cx_handle_instance_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
383                                     cx_xpath_t *xpath, value_list_t *vl,
384                                     _Bool is_table) {
385   xmlXPathObjectPtr instance_node_obj = NULL;
386   xmlNodeSetPtr instance_node = NULL;
387
388   memset(vl->type_instance, 0, sizeof(vl->type_instance));
389
390   /* If the base xpath returns more than one block, the result is assumed to be
391    * a table. The `Instance' option is not optional in this case. Check for the
392    * condition and inform the user. */
393   if (is_table && (xpath->instance == NULL)) {
394     WARNING("curl_xml plugin: "
395             "Base-XPath %s is a table (more than one result was returned), "
396             "but no instance-XPath has been defined.",
397             xpath->path);
398     return -1;
399   }
400
401   /* instance has to be an xpath expression */
402   if (xpath->instance != NULL) {
403     int tmp_size;
404
405     instance_node_obj = cx_evaluate_xpath(xpath_ctx, BAD_CAST xpath->instance);
406     if (instance_node_obj == NULL)
407       return -1; /* error is logged already */
408
409     instance_node = instance_node_obj->nodesetval;
410     tmp_size = (instance_node) ? instance_node->nodeNr : 0;
411
412     if (tmp_size <= 0) {
413       WARNING(
414           "curl_xml plugin: "
415           "relative xpath expression for 'InstanceFrom' \"%s\" doesn't match "
416           "any of the nodes. Skipping the node.",
417           xpath->instance);
418       xmlXPathFreeObject(instance_node_obj);
419       return -1;
420     }
421
422     if (tmp_size > 1) {
423       WARNING("curl_xml plugin: "
424               "relative xpath expression for 'InstanceFrom' \"%s\" is expected "
425               "to return only one text node. Skipping the node.",
426               xpath->instance);
427       xmlXPathFreeObject(instance_node_obj);
428       return -1;
429     }
430
431     /* ignoring the element if other than textnode/attribute */
432     if (cx_if_not_text_node(instance_node->nodeTab[0])) {
433       WARNING("curl_xml plugin: "
434               "relative xpath expression \"%s\" is expected to return only "
435               "text node "
436               "which is not the case. Skipping the node.",
437               xpath->instance);
438       xmlXPathFreeObject(instance_node_obj);
439       return -1;
440     }
441   } /* if (xpath->instance != NULL) */
442
443   if (xpath->instance_prefix != NULL) {
444     if (instance_node != NULL) {
445       char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]);
446       snprintf(vl->type_instance, sizeof(vl->type_instance), "%s%s",
447                xpath->instance_prefix, node_value);
448       sfree(node_value);
449     } else
450       sstrncpy(vl->type_instance, xpath->instance_prefix,
451                sizeof(vl->type_instance));
452   } else {
453     /* If instance_prefix and instance_node are NULL, then
454      * don't set the type_instance */
455     if (instance_node != NULL) {
456       char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]);
457       sstrncpy(vl->type_instance, node_value, sizeof(vl->type_instance));
458       sfree(node_value);
459     }
460   }
461
462   /* Free `instance_node_obj' this late, because `instance_node' points to
463    * somewhere inside this structure. */
464   xmlXPathFreeObject(instance_node_obj);
465
466   return 0;
467 } /* }}} int cx_handle_instance_xpath */
468
469 static int cx_handle_base_xpath(const cx_t *db, /* {{{ */
470                                 xmlXPathContextPtr xpath_ctx,
471                                 const data_set_t *ds, char *base_xpath,
472                                 cx_xpath_t *xpath) {
473   int total_nodes;
474
475   xmlXPathObjectPtr base_node_obj = NULL;
476   xmlNodeSetPtr base_nodes = NULL;
477
478   value_list_t vl = VALUE_LIST_INIT;
479
480   base_node_obj = cx_evaluate_xpath(xpath_ctx, BAD_CAST base_xpath);
481   if (base_node_obj == NULL)
482     return -1; /* error is logged already */
483
484   base_nodes = base_node_obj->nodesetval;
485   total_nodes = (base_nodes) ? base_nodes->nodeNr : 0;
486
487   if (total_nodes == 0) {
488     ERROR("curl_xml plugin: "
489           "xpath expression \"%s\" doesn't match any of the nodes. "
490           "Skipping the xpath block...",
491           base_xpath);
492     xmlXPathFreeObject(base_node_obj);
493     return -1;
494   }
495
496   /* If base_xpath returned multiple results, then */
497   /* Instance in the xpath block is required */
498   if (total_nodes > 1 && xpath->instance == NULL) {
499     ERROR("curl_xml plugin: "
500           "InstanceFrom is must in xpath block since the base xpath expression "
501           "\"%s\" "
502           "returned multiple results. Skipping the xpath block...",
503           base_xpath);
504     return -1;
505   }
506
507   /* set the values for the value_list */
508   vl.values_len = ds->ds_num;
509   sstrncpy(vl.type, xpath->type, sizeof(vl.type));
510   sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_xml",
511            sizeof(vl.plugin));
512   sstrncpy(vl.host, cx_host(db), sizeof(vl.host));
513   if (db->instance != NULL)
514     sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
515
516   for (int i = 0; i < total_nodes; i++) {
517     int status;
518
519     xpath_ctx->node = base_nodes->nodeTab[i];
520
521     status = cx_handle_instance_xpath(xpath_ctx, xpath, &vl,
522                                       /* is_table = */ (total_nodes > 1));
523     if (status != 0)
524       continue; /* An error has already been reported. */
525
526     status = cx_handle_all_value_xpaths(xpath_ctx, xpath, ds, &vl);
527     if (status != 0)
528       continue; /* An error has been logged. */
529   }             /* for (i = 0; i < total_nodes; i++) */
530
531   /* free up the allocated memory */
532   xmlXPathFreeObject(base_node_obj);
533
534   return 0;
535 } /* }}} cx_handle_base_xpath */
536
537 static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */
538                                 xmlXPathContextPtr xpath_ctx, cx_t *db) {
539   llentry_t *le;
540   const data_set_t *ds;
541   cx_xpath_t *xpath;
542   int status = -1;
543
544   le = llist_head(db->list);
545   while (le != NULL) {
546     /* get the ds */
547     xpath = (cx_xpath_t *)le->value;
548     ds = plugin_get_ds(xpath->type);
549
550     if ((cx_check_type(ds, xpath) == 0) &&
551         (cx_handle_base_xpath(db, xpath_ctx, ds, le->key, xpath) == 0))
552       status = 0; /* we got atleast one success */
553
554     le = le->next;
555   } /* while (le != NULL) */
556
557   return status;
558 } /* }}} cx_handle_parsed_xml */
559
560 static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */
561 {
562   int status;
563   xmlDocPtr doc;
564   xmlXPathContextPtr xpath_ctx;
565
566   /* Load the XML */
567   doc = xmlParseDoc(xml);
568   if (doc == NULL) {
569     ERROR("curl_xml plugin: Failed to parse the xml document  - %s", xml);
570     return -1;
571   }
572
573   xpath_ctx = xmlXPathNewContext(doc);
574   if (xpath_ctx == NULL) {
575     ERROR("curl_xml plugin: Failed to create the xml context");
576     xmlFreeDoc(doc);
577     return -1;
578   }
579
580   for (size_t i = 0; i < db->namespaces_num; i++) {
581     cx_namespace_t const *ns = db->namespaces + i;
582     status =
583         xmlXPathRegisterNs(xpath_ctx, BAD_CAST ns->prefix, BAD_CAST ns->url);
584     if (status != 0) {
585       ERROR("curl_xml plugin: "
586             "unable to register NS with prefix=\"%s\" and href=\"%s\"\n",
587             ns->prefix, ns->url);
588       xmlXPathFreeContext(xpath_ctx);
589       xmlFreeDoc(doc);
590       return status;
591     }
592   }
593
594   status = cx_handle_parsed_xml(doc, xpath_ctx, db);
595   /* Cleanup */
596   xmlXPathFreeContext(xpath_ctx);
597   xmlFreeDoc(doc);
598   return status;
599 } /* }}} cx_parse_stats_xml */
600
601 static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */
602 {
603   int status;
604   long rc;
605   char *ptr;
606   char *url;
607
608   db->buffer_fill = 0;
609
610   curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
611
612   status = curl_easy_perform(curl);
613   if (status != CURLE_OK) {
614     ERROR("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)",
615           status, db->curl_errbuf, db->url);
616     return -1;
617   }
618   if (db->stats != NULL)
619     curl_stats_dispatch(db->stats, db->curl, cx_host(db), "curl_xml",
620                         db->instance);
621
622   curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
623   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
624
625   /* The response code is zero if a non-HTTP transport was used. */
626   if ((rc != 0) && (rc != 200)) {
627     ERROR(
628         "curl_xml plugin: curl_easy_perform failed with response code %ld (%s)",
629         rc, url);
630     return -1;
631   }
632
633   ptr = db->buffer;
634
635   status = cx_parse_stats_xml(BAD_CAST ptr, db);
636   db->buffer_fill = 0;
637
638   return status;
639 } /* }}} int cx_curl_perform */
640
641 static int cx_read(user_data_t *ud) /* {{{ */
642 {
643   cx_t *db;
644
645   if ((ud == NULL) || (ud->data == NULL)) {
646     ERROR("curl_xml plugin: cx_read: Invalid user data.");
647     return -1;
648   }
649
650   db = (cx_t *)ud->data;
651
652   return cx_curl_perform(db, db->curl);
653 } /* }}} int cx_read */
654
655 /* Configuration handling functions {{{ */
656
657 static int cx_config_add_values(const char *name, cx_xpath_t *xpath, /* {{{ */
658                                 oconfig_item_t *ci) {
659   if (ci->values_num < 1) {
660     WARNING("curl_xml plugin: `ValuesFrom' needs at least one argument.");
661     return -1;
662   }
663
664   for (int i = 0; i < ci->values_num; i++)
665     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
666       WARNING("curl_xml plugin: `ValuesFrom' needs only string argument.");
667       return -1;
668     }
669
670   sfree(xpath->values);
671
672   xpath->values_len = 0;
673   xpath->values = malloc(sizeof(cx_values_t) * ci->values_num);
674   if (xpath->values == NULL)
675     return -1;
676   xpath->values_len = (size_t)ci->values_num;
677
678   /* populate cx_values_t structure */
679   for (int i = 0; i < ci->values_num; i++) {
680     xpath->values[i].path_len = sizeof(ci->values[i].value.string);
681     sstrncpy(xpath->values[i].path, ci->values[i].value.string,
682              sizeof(xpath->values[i].path));
683   }
684
685   return 0;
686 } /* }}} cx_config_add_values */
687
688 static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */
689 {
690   cx_xpath_t *xpath;
691   char *name;
692   llentry_t *le;
693   int status;
694
695   xpath = calloc(1, sizeof(*xpath));
696   if (xpath == NULL) {
697     ERROR("curl_xml plugin: calloc failed.");
698     return -1;
699   }
700
701   status = cf_util_get_string(ci, &xpath->path);
702   if (status != 0) {
703     cx_xpath_free(xpath);
704     return status;
705   }
706
707   /* error out if xpath->path is an empty string */
708   if (strlen(xpath->path) == 0) {
709     ERROR("curl_xml plugin: invalid xpath. "
710           "xpath value can't be an empty string");
711     cx_xpath_free(xpath);
712     return -1;
713   }
714
715   status = 0;
716   for (int i = 0; i < ci->children_num; i++) {
717     oconfig_item_t *child = ci->children + i;
718
719     if (strcasecmp("Type", child->key) == 0)
720       status = cf_util_get_string(child, &xpath->type);
721     else if (strcasecmp("InstancePrefix", child->key) == 0)
722       status = cf_util_get_string(child, &xpath->instance_prefix);
723     else if (strcasecmp("InstanceFrom", child->key) == 0)
724       status = cf_util_get_string(child, &xpath->instance);
725     else if (strcasecmp("ValuesFrom", child->key) == 0)
726       status = cx_config_add_values("ValuesFrom", xpath, child);
727     else {
728       WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
729       status = -1;
730     }
731
732     if (status != 0)
733       break;
734   } /* for (i = 0; i < ci->children_num; i++) */
735
736   if (status != 0) {
737     cx_xpath_free(xpath);
738     return status;
739   }
740
741   if (xpath->type == NULL) {
742     WARNING("curl_xml plugin: `Type' missing in `xpath' block.");
743     cx_xpath_free(xpath);
744     return -1;
745   }
746
747   if (db->list == NULL) {
748     db->list = llist_create();
749     if (db->list == NULL) {
750       ERROR("curl_xml plugin: list creation failed.");
751       cx_xpath_free(xpath);
752       return -1;
753     }
754   }
755
756   name = strdup(xpath->path);
757   if (name == NULL) {
758     ERROR("curl_xml plugin: strdup failed.");
759     cx_xpath_free(xpath);
760     return -1;
761   }
762
763   le = llentry_create(name, xpath);
764   if (le == NULL) {
765     ERROR("curl_xml plugin: llentry_create failed.");
766     cx_xpath_free(xpath);
767     sfree(name);
768     return -1;
769   }
770
771   llist_append(db->list, le);
772   return 0;
773 } /* }}} int cx_config_add_xpath */
774
775 static int cx_config_add_namespace(cx_t *db, /* {{{ */
776                                    oconfig_item_t *ci) {
777   cx_namespace_t *ns;
778
779   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
780       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
781     WARNING("curl_xml plugin: The `Namespace' option "
782             "needs exactly two string arguments.");
783     return EINVAL;
784   }
785
786   ns = realloc(db->namespaces,
787                sizeof(*db->namespaces) * (db->namespaces_num + 1));
788   if (ns == NULL) {
789     ERROR("curl_xml plugin: realloc failed.");
790     return ENOMEM;
791   }
792   db->namespaces = ns;
793   ns = db->namespaces + db->namespaces_num;
794   memset(ns, 0, sizeof(*ns));
795
796   ns->prefix = strdup(ci->values[0].value.string);
797   ns->url = strdup(ci->values[1].value.string);
798
799   if ((ns->prefix == NULL) || (ns->url == NULL)) {
800     sfree(ns->prefix);
801     sfree(ns->url);
802     ERROR("curl_xml plugin: strdup failed.");
803     return ENOMEM;
804   }
805
806   db->namespaces_num++;
807   return 0;
808 } /* }}} int cx_config_add_namespace */
809
810 /* Initialize db->curl */
811 static int cx_init_curl(cx_t *db) /* {{{ */
812 {
813   db->curl = curl_easy_init();
814   if (db->curl == NULL) {
815     ERROR("curl_xml plugin: curl_easy_init failed.");
816     return -1;
817   }
818
819   curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
820   curl_easy_setopt(db->curl, CURLOPT_WRITEFUNCTION, cx_curl_callback);
821   curl_easy_setopt(db->curl, CURLOPT_WRITEDATA, db);
822   curl_easy_setopt(db->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
823   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
824   curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
825   curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
826
827   if (db->user != NULL) {
828 #ifdef HAVE_CURLOPT_USERNAME
829     curl_easy_setopt(db->curl, CURLOPT_USERNAME, db->user);
830     curl_easy_setopt(db->curl, CURLOPT_PASSWORD,
831                      (db->pass == NULL) ? "" : db->pass);
832 #else
833     size_t credentials_size;
834
835     credentials_size = strlen(db->user) + 2;
836     if (db->pass != NULL)
837       credentials_size += strlen(db->pass);
838
839     db->credentials = malloc(credentials_size);
840     if (db->credentials == NULL) {
841       ERROR("curl_xml plugin: malloc failed.");
842       return -1;
843     }
844
845     snprintf(db->credentials, credentials_size, "%s:%s", db->user,
846              (db->pass == NULL) ? "" : db->pass);
847     curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
848 #endif
849
850     if (db->digest)
851       curl_easy_setopt(db->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
852   }
853
854   curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYPEER, db->verify_peer ? 1L : 0L);
855   curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYHOST, db->verify_host ? 2L : 0L);
856   if (db->cacert != NULL)
857     curl_easy_setopt(db->curl, CURLOPT_CAINFO, db->cacert);
858   if (db->headers != NULL)
859     curl_easy_setopt(db->curl, CURLOPT_HTTPHEADER, db->headers);
860   if (db->post_body != NULL)
861     curl_easy_setopt(db->curl, CURLOPT_POSTFIELDS, db->post_body);
862
863 #ifdef HAVE_CURLOPT_TIMEOUT_MS
864   if (db->timeout >= 0)
865     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
866   else
867     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
868                      (long)CDTIME_T_TO_MS(plugin_get_interval()));
869 #endif
870
871   return 0;
872 } /* }}} int cx_init_curl */
873
874 static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
875 {
876   cx_t *db;
877   int status = 0;
878
879   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
880     WARNING("curl_xml plugin: The `URL' block "
881             "needs exactly one string argument.");
882     return -1;
883   }
884
885   db = calloc(1, sizeof(*db));
886   if (db == NULL) {
887     ERROR("curl_xml plugin: calloc failed.");
888     return -1;
889   }
890
891   db->timeout = -1;
892
893   if (strcasecmp("URL", ci->key) == 0) {
894     status = cf_util_get_string(ci, &db->url);
895     if (status != 0) {
896       sfree(db);
897       return status;
898     }
899   } else {
900     ERROR("curl_xml plugin: cx_config: "
901           "Invalid key: %s",
902           ci->key);
903     cx_free(db);
904     return -1;
905   }
906
907   /* Fill the `cx_t' structure.. */
908   for (int i = 0; i < ci->children_num; i++) {
909     oconfig_item_t *child = ci->children + i;
910
911     if (strcasecmp("Instance", child->key) == 0)
912       status = cf_util_get_string(child, &db->instance);
913     else if (strcasecmp("PluginName", child->key) == 0)
914       status = cf_util_get_string(child, &db->plugin_name);
915     else if (strcasecmp("Host", child->key) == 0)
916       status = cf_util_get_string(child, &db->host);
917     else if (strcasecmp("User", child->key) == 0)
918       status = cf_util_get_string(child, &db->user);
919     else if (strcasecmp("Password", child->key) == 0)
920       status = cf_util_get_string(child, &db->pass);
921     else if (strcasecmp("Digest", child->key) == 0)
922       status = cf_util_get_boolean(child, &db->digest);
923     else if (strcasecmp("VerifyPeer", child->key) == 0)
924       status = cf_util_get_boolean(child, &db->verify_peer);
925     else if (strcasecmp("VerifyHost", child->key) == 0)
926       status = cf_util_get_boolean(child, &db->verify_host);
927     else if (strcasecmp("CACert", child->key) == 0)
928       status = cf_util_get_string(child, &db->cacert);
929     else if (strcasecmp("xpath", child->key) == 0)
930       status = cx_config_add_xpath(db, child);
931     else if (strcasecmp("Header", child->key) == 0)
932       status = cx_config_append_string("Header", &db->headers, child);
933     else if (strcasecmp("Post", child->key) == 0)
934       status = cf_util_get_string(child, &db->post_body);
935     else if (strcasecmp("Namespace", child->key) == 0)
936       status = cx_config_add_namespace(db, child);
937     else if (strcasecmp("Timeout", child->key) == 0)
938       status = cf_util_get_int(child, &db->timeout);
939     else if (strcasecmp("Statistics", child->key) == 0) {
940       db->stats = curl_stats_from_config(child);
941       if (db->stats == NULL)
942         status = -1;
943     } else {
944       WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
945       status = -1;
946     }
947
948     if (status != 0)
949       break;
950   }
951
952   if (status == 0) {
953     if (db->list == NULL) {
954       WARNING("curl_xml plugin: No (valid) `Key' block "
955               "within `URL' block `%s'.",
956               db->url);
957       status = -1;
958     }
959     if (status == 0)
960       status = cx_init_curl(db);
961   }
962
963   /* If all went well, register this database for reading */
964   if (status == 0) {
965     char *cb_name;
966
967     if (db->instance == NULL)
968       db->instance = strdup("default");
969
970     DEBUG("curl_xml plugin: Registering new read callback: %s", db->instance);
971
972     cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url);
973
974     plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read,
975                                  /* interval = */ 0,
976                                  &(user_data_t){
977                                      .data = db, .free_func = cx_free,
978                                  });
979     sfree(cb_name);
980   } else {
981     cx_free(db);
982     return -1;
983   }
984
985   return 0;
986 } /* }}} int cx_config_add_url */
987
988 /* }}} End of configuration handling functions */
989
990 static int cx_config(oconfig_item_t *ci) /* {{{ */
991 {
992   int success;
993   int errors;
994   int status;
995
996   success = 0;
997   errors = 0;
998
999   for (int i = 0; i < ci->children_num; i++) {
1000     oconfig_item_t *child = ci->children + i;
1001
1002     if (strcasecmp("URL", child->key) == 0) {
1003       status = cx_config_add_url(child);
1004       if (status == 0)
1005         success++;
1006       else
1007         errors++;
1008     } else {
1009       WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
1010       errors++;
1011     }
1012   }
1013
1014   if ((success == 0) && (errors > 0)) {
1015     ERROR("curl_xml plugin: All statements failed.");
1016     return -1;
1017   }
1018
1019   return 0;
1020 } /* }}} int cx_config */
1021
1022 static int cx_init(void) /* {{{ */
1023 {
1024   /* Call this while collectd is still single-threaded to avoid
1025    * initialization issues in libgcrypt. */
1026   curl_global_init(CURL_GLOBAL_SSL);
1027   return 0;
1028 } /* }}} int cx_init */
1029
1030 void module_register(void) {
1031   plugin_register_complex_config("curl_xml", cx_config);
1032   plugin_register_init("curl_xml", cx_init);
1033 } /* void module_register */