collection.cgi: Use -l 0 for df_complex graphs.
[collectd.git] / src / curl_json.c
index ca604fc..a9db925 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/curl_json.c
  * Copyright (C) 2009       Doug MacEachern
- * Copyright (C) 2006-2011  Florian octo Forster
+ * 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
 #include "utils_avltree.h"
 #include "utils_complain.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>
@@ -48,10 +53,10 @@ struct cj_key_s;
 typedef struct cj_key_s cj_key_t;
 struct cj_key_s /* {{{ */
 {
+  unsigned long magic;
   char *path;
   char *type;
   char *instance;
-  unsigned long magic;
 };
 /* }}} */
 
@@ -60,10 +65,13 @@ struct cj_s /* {{{ */
   char *instance;
   char *host;
 
+  char *sock;
+
   char *url;
   char *user;
   char *pass;
   char *credentials;
+  _Bool digest;
   _Bool verify_peer;
   _Bool verify_host;
   char *cacert;
@@ -96,7 +104,6 @@ typedef unsigned int yajl_len_t;
 #endif
 
 static int cj_read (user_data_t *ud);
-static int cj_curl_perform (cj_t *db, CURL *curl);
 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value);
 
 static size_t cj_curl_callback (void *buf, /* {{{ */
@@ -172,19 +179,22 @@ static int cj_get_type (cj_key_t *key)
 static int cj_cb_map_key (void *ctx, const unsigned char *val,
     yajl_len_t len);
 
-static void cj_cb_inc_array_index (void * ctx, _Bool ignore)
+static void cj_cb_inc_array_index (void *ctx, _Bool update_key)
 {
   cj_t *db = (cj_t *)ctx;
 
-  if (db->state[db->depth].in_array) {
-    if (ignore)
-      db->state[db->depth].index++;
-    else {
-      char name[DATA_MAX_NAME_LEN];
-      cj_cb_map_key (ctx, (unsigned char *)name,
-                     ssnprintf (name, sizeof (name),
-                                "%d", db->state[db->depth].index++));
-    }
+  if (!db->state[db->depth].in_array)
+    return;
+
+  db->state[db->depth].index++;
+
+  if (update_key)
+  {
+    char name[DATA_MAX_NAME_LEN];
+
+    ssnprintf (name, sizeof (name), "%d", db->state[db->depth].index - 1);
+
+    cj_cb_map_key (ctx, (unsigned char *)name, (yajl_len_t) strlen (name));
   }
 }
 
@@ -194,18 +204,16 @@ static void cj_cb_inc_array_index (void * ctx, _Bool ignore)
 
 static int cj_cb_boolean (void * ctx, int boolVal)
 {
-  cj_cb_inc_array_index (ctx, 1);
+  cj_cb_inc_array_index (ctx, /* update_key = */ 0);
   return (CJ_CB_CONTINUE);
 }
 
 static int cj_cb_null (void * ctx)
 {
-  cj_cb_inc_array_index (ctx, 1);
+  cj_cb_inc_array_index (ctx, /* update_key = */ 0);
   return (CJ_CB_CONTINUE);
 }
 
-/* "number" may not be null terminated, so copy it into a buffer before
- * parsing. */
 static int cj_cb_number (void *ctx,
     const char *number, yajl_len_t number_len)
 {
@@ -217,18 +225,19 @@ static int cj_cb_number (void *ctx,
   int type;
   int status;
 
+  /* Create a null-terminated version of the string. */
+  memcpy (buffer, number, number_len);
+  buffer[sizeof (buffer) - 1] = 0;
+
   if ((key == NULL) || !CJ_IS_KEY (key)) {
     if (key != NULL)
-      NOTICE ("curl_json plugin: Found \"%.*s\", but the configuration expects"
-              " a map.", (int)number_len > number_len ? 0 : (int)number_len,
-              number);
-    cj_cb_inc_array_index (ctx, 1);
+      NOTICE ("curl_json plugin: Found \"%s\", but the configuration expects"
+              " a map.", buffer);
+    cj_cb_inc_array_index (ctx, /* update_key = */ 0);
     return (CJ_CB_CONTINUE);
-  } else
-    cj_cb_inc_array_index (ctx, 0);
-
-  memcpy (buffer, number, number_len);
-  buffer[sizeof (buffer) - 1] = 0;
+  } else {
+    cj_cb_inc_array_index (ctx, /* update_key = */ 1);
+  }
 
   type = cj_get_type (key);
   status = parse_value (buffer, &vt, type);
@@ -242,8 +251,11 @@ static int cj_cb_number (void *ctx,
   return (CJ_CB_CONTINUE);
 } /* int cj_cb_number */
 
-static int cj_cb_map_key (void *ctx, const unsigned char *val,
-    yajl_len_t len)
+/* Queries the key-tree of the parent context for "in_name" and, if found,
+ * updates the "key" field of the current context. Otherwise, "key" is set to
+ * NULL. */
+static int cj_cb_map_key (void *ctx,
+    unsigned char const *in_name, yajl_len_t in_name_len)
 {
   cj_t *db = (cj_t *)ctx;
   c_avl_tree_t *tree;
@@ -252,12 +264,16 @@ static int cj_cb_map_key (void *ctx, const unsigned char *val,
 
   if (tree != NULL)
   {
-    cj_key_t *value;
+    cj_key_t *value = NULL;
     char *name;
+    size_t name_len;
 
+    /* Create a null-terminated version of the name. */
     name = db->state[db->depth].name;
-    len = COUCH_MIN(len, sizeof (db->state[db->depth].name)-1);
-    sstrncpy (name, (char *)val, len+1);
+    name_len = COUCH_MIN ((size_t) in_name_len,
+        sizeof (db->state[db->depth].name) - 1);
+    memcpy (name, in_name, name_len);
+    name[name_len] = 0;
 
     if (c_avl_get (tree, name, (void *) &value) == 0)
       db->state[db->depth].key = value;
@@ -282,7 +298,8 @@ static int cj_cb_start (void *ctx)
   cj_t *db = (cj_t *)ctx;
   if (++db->depth >= YAJL_MAX_DEPTH)
   {
-    ERROR ("curl_json plugin: %s depth exceeds max, aborting.", db->url);
+    ERROR ("curl_json plugin: %s depth exceeds max, aborting.",
+           db->url ? db->url : db->sock);
     return (CJ_CB_ABORT);
   }
   return (CJ_CB_CONTINUE);
@@ -298,7 +315,7 @@ static int cj_cb_end (void *ctx)
 
 static int cj_cb_start_map (void *ctx)
 {
-  cj_cb_inc_array_index (ctx, 0);
+  cj_cb_inc_array_index (ctx, /* update_key = */ 1);
   return cj_cb_start (ctx);
 }
 
@@ -310,7 +327,7 @@ static int cj_cb_end_map (void *ctx)
 static int cj_cb_start_array (void * ctx)
 {
   cj_t *db = (cj_t *)ctx;
-  cj_cb_inc_array_index (ctx, 0);
+  cj_cb_inc_array_index (ctx, /* update_key = */ 1);
   if (db->depth+1 < YAJL_MAX_DEPTH) {
     db->state[db->depth+1].in_array = 1;
     db->state[db->depth+1].index = 0;
@@ -395,6 +412,8 @@ static void cj_free (void *arg) /* {{{ */
   sfree (db->instance);
   sfree (db->host);
 
+  sfree (db->sock);
+
   sfree (db->url);
   sfree (db->user);
   sfree (db->pass);
@@ -531,6 +550,7 @@ static int cj_config_add_key (cj_t *db, /* {{{ */
         len = ptr-name;
         if (len == 0)
           break;
+        len = COUCH_MIN(len, sizeof (ent)-1);
         sstrncpy (ent, name, len+1);
 
         if (c_avl_get (tree, ent, (void *) &value) != 0)
@@ -568,8 +588,7 @@ static int cj_init_curl (cj_t *db) /* {{{ */
   curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1L);
   curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cj_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_USERAGENT, COLLECTD_USERAGENT);
   curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
   curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
 
@@ -591,6 +610,13 @@ static int cj_init_curl (cj_t *db) /* {{{ */
     ssnprintf (db->credentials, credentials_size, "%s:%s",
                db->user, (db->pass == NULL) ? "" : db->pass);
     curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
+    
+    if (db->digest)
+    {
+      curl_easy_setopt (db->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+      curl_easy_setopt (db->curl, CURLOPT_USERNAME, db->user);
+      curl_easy_setopt (db->curl, CURLOPT_PASSWORD, db->pass);
+    }
   }
 
   curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, (long) db->verify_peer);
@@ -629,20 +655,20 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
   memset (db, 0, sizeof (*db));
 
   if (strcasecmp ("URL", ci->key) == 0)
-  {
     status = cf_util_get_string (ci, &db->url);
-    if (status != 0)
-    {
-      sfree (db);
-      return (status);
-    }
-  }
+  else if (strcasecmp ("Sock", ci->key) == 0)
+    status = cf_util_get_string (ci, &db->sock);
   else
   {
     ERROR ("curl_json plugin: cj_config: "
            "Invalid key: %s", ci->key);
     return (-1);
   }
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
 
   /* Fill the `cj_t' structure.. */
   for (i = 0; i < ci->children_num; i++)
@@ -653,19 +679,21 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
       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 ("User", child->key) == 0)
+    else if (db->url && strcasecmp ("User", child->key) == 0)
       status = cf_util_get_string (child, &db->user);
-    else if (strcasecmp ("Password", child->key) == 0)
+    else if (db->url && strcasecmp ("Password", child->key) == 0)
       status = cf_util_get_string (child, &db->pass);
-    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+    else if (strcasecmp ("Digest", child->key) == 0)
+      status = cf_util_get_boolean (child, &db->digest);
+    else if (db->url && strcasecmp ("VerifyPeer", child->key) == 0)
       status = cf_util_get_boolean (child, &db->verify_peer);
-    else if (strcasecmp ("VerifyHost", child->key) == 0)
+    else if (db->url && strcasecmp ("VerifyHost", child->key) == 0)
       status = cf_util_get_boolean (child, &db->verify_host);
-    else if (strcasecmp ("CACert", child->key) == 0)
+    else if (db->url && strcasecmp ("CACert", child->key) == 0)
       status = cf_util_get_string (child, &db->cacert);
-    else if (strcasecmp ("Header", child->key) == 0)
+    else if (db->url && strcasecmp ("Header", child->key) == 0)
       status = cj_config_append_string ("Header", &db->headers, child);
-    else if (strcasecmp ("Post", child->key) == 0)
+    else if (db->url && strcasecmp ("Post", child->key) == 0)
       status = cf_util_get_string (child, &db->post_body);
     else if (strcasecmp ("Key", child->key) == 0)
       status = cj_config_add_key (db, child);
@@ -683,11 +711,11 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
   {
     if (db->tree == NULL)
     {
-      WARNING ("curl_json plugin: No (valid) `Key' block "
-               "within `URL' block `%s'.", db->url);
+      WARNING ("curl_json plugin: No (valid) `Key' block within `%s' \"`%s'\".",
+               db->url ? "URL" : "Sock", db->url ? db->url : db->sock);
       status = -1;
     }
-    if (status == 0)
+    if (status == 0 && db->url)
       status = cj_init_curl (db);
   }
 
@@ -708,7 +736,7 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
     ud.free_func = cj_free;
 
     ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
-               db->instance, db->url);
+               db->instance, db->url ? db->url : db->sock);
 
     plugin_register_complex_read (/* group = */ NULL, cb_name, cj_read,
                                   /* interval = */ NULL, &ud);
@@ -737,7 +765,8 @@ static int cj_config (oconfig_item_t *ci) /* {{{ */
   {
     oconfig_item_t *child = ci->children + i;
 
-    if (strcasecmp ("URL", child->key) == 0)
+    if (strcasecmp ("Sock", child->key) == 0
+        || strcasecmp ("URL", child->key) == 0)
     {
       status = cj_config_add_url (child);
       if (status == 0)
@@ -797,11 +826,75 @@ static void cj_submit (cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
   plugin_dispatch_values (&vl);
 } /* }}} int cj_submit */
 
-static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
+static int cj_sock_perform (cj_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_json 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_json plugin: read(%s) failed: %s",
+               (db->sock != NULL) ? db->sock : "<null>",
+               sstrerror(errno, errbuf, sizeof (errbuf)));
+        close (fd);
+        return (-1);
+    }
+    if (!cj_curl_callback (buffer, red, 1, db))
+        break;
+  } while (red > 0);
+  close (fd);
+  return (0);
+} /* }}} int cj_sock_perform */
+
+
+static int cj_curl_perform(cj_t *db) /* {{{ */
 {
   int status;
   long rc;
   char *url;
+  url = db->url;
+
+  status = curl_easy_perform (db->curl);
+  if (status != CURLE_OK)
+  {
+    ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
+           status, db->curl_errbuf, url);
+    return (-1);
+  }
+
+  curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
+  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_json plugin: curl_easy_perform failed with "
+        "response code %ld (%s)", rc, url);
+    return (-1);
+  }
+  return (0);
+} /* }}} int cj_curl_perform */
+
+static int cj_perform (cj_t *db) /* {{{ */
+{
+  int status;
   yajl_handle yprev = db->yajl;
 
   db->yajl = yajl_alloc (&ycallbacks,
@@ -818,26 +911,12 @@ static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
     return (-1);
   }
 
-  url = NULL;
-  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
-
-  status = curl_easy_perform (curl);
-  if (status != CURLE_OK)
-  {
-    ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
-           status, db->curl_errbuf, (url != NULL) ? url : "<null>");
-    yajl_free (db->yajl);
-    db->yajl = yprev;
-    return (-1);
-  }
-
-  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
-
-  /* The response code is zero if a non-HTTP transport was used. */
-  if ((rc != 0) && (rc != 200))
+  if (db->url)
+    status = cj_curl_perform (db);
+  else
+    status = cj_sock_perform (db);
+  if (status < 0)
   {
-    ERROR ("curl_json plugin: curl_easy_perform failed with "
-        "response code %ld (%s)", rc, url);
     yajl_free (db->yajl);
     db->yajl = yprev;
     return (-1);
@@ -865,7 +944,7 @@ static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
   yajl_free (db->yajl);
   db->yajl = yprev;
   return (0);
-} /* }}} int cj_curl_perform */
+} /* }}} int cj_perform */
 
 static int cj_read (user_data_t *ud) /* {{{ */
 {
@@ -884,7 +963,7 @@ static int cj_read (user_data_t *ud) /* {{{ */
   db->state[db->depth].tree = db->tree;
   db->key = NULL;
 
-  return cj_curl_perform (db, db->curl);
+  return cj_perform (db);
 } /* }}} int cj_read */
 
 void module_register (void)