curl_xml plugin: Allow XML element nodes to be selected for text
[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       node->type == XML_ELEMENT_NODE)
250     return (0);
251
252   WARNING ("curl_xml plugin: "
253            "Node \"%s\" doesn't seem to be a text node. Skipping...", node->name);
254   return -1;
255 } /* }}} cx_if_not_text_node */
256
257 static int cx_handle_single_value_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */
258     cx_xpath_t *xpath,
259     const data_set_t *ds, value_list_t *vl, int index)
260 {
261   xmlXPathObjectPtr values_node_obj;
262   xmlNodeSetPtr values_node;
263   int tmp_size;
264   char *node_value;
265
266   values_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->values[index].path);
267   if (values_node_obj == NULL)
268     return (-1); /* Error already logged. */
269
270   values_node = values_node_obj->nodesetval;
271   tmp_size = (values_node) ? values_node->nodeNr : 0;
272
273   if (tmp_size == 0)
274   {
275     WARNING ("curl_xml plugin: "
276         "relative xpath expression \"%s\" doesn't match any of the nodes. "
277         "Skipping...", xpath->values[index].path);
278     xmlXPathFreeObject (values_node_obj);
279     return (-1);
280   }
281
282   if (tmp_size > 1)
283   {
284     WARNING ("curl_xml plugin: "
285         "relative xpath expression \"%s\" is expected to return "
286         "only one node. Skipping...", xpath->values[index].path);
287     xmlXPathFreeObject (values_node_obj);
288     return (-1);
289   }
290
291   /* ignoring the element if other than textnode/attribute*/
292   if (cx_if_not_text_node(values_node->nodeTab[0]))
293   {
294     WARNING ("curl_xml plugin: "
295         "relative xpath expression \"%s\" is expected to return "
296         "only text/attribute node which is not the case. Skipping...", 
297         xpath->values[index].path);
298     xmlXPathFreeObject (values_node_obj);
299     return (-1);
300   }
301
302   node_value = (char *) xmlNodeGetContent(values_node->nodeTab[0]);
303   switch (ds->ds[index].type)
304   {
305     case DS_TYPE_COUNTER:
306       vl->values[index].counter = (counter_t) strtoull (node_value,
307           /* endptr = */ NULL, /* base = */ 0);
308       break;
309     case DS_TYPE_DERIVE:
310       vl->values[index].derive = (derive_t) strtoll (node_value,
311           /* endptr = */ NULL, /* base = */ 0);
312       break;
313     case DS_TYPE_ABSOLUTE:
314       vl->values[index].absolute = (absolute_t) strtoull (node_value,
315           /* endptr = */ NULL, /* base = */ 0);
316       break;
317     case DS_TYPE_GAUGE: 
318       vl->values[index].gauge = (gauge_t) strtod (node_value,
319           /* endptr = */ NULL);
320   }
321
322   /* free up object */
323   xmlXPathFreeObject (values_node_obj);
324
325   /* We have reached here which means that
326    * we have got something to work */
327   return (0);
328 } /* }}} int cx_handle_single_value_xpath */
329
330 static int cx_handle_all_value_xpaths (xmlXPathContextPtr xpath_ctx, /* {{{ */
331     cx_xpath_t *xpath,
332     const data_set_t *ds, value_list_t *vl)
333 {
334   value_t values[xpath->values_len];
335   int status;
336   int i;
337
338   assert (xpath->values_len > 0);
339   assert (xpath->values_len == vl->values_len);
340   assert (xpath->values_len == ds->ds_num);
341   vl->values = values;
342
343   for (i = 0; i < xpath->values_len; i++)
344   {
345     status = cx_handle_single_value_xpath (xpath_ctx, xpath, ds, vl, i);
346     if (status != 0)
347       return (-1); /* An error has been printed. */
348   } /* for (i = 0; i < xpath->values_len; i++) */
349
350   plugin_dispatch_values (vl);
351   vl->values = NULL;
352
353   return (0);
354 } /* }}} int cx_handle_all_value_xpaths */
355
356 static int cx_handle_instance_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */
357     cx_xpath_t *xpath, value_list_t *vl,
358     _Bool is_table)
359 {
360   xmlXPathObjectPtr instance_node_obj = NULL;
361   xmlNodeSetPtr instance_node = NULL;
362
363   memset (vl->type_instance, 0, sizeof (vl->type_instance));
364
365   /* If the base xpath returns more than one block, the result is assumed to be
366    * a table. The `Instnce' option is not optional in this case. Check for the
367    * condition and inform the user. */
368   if (is_table && (vl->type_instance == NULL))
369   {
370     WARNING ("curl_xml plugin: "
371         "Base-XPath %s is a table (more than one result was returned), "
372         "but no instance-XPath has been defined.",
373         xpath->path);
374     return (-1);
375   }
376
377   /* instance has to be an xpath expression */
378   if (xpath->instance != NULL)
379   {
380     int tmp_size;
381
382     instance_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->instance);
383     if (instance_node_obj == NULL)
384       return (-1); /* error is logged already */
385
386     instance_node = instance_node_obj->nodesetval;
387     tmp_size = (instance_node) ? instance_node->nodeNr : 0;
388
389     if (tmp_size <= 0)
390     {
391       WARNING ("curl_xml plugin: "
392           "relative xpath expression for 'InstanceFrom' \"%s\" doesn't match "
393           "any of the nodes. Skipping the node.", xpath->instance);
394       xmlXPathFreeObject (instance_node_obj);
395       return (-1);
396     }
397
398     if (tmp_size > 1)
399     {
400       WARNING ("curl_xml plugin: "
401           "relative xpath expression for 'InstanceFrom' \"%s\" is expected "
402           "to return only one text node. Skipping the node.", xpath->instance);
403       xmlXPathFreeObject (instance_node_obj);
404       return (-1);
405     }
406
407     /* ignoring the element if other than textnode/attribute */
408     if (cx_if_not_text_node(instance_node->nodeTab[0]))
409     {
410       WARNING ("curl_xml plugin: "
411           "relative xpath expression \"%s\" is expected to return only text node "
412           "which is not the case. Skipping the node.", xpath->instance);
413       xmlXPathFreeObject (instance_node_obj);
414       return (-1);
415     }
416   } /* if (xpath->instance != NULL) */
417
418   if (xpath->instance_prefix != NULL)
419   {
420     if (instance_node != NULL)
421       ssnprintf (vl->type_instance, sizeof (vl->type_instance),"%s%s",
422           xpath->instance_prefix, (char *) xmlNodeGetContent(instance_node->nodeTab[0]));
423     else
424       sstrncpy (vl->type_instance, xpath->instance_prefix,
425           sizeof (vl->type_instance));
426   }
427   else
428   {
429     /* If instance_prefix and instance_node are NULL, then
430      * don't set the type_instance */
431     if (instance_node != NULL)
432       sstrncpy (vl->type_instance, (char *) xmlNodeGetContent(instance_node->nodeTab[0]),
433           sizeof (vl->type_instance));
434   }
435
436   /* Free `instance_node_obj' this late, because `instance_node' points to
437    * somewhere inside this structure. */
438   xmlXPathFreeObject (instance_node_obj);
439
440   return (0);
441 } /* }}} int cx_handle_instance_xpath */
442
443 static int  cx_handle_base_xpath (char const *plugin_instance, /* {{{ */
444     char const *host,
445     xmlXPathContextPtr xpath_ctx, const data_set_t *ds, 
446     char *base_xpath, cx_xpath_t *xpath)
447 {
448   int total_nodes;
449   int i;
450
451   xmlXPathObjectPtr base_node_obj = NULL;
452   xmlNodeSetPtr base_nodes = NULL;
453
454   value_list_t vl = VALUE_LIST_INIT;
455
456   base_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST base_xpath); 
457   if (base_node_obj == NULL)
458     return -1; /* error is logged already */
459
460   base_nodes = base_node_obj->nodesetval;
461   total_nodes = (base_nodes) ? base_nodes->nodeNr : 0;
462
463   if (total_nodes == 0)
464   {
465      ERROR ("curl_xml plugin: "
466               "xpath expression \"%s\" doesn't match any of the nodes. "
467               "Skipping the xpath block...", base_xpath);
468      xmlXPathFreeObject (base_node_obj);
469      return -1;
470   }
471
472   /* If base_xpath returned multiple results, then */
473   /* Instance in the xpath block is required */ 
474   if (total_nodes > 1 && xpath->instance == NULL)
475   {
476     ERROR ("curl_xml plugin: "
477              "InstanceFrom is must in xpath block since the base xpath expression \"%s\" "
478              "returned multiple results. Skipping the xpath block...", base_xpath);
479     return -1;
480   }
481
482   /* set the values for the value_list */
483   vl.values_len = ds->ds_num;
484   sstrncpy (vl.type, xpath->type, sizeof (vl.type));
485   sstrncpy (vl.plugin, "curl_xml", sizeof (vl.plugin));
486   sstrncpy (vl.host, (host != NULL) ? host : hostname_g, sizeof (vl.host));
487   if (plugin_instance != NULL)
488     sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance)); 
489
490   for (i = 0; i < total_nodes; i++)
491   {
492     int status;
493
494     xpath_ctx->node = base_nodes->nodeTab[i];
495
496     status = cx_handle_instance_xpath (xpath_ctx, xpath, &vl,
497         /* is_table = */ (total_nodes > 1));
498     if (status != 0)
499       continue; /* An error has already been reported. */
500
501     status = cx_handle_all_value_xpaths (xpath_ctx, xpath, ds, &vl);
502     if (status != 0)
503       continue; /* An error has been logged. */
504   } /* for (i = 0; i < total_nodes; i++) */
505
506   /* free up the allocated memory */
507   xmlXPathFreeObject (base_node_obj); 
508
509   return (0); 
510 } /* }}} cx_handle_base_xpath */
511
512 static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */ 
513                        xmlXPathContextPtr xpath_ctx, cx_t *db)
514 {
515   llentry_t *le;
516   const data_set_t *ds;
517   cx_xpath_t *xpath;
518   int status=-1;
519   
520
521   le = llist_head (db->list);
522   while (le != NULL)
523   {
524     /* get the ds */
525     xpath = (cx_xpath_t *) le->value;
526     ds = plugin_get_ds (xpath->type);
527
528     if ( (cx_check_type(ds, xpath) == 0) &&
529          (cx_handle_base_xpath(db->instance, db->host,
530                                xpath_ctx, ds, le->key, xpath) == 0) )
531       status = 0; /* we got atleast one success */
532
533     le = le->next;
534   } /* while (le != NULL) */
535
536   return status;
537 } /* }}} cx_handle_parsed_xml */
538
539 static int cx_parse_stats_xml(xmlChar* xml, cx_t *db) /* {{{ */
540 {
541   int status;
542   xmlDocPtr doc;
543   xmlXPathContextPtr xpath_ctx;
544
545   /* Load the XML */
546   doc = xmlParseDoc(xml);
547   if (doc == NULL)
548   {
549     ERROR ("curl_xml plugin: Failed to parse the xml document  - %s", xml);
550     return (-1);
551   }
552
553   xpath_ctx = xmlXPathNewContext(doc);
554   if(xpath_ctx == NULL)
555   {
556     ERROR ("curl_xml plugin: Failed to create the xml context");
557     xmlFreeDoc(doc);
558     return (-1);
559   }
560
561   status = cx_handle_parsed_xml (doc, xpath_ctx, db);
562   /* Cleanup */
563   xmlXPathFreeContext(xpath_ctx);
564   xmlFreeDoc(doc);
565   return status;
566 } /* }}} cx_parse_stats_xml */
567
568 static int cx_curl_perform (cx_t *db, CURL *curl) /* {{{ */
569 {
570   int status;
571   long rc;
572   char *ptr;
573   char *url;
574
575   db->buffer_fill = 0; 
576   status = curl_easy_perform (curl);
577   if (status != CURLE_OK)
578   {
579     ERROR ("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)",
580            status, db->curl_errbuf, url);
581     return (-1);
582   }
583
584   curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
585   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
586
587   /* The response code is zero if a non-HTTP transport was used. */
588   if ((rc != 0) && (rc != 200))
589   {
590     ERROR ("curl_xml plugin: curl_easy_perform failed with response code %ld (%s)",
591            rc, url);
592     return (-1);
593   }
594
595   ptr = db->buffer;
596
597   status = cx_parse_stats_xml(BAD_CAST ptr, db);
598   db->buffer_fill = 0;
599
600   return status;
601 } /* }}} int cx_curl_perform */
602
603 static int cx_read (user_data_t *ud) /* {{{ */
604 {
605   cx_t *db;
606
607   if ((ud == NULL) || (ud->data == NULL))
608   {
609     ERROR ("curl_xml plugin: cx_read: Invalid user data.");
610     return (-1);
611   }
612
613   db = (cx_t *) ud->data;
614
615   return cx_curl_perform (db, db->curl);
616 } /* }}} int cx_read */
617
618 /* Configuration handling functions {{{ */
619
620 static int cx_config_add_values (const char *name, cx_xpath_t *xpath, /* {{{ */
621                                       oconfig_item_t *ci)
622 {
623   int i;
624
625   if (ci->values_num < 1)
626   {
627     WARNING ("curl_xml plugin: `ValuesFrom' needs at least one argument.");
628     return (-1);
629   }
630
631   for (i = 0; i < ci->values_num; i++)
632     if (ci->values[i].type != OCONFIG_TYPE_STRING)
633     {
634       WARNING ("curl_xml plugin: `ValuesFrom' needs only string argument.");
635       return (-1);
636     }
637
638   sfree (xpath->values);
639
640   xpath->values_len = 0;
641   xpath->values = (cx_values_t *) malloc (sizeof (cx_values_t) * ci->values_num);
642   if (xpath->values == NULL)
643     return (-1);
644   xpath->values_len = ci->values_num;
645
646   /* populate cx_values_t structure */
647   for (i = 0; i < ci->values_num; i++)
648   {
649     xpath->values[i].path_len = sizeof (ci->values[i].value.string);
650     sstrncpy (xpath->values[i].path, ci->values[i].value.string, sizeof (xpath->values[i].path));
651   }
652
653   return (0); 
654 } /* }}} cx_config_add_values */
655
656 static int cx_config_add_xpath (cx_t *db, /* {{{ */
657                                    oconfig_item_t *ci)
658 {
659   cx_xpath_t *xpath;
660   int status;
661   int i;
662
663   xpath = (cx_xpath_t *) malloc (sizeof (*xpath));
664   if (xpath == NULL)
665   {
666     ERROR ("curl_xml plugin: malloc failed.");
667     return (-1);
668   }
669   memset (xpath, 0, sizeof (*xpath));
670
671   status = cf_util_get_string (ci, &xpath->path);
672   if (status != 0)
673   {
674     sfree (xpath);
675     return (status);
676   }
677
678   /* error out if xpath->path is an empty string */
679   if (*xpath->path == 0)
680   {
681     ERROR ("curl_xml plugin: invalid xpath. "
682            "xpath value can't be an empty string");
683     return (-1);
684   }
685
686   status = 0;
687   for (i = 0; i < ci->children_num; i++)
688   {
689     oconfig_item_t *child = ci->children + i;
690
691     if (strcasecmp ("Type", child->key) == 0)
692       status = cf_util_get_string (child, &xpath->type);
693     else if (strcasecmp ("InstancePrefix", child->key) == 0)
694       status = cf_util_get_string (child, &xpath->instance_prefix);
695     else if (strcasecmp ("InstanceFrom", child->key) == 0)
696       status = cf_util_get_string (child, &xpath->instance);
697     else if (strcasecmp ("ValuesFrom", child->key) == 0)
698       status = cx_config_add_values ("ValuesFrom", xpath, child);
699     else
700     {
701       WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
702       status = -1;
703     }
704
705     if (status != 0)
706       break;
707   } /* for (i = 0; i < ci->children_num; i++) */
708
709   if (status == 0 && xpath->type == NULL)
710   {
711     WARNING ("curl_xml plugin: `Type' missing in `xpath' block.");
712     status = -1;
713   }
714
715   if (status == 0)
716   {
717     char *name;
718     llentry_t *le;
719
720     if (db->list == NULL)
721     {
722       db->list = llist_create();
723       if (db->list == NULL)
724       {
725         ERROR ("curl_xml plugin: list creation failed.");
726         return (-1);
727       }
728     }
729
730     name = strdup(xpath->path);
731     if (name == NULL)
732     {
733         ERROR ("curl_xml plugin: strdup failed.");
734         return (-1);
735     }
736
737     le = llentry_create (name, xpath);
738     if (le == NULL)
739     {
740       ERROR ("curl_xml plugin: llentry_create failed.");
741       return (-1);
742     }
743
744     llist_append (db->list, le);
745   }
746
747   return (status);
748 } /* }}} int cx_config_add_xpath */
749
750 /* Initialize db->curl */
751 static int cx_init_curl (cx_t *db) /* {{{ */
752 {
753   db->curl = curl_easy_init ();
754   if (db->curl == NULL)
755   {
756     ERROR ("curl_xml plugin: curl_easy_init failed.");
757     return (-1);
758   }
759
760   curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1L);
761   curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cx_curl_callback);
762   curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db);
763   curl_easy_setopt (db->curl, CURLOPT_USERAGENT,
764                     PACKAGE_NAME"/"PACKAGE_VERSION);
765   curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
766   curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
767
768   if (db->user != NULL)
769   {
770     size_t credentials_size;
771
772     credentials_size = strlen (db->user) + 2;
773     if (db->pass != NULL)
774       credentials_size += strlen (db->pass);
775
776     db->credentials = (char *) malloc (credentials_size);
777     if (db->credentials == NULL)
778     {
779       ERROR ("curl_xml plugin: malloc failed.");
780       return (-1);
781     }
782
783     ssnprintf (db->credentials, credentials_size, "%s:%s",
784                db->user, (db->pass == NULL) ? "" : db->pass);
785     curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
786   }
787
788   curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, db->verify_peer ? 1L : 0L);
789   curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST,
790                     db->verify_host ? 2L : 0L);
791   if (db->cacert != NULL)
792     curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
793   if (db->headers != NULL)
794     curl_easy_setopt (db->curl, CURLOPT_HTTPHEADER, db->headers);
795   if (db->post_body != NULL)
796     curl_easy_setopt (db->curl, CURLOPT_POSTFIELDS, db->post_body);
797
798   return (0);
799 } /* }}} int cx_init_curl */
800
801 static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */
802 {
803   cx_t *db;
804   int status = 0;
805   int i;
806
807   if ((ci->values_num != 1)
808       || (ci->values[0].type != OCONFIG_TYPE_STRING))
809   {
810     WARNING ("curl_xml plugin: The `URL' block "
811              "needs exactly one string argument.");
812     return (-1);
813   }
814
815   db = (cx_t *) malloc (sizeof (*db));
816   if (db == NULL)
817   {
818     ERROR ("curl_xml plugin: malloc failed.");
819     return (-1);
820   }
821   memset (db, 0, sizeof (*db));
822
823   if (strcasecmp ("URL", ci->key) == 0)
824   {
825     status = cf_util_get_string (ci, &db->url);
826     if (status != 0)
827     {
828       sfree (db);
829       return (status);
830     }
831   }
832   else
833   {
834     ERROR ("curl_xml plugin: cx_config: "
835            "Invalid key: %s", ci->key);
836     return (-1);
837   }
838
839   /* Fill the `cx_t' structure.. */
840   for (i = 0; i < ci->children_num; i++)
841   {
842     oconfig_item_t *child = ci->children + i;
843
844     if (strcasecmp ("Instance", child->key) == 0)
845       status = cf_util_get_string (child, &db->instance);
846     else if (strcasecmp ("Host", child->key) == 0)
847       status = cf_util_get_string (child, &db->host);
848     else if (strcasecmp ("User", child->key) == 0)
849       status = cf_util_get_string (child, &db->user);
850     else if (strcasecmp ("Password", child->key) == 0)
851       status = cf_util_get_string (child, &db->pass);
852     else if (strcasecmp ("VerifyPeer", child->key) == 0)
853       status = cf_util_get_boolean (child, &db->verify_peer);
854     else if (strcasecmp ("VerifyHost", child->key) == 0)
855       status = cf_util_get_boolean (child, &db->verify_host);
856     else if (strcasecmp ("CACert", child->key) == 0)
857       status = cf_util_get_string (child, &db->cacert);
858     else if (strcasecmp ("xpath", child->key) == 0)
859       status = cx_config_add_xpath (db, child);
860     else if (strcasecmp ("Header", child->key) == 0)
861       status = cx_config_append_string ("Header", &db->headers, child);
862     else if (strcasecmp ("Post", child->key) == 0)
863       status = cf_util_get_string (child, &db->post_body);
864     else
865     {
866       WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
867       status = -1;
868     }
869
870     if (status != 0)
871       break;
872   }
873
874   if (status == 0)
875   {
876     if (db->list == NULL)
877     {
878       WARNING ("curl_xml plugin: No (valid) `Key' block "
879                "within `URL' block `%s'.", db->url);
880       status = -1;
881     }
882     if (status == 0)
883       status = cx_init_curl (db);
884   }
885
886   /* If all went well, register this database for reading */
887   if (status == 0)
888   {
889     user_data_t ud;
890     char cb_name[DATA_MAX_NAME_LEN];
891
892     if (db->instance == NULL)
893       db->instance = strdup("default");
894
895     DEBUG ("curl_xml plugin: Registering new read callback: %s",
896            db->instance);
897
898     memset (&ud, 0, sizeof (ud));
899     ud.data = (void *) db;
900     ud.free_func = cx_free;
901
902     ssnprintf (cb_name, sizeof (cb_name), "curl_xml-%s-%s",
903                db->instance, db->url);
904
905     plugin_register_complex_read (/* group = */ NULL, cb_name, cx_read,
906                                   /* interval = */ NULL, &ud);
907   }
908   else
909   {
910     cx_free (db);
911     return (-1);
912   }
913
914   return (0);
915 } /* }}} int cx_config_add_url */
916
917 /* }}} End of configuration handling functions */
918
919 static int cx_config (oconfig_item_t *ci) /* {{{ */
920 {
921   int success;
922   int errors;
923   int status;
924   int i;
925
926   success = 0;
927   errors = 0;
928
929   for (i = 0; i < ci->children_num; i++)
930   {
931     oconfig_item_t *child = ci->children + i;
932
933     if (strcasecmp ("URL", child->key) == 0)
934     {
935       status = cx_config_add_url (child);
936       if (status == 0)
937         success++;
938       else
939         errors++;
940     }
941     else
942     {
943       WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
944       errors++;
945     }
946   }
947
948   if ((success == 0) && (errors > 0))
949   {
950     ERROR ("curl_xml plugin: All statements failed.");
951     return (-1);
952   }
953
954   return (0);
955 } /* }}} int cx_config */
956
957 void module_register (void)
958 {
959   plugin_register_complex_config ("curl_xml", cx_config);
960 } /* void module_register */
961
962 /* vim: set sw=2 sts=2 et fdm=marker : */