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