]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
styleguide
authorWilfried Goesgens <willi@arangodb.com>
Sun, 8 Oct 2017 12:04:02 +0000 (14:04 +0200)
committerWilfried Goesgens <willi@arangodb.com>
Tue, 3 Mar 2020 13:05:14 +0000 (14:05 +0100)
src/curl_jolokia.c [new file with mode: 0644]

diff --git a/src/curl_jolokia.c b/src/curl_jolokia.c
new file mode 100644 (file)
index 0000000..8dce45c
--- /dev/null
@@ -0,0 +1,1099 @@
+/**
+ * collectd - src/curl_jolokia.c
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2006-2013  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Florian octo Forster <octo at collectd.org>
+ *   Wilfried dothebart Goesgens <dothebart at citadel.org>
+ **/
+
+#include "common.h"
+#include "configfile.h"
+#include "plugin.h"
+#include "utils_complain.h"
+#include "utils_llist.h"
+#include "collectd.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <curl/curl.h>
+
+#include <yajl/yajl_parse.h>
+#if HAVE_YAJL_YAJL_VERSION_H
+#include <yajl/yajl_version.h>
+#endif
+
+#if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
+#define HAVE_YAJL_V2 1
+#endif
+
+#define CJO_DEFAULT_HOST "localhost"
+
+#if HAVE_YAJL_V2
+typedef size_t yajl_len_t;
+#else
+typedef unsigned int yajl_len_t;
+#endif
+
+struct cjo_attribute_s /* {{{ */
+{
+  char *attribute_name;
+  char *attribute_match;
+  size_t attribute_match_len;
+  char *type;
+};
+typedef struct cjo_attribute_s cjo_attribute_t;
+/* }}} */
+
+struct cjo_bean_s /* {{{ */
+{
+  char *bean_name;
+  char *mbean_match;
+  char *mbean_namespace;
+  size_t mbean_match_len;
+  llist_t *attributes; /* list of cjo_attribute_t */
+};
+typedef struct cjo_bean_s cjo_bean_t;
+/* }}} */
+
+typedef enum _expect_token { eNone = 0, eValue, eMBean } cjo_expect_token;
+
+/* {{{ */
+struct cjo_attribute_values_s {
+  const char *json_value;
+  yajl_len_t json_value_len;
+  const char *json_name;
+  yajl_len_t json_name_len;
+};
+typedef struct cjo_attribute_values_s cjo_attribute_values_t;
+/* }}} */
+
+struct cjo_membuffer_s {
+  char *buffer;
+  size_t size;
+  size_t used;
+};
+
+typedef struct cjo_membuffer_s cjo_membuffer_t;
+
+struct cjo_s /* {{{ */
+{
+  char *instance;
+  char *host;
+
+  char *sock;
+
+  char *url;
+  char *user;
+  char *pass;
+  char *credentials;
+
+  const cjo_bean_t *match_this_bean;
+  cjo_attribute_values_t *curr_attribute; /* points to the pool below. */
+  cjo_attribute_values_t *attributepool;
+  int attribute_pool_used;
+
+  const char *json_key;
+  yajl_len_t json_key_len;
+
+  cjo_expect_token expect;
+
+  _Bool verify_peer;
+  _Bool verify_host;
+  char *cacert;
+  struct curl_slist *headers;
+  char *post_body;
+  size_t post_body_len;
+  _Bool expect_escapes;
+  cdtime_t interval;
+  int timeout;
+
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  const unsigned char *start, *end;
+  cjo_membuffer_t replybuffer;
+  cjo_membuffer_t itembuffer;
+
+  size_t poolsize;
+  char *pool;
+  const char *poolmax;
+  char *pool_ptr;
+
+  yajl_handle yajl;
+  llist_t *bean_configs;
+  int max_attribute_count;
+  int max_value_len;
+};
+typedef struct cjo_s cjo_t; /* }}} */
+
+static void cjo_init_buffer(cjo_membuffer_t *buf, size_t initial_size) {
+  buf->buffer = malloc(initial_size);
+  buf->used = 0;
+  buf->size = initial_size;
+}
+
+static void cjo_append_buffer(cjo_membuffer_t *buf, const char *append,
+                              size_t appendlen) {
+  if (buf->used + appendlen + 1 > buf->size) {
+    char *newbuf;
+    size_t newsize;
+
+    newsize = buf->size * 2;
+
+    newbuf = malloc(newsize);
+    memcpy(newbuf, buf->buffer, buf->used);
+    free(buf->buffer);
+    buf->buffer = newbuf;
+    buf->size = newsize;
+  }
+  memcpy(buf->buffer + buf->used, append, appendlen);
+  buf->used += appendlen;
+}
+
+static void cjo_release_buffercontent(cjo_membuffer_t *buf) {
+  sfree(buf->buffer);
+  memset(buf, 0, sizeof(cjo_membuffer_t));
+}
+
+static void cjo_remember_value(cjo_membuffer_t *ShouldBeInHere,
+                               cjo_membuffer_t *BufferHereIfNot,
+                               const char *ptr, size_t len,
+                               const char **SetThisOne, size_t *SetThisLen) {
+  if (!((ptr > ShouldBeInHere->buffer) &&
+        (ptr < ShouldBeInHere->buffer + ShouldBeInHere->used))) {
+    char *tptr;
+    tptr = BufferHereIfNot->buffer + BufferHereIfNot->used;
+    cjo_append_buffer(BufferHereIfNot, ptr, len);
+    ptr = tptr;
+  }
+
+  *SetThisOne = ptr;
+  *SetThisLen = len;
+}
+
+/*
+ * llist lookup methods
+ */
+static int cjo_cfg_compare_attribute(llentry_t *match, void *vdb) {
+  cjo_t *db = (cjo_t *)vdb;
+  cjo_attribute_t *pcfg = (cjo_attribute_t *)match->value;
+
+  if (pcfg->attribute_match_len != db->curr_attribute->json_name_len)
+    return -1;
+  else
+    return strncmp(pcfg->attribute_match, db->curr_attribute->json_name,
+                   db->curr_attribute->json_name_len);
+}
+static int cjo_cfg_compare_bean(llentry_t *match, void *vdb) {
+  cjo_t *db = (cjo_t *)vdb;
+  cjo_bean_t *pbeancfg = (cjo_bean_t *)match->value;
+
+  if (pbeancfg->mbean_match_len != db->json_key_len)
+    return -1;
+  return strncmp(pbeancfg->mbean_match, db->json_key, db->json_key_len);
+}
+
+/*
+ * Aligning data with Configs & Sending it
+ */
+
+static int cjo_get_type(cjo_attribute_t *attribute) {
+  const data_set_t *ds;
+
+  assert(attribute->type != NULL);
+  ds = plugin_get_ds(attribute->type);
+  if (ds == NULL) {
+    static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
+
+    if (strcmp(type, attribute->type) != 0) {
+      ERROR("curl_jolokia plugin: Unable to look up DS type \"%s\".",
+            attribute->type);
+      sstrncpy(type, attribute->type, sizeof(type));
+    }
+
+    return -1;
+  } else if (ds->ds_num > 1) {
+    static c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
+
+    c_complain_once(
+        LOG_WARNING, &complaint,
+        "curl_jolokia plugin: The type \"%s\" has more than one data source. "
+        "This is currently not supported. I will return the type of the "
+        "first data source, but this will likely lead to problems later on.",
+        attribute->type);
+  }
+
+  return ds->ds[0].type;
+}
+
+static void cjo_submit(cjo_t *db) /* {{{ */
+{
+  llentry_t *cfgptr;
+  char buffer[db->max_value_len + 1];
+  value_t ret_value;
+  char *host;
+  int status;
+  int i;
+  int ds_type;
+  cjo_attribute_t *curr_attribute;
+
+  if ((db->match_this_bean == NULL) || (db->attribute_pool_used == 0))
+    return; /* nothing to do yet. */
+
+  for (i = 0; i < db->attribute_pool_used; i++) {
+    value_list_t vl = VALUE_LIST_INIT;
+
+    db->curr_attribute = &db->attributepool[i];
+    cfgptr = llist_search_custom(db->match_this_bean->attributes,
+                                 cjo_cfg_compare_attribute, db);
+    if (cfgptr == NULL) {
+      char attribute[db->curr_attribute->json_name_len + 1];
+
+      memcpy(attribute, db->curr_attribute->json_name,
+             db->curr_attribute->json_name_len);
+      attribute[db->curr_attribute->json_name_len] = '\0';
+      ERROR("curl_jolokia plugin: failed to locate attribute [%s:\"%s\"]",
+            db->match_this_bean->bean_name, attribute);
+
+      continue; /* we don't know this! */
+    }
+    curr_attribute = cfgptr->value;
+
+    /* Create a null-terminated version of the string. */
+    ds_type = cjo_get_type(curr_attribute);
+    if (ds_type == -1) {
+      char attribute[db->curr_attribute->json_name_len + 1];
+
+      memcpy(attribute, db->curr_attribute->json_name,
+             db->curr_attribute->json_name_len);
+      attribute[db->curr_attribute->json_name_len] = '\0';
+
+      ERROR("curl_jolokia plugin: failed to map type for [%s:%s:%s]",
+            db->match_this_bean->bean_name, attribute, curr_attribute->type);
+      continue;
+    }
+
+    memset(&ret_value, 0, sizeof(ret_value));
+
+    memcpy(buffer, db->curr_attribute->json_value,
+           db->curr_attribute->json_value_len);
+
+    buffer[db->curr_attribute->json_value_len] = '\0';
+
+    status = parse_value(buffer, &ret_value, ds_type);
+    if (status != 0) {
+      char attribute[db->curr_attribute->json_name_len + 1];
+
+      memcpy(attribute, db->curr_attribute->json_name,
+             db->curr_attribute->json_name_len);
+      attribute[db->curr_attribute->json_name_len] = '\0';
+
+      WARNING("curl_jolokia plugin: Unable to parse number: [%s:%s:\"%s\"]",
+              db->match_this_bean->bean_name, attribute, buffer);
+      continue;
+    }
+
+    vl.values = &ret_value;
+    vl.values_len = 1;
+
+    if ((db->host == NULL) || (strcmp("", db->host) == 0) ||
+        (strcmp(CJO_DEFAULT_HOST, db->host) == 0))
+      host = hostname_g;
+    else
+      host = db->host;
+
+    sstrncpy(vl.type_instance, curr_attribute->attribute_name,
+             sizeof(vl.type_instance));
+    sstrncpy(vl.plugin_instance, db->match_this_bean->bean_name,
+             sizeof(vl.type_instance));
+
+    sstrncpy(vl.host, host, sizeof(vl.host));
+    if (db->match_this_bean->mbean_namespace == NULL)
+      sstrncpy(vl.plugin, "jolokia", sizeof(vl.plugin));
+    else
+      sstrncpy(vl.plugin, db->match_this_bean->mbean_namespace,
+               sizeof(vl.plugin));
+    sstrncpy(vl.type, curr_attribute->type, sizeof(vl.type));
+
+    plugin_dispatch_values(&vl);
+  }
+
+  /* flush values */
+  db->match_this_bean = NULL;
+  db->attribute_pool_used = 0;
+
+  memset(db->attributepool, 0,
+         sizeof(*db->attributepool) * db->max_attribute_count);
+
+} /* }}} int cjo_submit */
+
+/* yajl callbacks */
+#define CJO_CB_ABORT 0
+#define CJO_CB_CONTINUE 1
+
+static int cjo_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
+  llentry_t *cfgptr;
+  cjo_t *db = (cjo_t *)ctx;
+
+  switch (db->expect) {
+  case eValue:
+    cjo_remember_value(&db->replybuffer, &db->itembuffer, (const char *)val,
+                       len, &db->curr_attribute->json_value,
+                       &db->curr_attribute->json_value_len);
+
+    if (len > db->max_value_len)
+      db->max_value_len = len;
+
+    break;
+  case eMBean:
+    db->json_key = (const char *)val;
+    db->json_key_len = len;
+
+    cfgptr = llist_search_custom(db->bean_configs, cjo_cfg_compare_bean, db);
+    if (cfgptr != NULL) {
+      db->match_this_bean = cfgptr->value;
+      cjo_submit(db);
+    }
+    db->expect = eNone;
+    break;
+  case eNone:
+    break; /* Don't care... */
+  }
+
+  /* Handle the string as if it was a number. */
+  return CJO_CB_CONTINUE;
+} /* int cjo_cb_string */
+
+static int cjo_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
+  cjo_t *db = (cjo_t *)ctx;
+
+  switch (db->expect) {
+  case eValue:
+    cjo_remember_value(&db->replybuffer, &db->itembuffer, number, number_len,
+                       &db->curr_attribute->json_value,
+                       &db->curr_attribute->json_value_len);
+
+    if (number_len > db->max_value_len)
+      db->max_value_len = number_len;
+    break;
+  case eMBean:
+  case eNone:
+    db->expect = eNone;
+    break; /* Don't care... */
+  }
+
+  /* are we complete yet? */
+
+  return CJO_CB_CONTINUE;
+} /* int cjo_cb_number */
+
+static int cjo_cb_map_key(void *ctx, unsigned char const *in_name,
+                          yajl_len_t in_name_len) {
+  cjo_t *db = (cjo_t *)ctx;
+
+  if ((in_name_len == 5) && !strncmp((const char *)in_name, "value", 5)) {
+    db->expect = eValue;
+  } else if ((in_name_len == 5) &&
+             !strncmp((const char *)in_name, "mbean", 5)) {
+    db->expect = eMBean;
+  } else if (db->expect == eValue) {
+    if (db->attribute_pool_used <= db->max_attribute_count) {
+      db->curr_attribute = &db->attributepool[db->attribute_pool_used];
+      db->attribute_pool_used++;
+    } else {
+      char buffer[in_name_len + 1];
+
+      memcpy(buffer, in_name, in_name_len);
+      buffer[in_name_len] = '\0';
+      ERROR("curl_jolokia plugin: attribute pool[%d/%d] [%s] exhausted! We may "
+            "loose values!",
+            db->attribute_pool_used, db->max_attribute_count, buffer);
+    }
+    cjo_remember_value(&db->replybuffer, &db->itembuffer, (const char *)in_name,
+                       in_name_len, &db->curr_attribute->json_name,
+                       &db->curr_attribute->json_name_len);
+  }
+  return CJO_CB_CONTINUE;
+}
+
+static int cjo_cb_end_map(void *ctx) {
+  cjo_t *db = (cjo_t *)ctx;
+
+  if (db->expect == eValue) {
+    cjo_submit(db);
+    db->expect = eNone;
+  }
+  return CJO_CB_CONTINUE;
+}
+
+static yajl_callbacks ycallbacks = {
+    NULL, /* null */
+    NULL, /* boolean */
+    NULL, /* integer */
+    NULL, /* double */
+    cjo_cb_number,  cjo_cb_string, NULL, cjo_cb_map_key,
+    cjo_cb_end_map, NULL,          NULL};
+
+/*
+ *------------------------------------------------------------------------------
+ * cURL Handling
+ */
+static size_t cjo_curl_callback(void *buf, /* {{{ */
+                                size_t size, size_t nmemb, void *user_data) {
+  cjo_t *db;
+  size_t len;
+
+  len = size * nmemb;
+
+  if (len <= 0)
+    return len;
+
+  db = user_data;
+  if (db == NULL)
+    return 0;
+
+  cjo_append_buffer(&db->replybuffer, (char *)buf, len);
+
+  return len;
+} /* }}} size_t cjo_curl_callback */
+
+static int cjo_init_curl(cjo_t *db) /* {{{ */
+{
+  db->curl = curl_easy_init();
+  if (db->curl == NULL) {
+    ERROR("curl_jolokia plugin: curl_easy_init failed.");
+    return -1;
+  }
+
+  curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
+  curl_easy_setopt(db->curl, CURLOPT_WRITEFUNCTION, cjo_curl_callback);
+  curl_easy_setopt(db->curl, CURLOPT_WRITEDATA, db);
+  curl_easy_setopt(db->curl, CURLOPT_USERAGENT,
+                   PACKAGE_NAME "/" PACKAGE_VERSION);
+  curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
+  curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
+
+  if (db->user != NULL) {
+    size_t credentials_size;
+
+    credentials_size = strlen(db->user) + 2;
+    if (db->pass != NULL)
+      credentials_size += strlen(db->pass);
+
+    db->credentials = (char *)malloc(credentials_size);
+    if (db->credentials == NULL) {
+      ERROR("curl_jolokia plugin: malloc failed.");
+      return -1;
+    }
+
+    snprintf(db->credentials, credentials_size, "%s:%s", db->user,
+             (db->pass == NULL) ? "" : db->pass);
+    curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
+  }
+
+  curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYPEER, (long)db->verify_peer);
+  curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYHOST, db->verify_host ? 2L : 0L);
+  if (db->cacert != NULL)
+    curl_easy_setopt(db->curl, CURLOPT_CAINFO, db->cacert);
+  if (db->headers != NULL)
+    curl_easy_setopt(db->curl, CURLOPT_HTTPHEADER, db->headers);
+  if (db->post_body != NULL)
+    curl_easy_setopt(db->curl, CURLOPT_POSTFIELDS, db->post_body);
+
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (db->timeout >= 0)
+    curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
+  else if (db->interval > 0)
+    curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
+                     (long)CDTIME_T_TO_MS(db->interval));
+  else
+    curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
+                     (long)CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+  return 0;
+} /* }}} int cjo_init_curl */
+
+static int cjo_sock_perform(cjo_t *db) /* {{{ */
+{
+  char errbuf[1024];
+  struct sockaddr_un sa_unix = {};
+  sa_unix.sun_family = AF_UNIX;
+  sstrncpy(sa_unix.sun_path, db->sock, sizeof(sa_unix.sun_path));
+
+  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (fd < 0)
+    return -1;
+  if (connect(fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0) {
+    ERROR("curl_jolokia plugin: connect(%s) failed: %s",
+          (db->sock != NULL) ? db->sock : "<null>",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    close(fd);
+    return -1;
+  }
+
+  ssize_t red;
+  do {
+    unsigned char buffer[4096];
+    red = read(fd, buffer, sizeof(buffer));
+    if (red < 0) {
+      ERROR("curl_jolokia plugin: read(%s) failed: %s",
+            (db->sock != NULL) ? db->sock : "<null>",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+      close(fd);
+      return -1;
+    }
+    if (!cjo_curl_callback(buffer, red, 1, db))
+      break;
+  } while (red > 0);
+  close(fd);
+  return 0;
+} /* }}} int cjo_sock_perform */
+
+static int cjo_curl_perform(cjo_t *db) /* {{{ */
+{
+  int status;
+  yajl_status json_status;
+  long rc;
+  char *url;
+  size_t len;
+  url = NULL;
+
+  curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
+
+  len = db->post_body_len;
+  if (len < 4096)
+    len = 4096;
+
+  cjo_init_buffer(&db->replybuffer, len * 4);
+  cjo_init_buffer(&db->itembuffer, len);
+
+  status = curl_easy_perform(db->curl);
+  if (status != CURLE_OK) {
+    ERROR(
+        "curl_jolokia plugin: curl_easy_perform failed with status %i: %s (%s)",
+        status, db->curl_errbuf, (url != NULL) ? url : "<null>");
+    cjo_release_buffercontent(&db->replybuffer);
+    return -1;
+  }
+
+  curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc);
+
+  /* The response code is zero if a non-HTTP transport was used. */
+  if ((rc != 0) && (rc != 200)) {
+    ERROR("curl_jolokia plugin: curl_easy_perform failed with "
+          "response code %ld (%s)",
+          rc, url);
+    cjo_release_buffercontent(&db->replybuffer);
+    return -1;
+  }
+
+  json_status = yajl_parse(db->yajl, (unsigned char *)db->replybuffer.buffer,
+                           db->replybuffer.used);
+
+  if (json_status != yajl_status_ok) {
+    unsigned char *msg =
+        yajl_get_error(db->yajl, /* verbose = */ 1,
+                       /* jsonText = */ (unsigned char *)db->replybuffer.buffer,
+                       db->replybuffer.used);
+    ERROR("curl_jolokia plugin: yajl_parse failed: %s", msg);
+    yajl_free_error(db->yajl, msg);
+  }
+
+  cjo_release_buffercontent(&db->replybuffer);
+  cjo_release_buffercontent(&db->itembuffer);
+  return 0;
+} /* }}} int cjo_curl_perform */
+
+static int cjo_perform(cjo_t *db) /* {{{ */
+{
+  int status;
+  yajl_handle yprev = db->yajl;
+
+  db->yajl = yajl_alloc(&ycallbacks,
+#if HAVE_YAJL_V2
+                        /* alloc funcs = */ NULL,
+#else
+                        /* alloc funcs = */ NULL, NULL,
+#endif
+                        /* context = */ (void *)db);
+  if (db->yajl == NULL) {
+    ERROR("curl_jolokia plugin: yajl_alloc failed.");
+    db->yajl = yprev;
+    return -1;
+  }
+
+  if (db->url)
+    status = cjo_curl_perform(db);
+  else
+    status = cjo_sock_perform(db);
+  if (status < 0) {
+    yajl_free(db->yajl);
+    db->yajl = yprev;
+    return -1;
+  }
+
+#if HAVE_YAJL_V2
+  status = yajl_complete_parse(db->yajl);
+#else
+  status = yajl_parse_complete(db->yajl);
+#endif
+  if (status != yajl_status_ok) {
+    unsigned char *errmsg;
+
+    errmsg = yajl_get_error(db->yajl, /* verbose = */ 0,
+                            /* jsonText = */ NULL, /* jsonTextLen = */ 0);
+    ERROR("curl_jolokia plugin: yajl_parse_complete failed: %s",
+          (char *)errmsg);
+    yajl_free_error(db->yajl, errmsg);
+    yajl_free(db->yajl);
+    db->yajl = yprev;
+    return -1;
+  }
+
+  yajl_free(db->yajl);
+  db->yajl = yprev;
+  return 0;
+} /* }}} int cjo_perform */
+
+static int cjo_read(user_data_t *ud) /* {{{ */
+{
+  cjo_t *db;
+
+  if ((ud == NULL) || (ud->data == NULL)) {
+    ERROR("curl_jolokia plugin: cjo_read: Invalid user data.");
+    return -1;
+  }
+
+  db = (cjo_t *)ud->data;
+
+  return cjo_perform(db);
+} /* }}} int cjo_read */
+/* end cURL callbacks */
+
+/*----------------------------------------------------------------------------*/
+/* Configuration handling functions {{{ */
+
+static int cjo_config_append_string(const char *name,
+                                    struct curl_slist **dest, /* {{{ */
+                                    oconfig_item_t *ci) {
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    WARNING("curl_jolokia plugin: `%s' needs exactly one string argument.",
+            name);
+    return -1;
+  }
+
+  *dest = curl_slist_append(*dest, ci->values[0].value.string);
+  if (*dest == NULL)
+    return -1;
+
+  return 0;
+} /* }}} int cjo_config_append_string */
+
+void destroy_attribute_config(cjo_attribute_t *attribute) {
+  sfree(attribute->attribute_name);
+  sfree(attribute->attribute_match);
+  sfree(attribute->type);
+  sfree(attribute);
+}
+
+int cjo_get_attribute(cjo_bean_t *bean, oconfig_item_t *ci) {
+  cjo_attribute_t *attribute;
+  llentry_t *listentry;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    ERROR("curl_jolokia plugin: The `Attribute' block "
+          "needs exactly one string argument.");
+
+    return -1;
+  }
+  if (strcasecmp("AttributeName", ci->key)) {
+    ERROR("curl_jolokia plugin: cjo_config_add_attribute: "
+          "Invalid key: %s",
+          ci->key);
+    return -1;
+  }
+
+  attribute = (cjo_attribute_t *)malloc(sizeof(*attribute));
+  if (attribute == NULL) {
+    ERROR("curl_jolokia plugin: malloc of bean attribute failed.");
+
+    return -1;
+  }
+  memset(attribute, 0, sizeof(*attribute));
+
+  status = cf_util_get_string(ci, &attribute->attribute_name);
+  if (status != 0) {
+    ERROR("curl_jolokia plugin: failed to get attribute name.");
+
+    destroy_attribute_config(attribute);
+    return status;
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Attribute", child->key) == 0) {
+      status = cf_util_get_string(child, &attribute->attribute_match);
+    } else if (strcasecmp("Type", child->key) == 0) {
+      status = cf_util_get_string(child, &attribute->type);
+    } else {
+      ERROR("curl_jolokia plugin: Option `%s' not allowed in Bean Attribute.",
+            child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0) {
+    if ((attribute->attribute_name == NULL) ||
+        (attribute->attribute_match == NULL) || (attribute->type == NULL)) {
+      ERROR("curl_jolokia plugin: some attribute property is missing..");
+      status = -1;
+    }
+  }
+  if (status >= 0) {
+    attribute->attribute_match_len = strlen(attribute->attribute_match);
+    listentry = llentry_create(NULL, attribute);
+    llist_append(bean->attributes, listentry);
+  } else {
+    destroy_attribute_config(attribute);
+  }
+  return status;
+}
+
+void destroy_bean_config(cjo_bean_t *bean) {
+  llentry_t *e;
+
+  if (bean == NULL)
+    return;
+
+  for (e = llist_head(bean->attributes); e != NULL; e = e->next) {
+    cjo_attribute_t *attribute = (cjo_attribute_t *)e->value;
+    e->value = NULL;
+
+    destroy_attribute_config(attribute);
+  }
+
+  llist_destroy(bean->attributes);
+  sfree(bean->bean_name);
+  sfree(bean->mbean_match);
+  sfree(bean->mbean_namespace);
+  sfree(bean);
+}
+
+static int cjo_config_add_bean(cjo_t *db, /* {{{ */
+                               oconfig_item_t *ci) {
+  cjo_bean_t *bean;
+  llentry_t *listentry;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    ERROR("curl_jolokia plugin: The `BeanName' block "
+          "needs exactly one string argument.");
+
+    return -1;
+  }
+  if (strcasecmp("BeanName", ci->key)) {
+    ERROR("curl_jolokia plugin: cjo_config_add_bean: "
+          "Invalid key: %s",
+          ci->key);
+    return -1;
+  }
+
+  bean = (cjo_bean_t *)malloc(sizeof(*bean));
+  if (bean == NULL) {
+    ERROR("curl_jolokia plugin: malloc of bean property failed.");
+
+    return -1;
+  }
+  memset(bean, 0, sizeof(*bean));
+  bean->attributes = llist_create();
+  if (bean->attributes == NULL) {
+    ERROR("curl_jolokia plugin: malloc of bean property failed.");
+
+    destroy_bean_config(bean);
+    return -1;
+  }
+
+  status = cf_util_get_string(ci, &bean->bean_name);
+  if (status != 0) {
+    ERROR("curl_jolokia plugin: fetching of bean name failed.");
+
+    destroy_bean_config(bean);
+    return status;
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("MBean", child->key) == 0) {
+      status = cf_util_get_string(child, &bean->mbean_match);
+    } else if (strcasecmp("BeanNameSpace", child->key) == 0) {
+      status = cf_util_get_string(child, &bean->mbean_namespace);
+    } else if (strcasecmp("AttributeName", child->key) == 0) {
+      status = cjo_get_attribute(bean, child);
+    } else {
+      ERROR("curl_jolokia plugin: Option `%s' not allowed in Bean.",
+            child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0) {
+    if ((bean->bean_name == NULL) || (bean->mbean_match == NULL) ||
+        (bean->attributes == NULL) || (llist_size(bean->attributes) == 0)) {
+      ERROR("curl_jolokia plugin: some bean property is invalid..");
+      status = -1;
+    }
+  }
+  if (status >= 0) {
+    int count;
+
+    count = llist_size(bean->attributes);
+    if (count > db->max_attribute_count)
+      db->max_attribute_count = count;
+
+    bean->mbean_match_len = strlen(bean->mbean_match);
+    if (db->bean_configs == NULL)
+      db->bean_configs = llist_create();
+
+    listentry = llentry_create(NULL, bean);
+    llist_append(db->bean_configs, listentry);
+  } else {
+    destroy_bean_config(bean);
+  }
+
+  return status;
+} /* }}} int cjo_config_add_key */
+
+static void cjo_destroy_bean_configs(llist_t *bean_configs) {
+  llentry_t *e;
+
+  if (bean_configs == NULL)
+    return;
+
+  for (e = llist_head(bean_configs); e != NULL; e = e->next) {
+    cjo_bean_t *bean = (cjo_bean_t *)e->value;
+    e->value = NULL;
+
+    destroy_bean_config(bean);
+  }
+
+  llist_destroy(bean_configs);
+}
+static void cjo_free(void *arg) /* {{{ */
+{
+  cjo_t *db;
+
+  DEBUG("curl_jolokia plugin: cjo_free (arg = %p);", arg);
+
+  db = (cjo_t *)arg;
+
+  if (db == NULL)
+    return;
+
+  if (db->curl != NULL)
+    curl_easy_cleanup(db->curl);
+  db->curl = NULL;
+
+  cjo_destroy_bean_configs(db->bean_configs);
+  db->bean_configs = NULL;
+  sfree(db->instance);
+  sfree(db->host);
+
+  sfree(db->sock);
+
+  sfree(db->url);
+  sfree(db->user);
+  sfree(db->pass);
+  sfree(db->credentials);
+  sfree(db->cacert);
+  sfree(db->post_body);
+  sfree(db->attributepool);
+
+  curl_slist_free_all(db->headers);
+  /// todo: freeme     cjo_attribute_values_t *attributepool;
+
+  sfree(db);
+} /* }}} void cjo_free */
+
+static int cjo_config_add_url(oconfig_item_t *ci) /* {{{ */
+{
+  cjo_t *db;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    ERROR("curl_jolokia plugin: The `URL' block "
+          "needs exactly one string argument.");
+    return -1;
+  }
+
+  db = (cjo_t *)malloc(sizeof(*db));
+  if (db == NULL) {
+    ERROR("curl_jolokia plugin: malloc failed.");
+    return -1;
+  }
+  memset(db, 0, sizeof(*db));
+
+  if (strcasecmp("URL", ci->key) == 0)
+    status = cf_util_get_string(ci, &db->url);
+  else if (strcasecmp("Sock", ci->key) == 0)
+    status = cf_util_get_string(ci, &db->sock);
+  else {
+    ERROR("curl_jolokia plugin: cjo_config: "
+          "Invalid key: %s",
+          ci->key);
+    return -1;
+  }
+  if (status != 0) {
+    sfree(db);
+    return status;
+  }
+
+  /* Fill the `cjo_t' structure.. */
+  for (i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Instance", child->key) == 0)
+      status = cf_util_get_string(child, &db->instance);
+    else if (strcasecmp("Host", child->key) == 0)
+      status = cf_util_get_string(child, &db->host);
+    else if (strcasecmp("MaxReadAttributes", child->key) == 0)
+      status = cf_util_get_int(child, &db->max_attribute_count);
+    else if (db->url && strcasecmp("User", child->key) == 0)
+      status = cf_util_get_string(child, &db->user);
+    else if (db->url && strcasecmp("Password", child->key) == 0)
+      status = cf_util_get_string(child, &db->pass);
+    else if (db->url && strcasecmp("VerifyPeer", child->key) == 0)
+      status = cf_util_get_boolean(child, &db->verify_peer);
+    else if (db->url && strcasecmp("VerifyHost", child->key) == 0)
+      status = cf_util_get_boolean(child, &db->verify_host);
+    else if (db->url && strcasecmp("CACert", child->key) == 0)
+      status = cf_util_get_string(child, &db->cacert);
+    else if (db->url && strcasecmp("Header", child->key) == 0)
+      status = cjo_config_append_string("Header", &db->headers, child);
+    else if (db->url && strcasecmp("Post", child->key) == 0) {
+      status = cf_util_get_string(child, &db->post_body);
+      db->post_body_len = strlen(db->post_body);
+      db->expect_escapes = (strchr(db->post_body, '\\') != NULL) ||
+                           (strchr(db->post_body, '/') != NULL);
+    } else if (strcasecmp("BeanName", child->key) == 0)
+      status = cjo_config_add_bean(db, child);
+    else {
+      ERROR("curl_jolokia plugin: Option `%s' not allowed here.", child->key);
+
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0) {
+    if (status == 0 && db->url)
+      status = cjo_init_curl(db);
+  }
+
+  /* If all went well, register this database for reading */
+  if (status == 0) {
+    user_data_t ud;
+    char cb_name[DATA_MAX_NAME_LEN];
+
+    if (db->instance == NULL)
+      db->instance = strdup("default");
+
+    DEBUG("curl_jolokia plugin: Registering new read callback: %s",
+          db->instance);
+
+    memset(&ud, 0, sizeof(ud));
+    ud.data = (void *)db;
+    ud.free_func = cjo_free;
+
+    snprintf(cb_name, sizeof(cb_name), "curl_jolokia-%s-%s", db->instance,
+             db->url ? db->url : db->sock);
+
+    db->attributepool =
+        malloc(sizeof(*db->attributepool) * (db->max_attribute_count + 1));
+
+    plugin_register_complex_read(/* group = */ NULL, cb_name, cjo_read,
+                                 /* interval = */ db->interval, &ud);
+  } else {
+    ERROR("curl_jolokia plugin: Failed to load URL");
+
+    cjo_free(db);
+    return -1;
+  }
+
+  return 0;
+}
+/* }}} int cjo_config_add_database */
+
+static int cjo_config(oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Sock", child->key) == 0 ||
+        strcasecmp("URL", child->key) == 0) {
+      status = cjo_config_add_url(child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    } else {
+      WARNING("curl_jolokia plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0)) {
+    ERROR("curl_jolokia plugin: All statements failed.");
+    return -1;
+  }
+
+  return 0;
+} /* }}} int cjo_config */
+
+/* }}} End of configuration handling functions */
+
+void module_register(void) {
+  plugin_register_complex_config("curl_jolokia", cjo_config);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */