curl_json plugin: support getting json from a Unix socket as well as a URL
[collectd.git] / src / curl_json.c
index 19efc5d..de66862 100644 (file)
 #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>
@@ -60,6 +65,8 @@ struct cj_s /* {{{ */
   char *instance;
   char *host;
 
+  char *sock;
+
   char *url;
   char *user;
   char *pass;
@@ -67,6 +74,8 @@ struct cj_s /* {{{ */
   _Bool verify_peer;
   _Bool verify_host;
   char *cacert;
+  struct curl_slist *headers;
+  char *post_body;
 
   CURL *curl;
   char curl_errbuf[CURL_ERROR_SIZE];
@@ -92,7 +101,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, /* {{{ */
@@ -258,7 +266,7 @@ 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);
@@ -367,6 +375,8 @@ static void cj_free (void *arg) /* {{{ */
   sfree (db->pass);
   sfree (db->credentials);
   sfree (db->cacert);
+  sfree (db->post_body);
+  curl_slist_free_all (db->headers);
 
   sfree (db);
 } /* }}} void cj_free */
@@ -378,6 +388,22 @@ static c_avl_tree_t *cj_avl_create(void)
   return c_avl_create ((int (*) (const void *, const void *)) strcmp);
 }
 
+static int cj_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_json 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 cj_config_append_string */
+
 static int cj_config_add_key (cj_t *db, /* {{{ */
                                    oconfig_item_t *ci)
 {
@@ -547,6 +573,10 @@ static int cj_init_curl (cj_t *db) /* {{{ */
                     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);
 
   return (0);
 } /* }}} int cj_init_curl */
@@ -574,20 +604,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++)
@@ -598,16 +628,20 @@ 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 (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 (db->url && strcasecmp ("Header", child->key) == 0)
+      status = cj_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);
     else if (strcasecmp ("Key", child->key) == 0)
       status = cj_config_add_key (db, child);
     else
@@ -625,10 +659,10 @@ 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);
+               "within `%s' block `%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);
   }
 
@@ -649,7 +683,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);
@@ -678,7 +712,7 @@ 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)
@@ -738,47 +772,90 @@ 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) /* {{{ */
 {
-  int status;
-  long rc;
-  char *url;
-  yajl_handle yprev = db->yajl;
+  struct sockaddr_un sa_unix = {};
+  sa_unix.sun_family = AF_UNIX;
+  sstrncpy (sa_unix.sun_path, db->sock, sizeof (sa_unix.sun_path));
 
-  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)
+  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: yajl_alloc failed.");
-    db->yajl = yprev;
+    ERROR ("curl_json plugin: connect(%s) failed: %s",
+           (db->sock != NULL) ? db->sock : "<null>", strerror(errno));
+    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>", strerror(errno));
+        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 = NULL;
-  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
+  curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
 
-  status = curl_easy_perform (curl);
+  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 != NULL) ? url : "<null>");
-    yajl_free (db->yajl);
-    db->yajl = yprev;
     return (-1);
   }
 
-  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
+  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,
+#if HAVE_YAJL_V2
+      /* alloc funcs = */ NULL,
+#else
+      /* alloc funcs = */ NULL, NULL,
+#endif
+      /* context = */ (void *)db);
+  if (db->yajl == NULL)
+  {
+    ERROR ("curl_json plugin: yajl_alloc failed.");
+    db->yajl = yprev;
+    return (-1);
+  }
+
+  if (db->url ? cj_curl_perform (db) < 0 : cj_sock_perform (db) < 0)
+  {
     yajl_free (db->yajl);
     db->yajl = yprev;
     return (-1);
@@ -806,7 +883,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) /* {{{ */
 {
@@ -825,7 +902,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)