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