Merge pull request #3329 from efuss/fix-3311
[collectd.git] / src / curl_json.c
1 /**
2  * collectd - src/curl_json.c
3  * Copyright (C) 2009       Doug MacEachern
4  * Copyright (C) 2006-2013  Florian octo Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Doug MacEachern <dougm at hyperic.com>
21  *   Florian octo Forster <octo at collectd.org>
22  **/
23
24 #include "collectd.h"
25
26 #include "plugin.h"
27 #include "utils/avltree/avltree.h"
28 #include "utils/common/common.h"
29 #include "utils/curl_stats/curl_stats.h"
30 #include "utils_complain.h"
31
32 #include <sys/types.h>
33 #include <sys/un.h>
34
35 #include <curl/curl.h>
36
37 #include <yajl/yajl_parse.h>
38 #if HAVE_YAJL_YAJL_VERSION_H
39 #include <yajl/yajl_version.h>
40 #endif
41
42 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
43 #define HAVE_YAJL_V2 1
44 #endif
45
46 #define CJ_DEFAULT_HOST "localhost"
47 #define CJ_ANY "*"
48 #define COUCH_MIN(x, y) ((x) < (y) ? (x) : (y))
49
50 struct cj_key_s;
51 typedef struct cj_key_s cj_key_t;
52 struct cj_key_s /* {{{ */
53 {
54   char *path;
55   char *type;
56   char *instance;
57 };
58 /* }}} */
59
60 /* cj_tree_entry_t is a union of either a metric configuration ("key") or a tree
61  * mapping array indexes / map keys to a descendant cj_tree_entry_t*. */
62 typedef struct {
63   enum { KEY, TREE } type;
64   union {
65     c_avl_tree_t *tree;
66     cj_key_t *key;
67   };
68 } cj_tree_entry_t;
69
70 /* cj_state_t is a stack providing the configuration relevant for the context
71  * that is currently being parsed. If entry->type == KEY, the parser should
72  * expect a metric (a numeric value). If entry->type == TREE, the parser should
73  * expect an array of map to descent into. If entry == NULL, no configuration
74  * exists for this part of the JSON structure. */
75 typedef struct {
76   cj_tree_entry_t *entry;
77   bool in_array;
78   int index;
79   char name[DATA_MAX_NAME_LEN];
80 } cj_state_t;
81
82 struct cj_s /* {{{ */
83 {
84   char *instance;
85   char *plugin_name;
86   char *host;
87
88   char *sock;
89
90   char *url;
91   int address_family;
92   char *user;
93   char *pass;
94   char *credentials;
95   bool digest;
96   bool verify_peer;
97   bool verify_host;
98   char *cacert;
99   struct curl_slist *headers;
100   char *post_body;
101   int timeout;
102   curl_stats_t *stats;
103
104   CURL *curl;
105   char curl_errbuf[CURL_ERROR_SIZE];
106
107   yajl_handle yajl;
108   c_avl_tree_t *tree;
109   int depth;
110   cj_state_t state[YAJL_MAX_DEPTH];
111 };
112 typedef struct cj_s cj_t; /* }}} */
113
114 #if HAVE_YAJL_V2
115 typedef size_t yajl_len_t;
116 #else
117 typedef unsigned int yajl_len_t;
118 #endif
119
120 static int cj_read(user_data_t *ud);
121 static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value);
122
123 /* cj_submit is a function pointer to cj_submit_impl, allowing the unit-test to
124  * overwrite which function is called. */
125 static void (*cj_submit)(cj_t *, cj_key_t *, value_t *) = cj_submit_impl;
126
127 static size_t cj_curl_callback(void *buf, /* {{{ */
128                                size_t size, size_t nmemb, void *user_data) {
129   cj_t *db;
130   size_t len;
131   yajl_status status;
132
133   len = size * nmemb;
134
135   if (len == 0)
136     return len;
137
138   db = user_data;
139   if (db == NULL)
140     return 0;
141
142   status = yajl_parse(db->yajl, (unsigned char *)buf, len);
143   if (status == yajl_status_ok)
144     return len;
145 #if !HAVE_YAJL_V2
146   else if (status == yajl_status_insufficient_data)
147     return len;
148 #endif
149
150   unsigned char *msg =
151       yajl_get_error(db->yajl, /* verbose = */ 1,
152                      /* jsonText = */ (unsigned char *)buf, (unsigned int)len);
153   ERROR("curl_json plugin: yajl_parse failed: %s", msg);
154   yajl_free_error(db->yajl, msg);
155   return 0; /* abort write callback */
156 } /* }}} size_t cj_curl_callback */
157
158 static int cj_get_type(cj_key_t *key) {
159   if (key == NULL)
160     return -EINVAL;
161
162   const data_set_t *ds = plugin_get_ds(key->type);
163   if (ds == NULL) {
164     static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
165
166     assert(key->type != NULL);
167     if (strcmp(type, key->type) != 0) {
168       ERROR("curl_json plugin: Unable to look up DS type \"%s\".", key->type);
169       sstrncpy(type, key->type, sizeof(type));
170     }
171
172     return -1;
173   } else if (ds->ds_num > 1) {
174     static c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
175
176     c_complain_once(
177         LOG_WARNING, &complaint,
178         "curl_json plugin: The type \"%s\" has more than one data source. "
179         "This is currently not supported. I will return the type of the "
180         "first data source, but this will likely lead to problems later on.",
181         key->type);
182   }
183
184   return ds->ds[0].type;
185 }
186
187 /* cj_load_key loads the configuration for "key" from the parent context and
188  * sets either .key or .tree in the current context. */
189 static int cj_load_key(cj_t *db, char const *key) {
190   if (db == NULL || key == NULL || db->depth <= 0)
191     return EINVAL;
192
193   sstrncpy(db->state[db->depth].name, key, sizeof(db->state[db->depth].name));
194
195   if (db->state[db->depth - 1].entry == NULL ||
196       db->state[db->depth - 1].entry->type != TREE) {
197     return 0;
198   }
199
200   c_avl_tree_t *tree = db->state[db->depth - 1].entry->tree;
201   cj_tree_entry_t *e = NULL;
202
203   if (c_avl_get(tree, key, (void *)&e) == 0) {
204     db->state[db->depth].entry = e;
205   } else if (c_avl_get(tree, CJ_ANY, (void *)&e) == 0) {
206     db->state[db->depth].entry = e;
207   } else {
208     db->state[db->depth].entry = NULL;
209   }
210
211   return 0;
212 }
213
214 static void cj_advance_array(cj_t *db) {
215   if (!db->state[db->depth].in_array)
216     return;
217
218   db->state[db->depth].index++;
219
220   char name[DATA_MAX_NAME_LEN];
221   snprintf(name, sizeof(name), "%d", db->state[db->depth].index);
222   cj_load_key(db, name);
223 }
224
225 /* yajl callbacks */
226 #define CJ_CB_ABORT 0
227 #define CJ_CB_CONTINUE 1
228
229 static int cj_cb_null(void *ctx) {
230   cj_advance_array(ctx);
231   return CJ_CB_CONTINUE;
232 }
233
234 static int cj_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
235   cj_t *db = (cj_t *)ctx;
236
237   /* Create a null-terminated version of the string. */
238   char buffer[number_len + 1];
239   memcpy(buffer, number, number_len);
240   buffer[sizeof(buffer) - 1] = '\0';
241
242   if (db->state[db->depth].entry == NULL ||
243       db->state[db->depth].entry->type != KEY) {
244     if (db->state[db->depth].entry != NULL) {
245       NOTICE("curl_json plugin: Found \"%s\", but the configuration expects a "
246              "map.",
247              buffer);
248     }
249     cj_advance_array(ctx);
250     return CJ_CB_CONTINUE;
251   }
252
253   cj_key_t *key = db->state[db->depth].entry->key;
254
255   int type = cj_get_type(key);
256   value_t vt;
257   int status = parse_value(buffer, &vt, type);
258   if (status != 0) {
259     cj_advance_array(ctx);
260     return CJ_CB_CONTINUE;
261   }
262
263   cj_submit(db, key, &vt);
264   cj_advance_array(ctx);
265   return CJ_CB_CONTINUE;
266 } /* int cj_cb_number */
267
268 /* Queries the key-tree of the parent context for "in_name" and, if found,
269  * updates the "key" field of the current context. Otherwise, "key" is set to
270  * NULL. */
271 static int cj_cb_map_key(void *ctx, unsigned char const *in_name,
272                          yajl_len_t in_name_len) {
273   char name[in_name_len + 1];
274
275   memmove(name, in_name, in_name_len);
276   name[sizeof(name) - 1] = '\0';
277
278   if (cj_load_key(ctx, name) != 0)
279     return CJ_CB_ABORT;
280
281   return CJ_CB_CONTINUE;
282 }
283
284 static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
285   /* Handle the string as if it was a number. */
286   return cj_cb_number(ctx, (const char *)val, len);
287 } /* int cj_cb_string */
288
289 static int cj_cb_boolean(void *ctx, int boolVal) {
290   if (boolVal)
291     return cj_cb_number(ctx, "1", 1);
292   else
293     return cj_cb_number(ctx, "0", 1);
294 } /* int cj_cb_boolean */
295
296 static int cj_cb_end(void *ctx) {
297   cj_t *db = (cj_t *)ctx;
298   memset(&db->state[db->depth], 0, sizeof(db->state[db->depth]));
299   db->depth--;
300   cj_advance_array(ctx);
301   return CJ_CB_CONTINUE;
302 }
303
304 static int cj_cb_start_map(void *ctx) {
305   cj_t *db = (cj_t *)ctx;
306
307   if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
308     ERROR("curl_json plugin: %s depth exceeds max, aborting.",
309           db->url ? db->url : db->sock);
310     return CJ_CB_ABORT;
311   }
312   db->depth++;
313   return CJ_CB_CONTINUE;
314 }
315
316 static int cj_cb_end_map(void *ctx) { return cj_cb_end(ctx); }
317
318 static int cj_cb_start_array(void *ctx) {
319   cj_t *db = (cj_t *)ctx;
320
321   if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
322     ERROR("curl_json plugin: %s depth exceeds max, aborting.",
323           db->url ? db->url : db->sock);
324     return CJ_CB_ABORT;
325   }
326   db->depth++;
327   db->state[db->depth].in_array = true;
328   db->state[db->depth].index = 0;
329
330   cj_load_key(db, "0");
331
332   return CJ_CB_CONTINUE;
333 }
334
335 static int cj_cb_end_array(void *ctx) {
336   cj_t *db = (cj_t *)ctx;
337   db->state[db->depth].in_array = false;
338   return cj_cb_end(ctx);
339 }
340
341 static yajl_callbacks ycallbacks = {
342     cj_cb_null,    /* null */
343     cj_cb_boolean, /* boolean */
344     NULL,          /* integer */
345     NULL,          /* double */
346     cj_cb_number,  cj_cb_string,      cj_cb_start_map, cj_cb_map_key,
347     cj_cb_end_map, cj_cb_start_array, cj_cb_end_array};
348
349 /* end yajl callbacks */
350
351 static void cj_key_free(cj_key_t *key) /* {{{ */
352 {
353   if (key == NULL)
354     return;
355
356   sfree(key->path);
357   sfree(key->type);
358   sfree(key->instance);
359
360   sfree(key);
361 } /* }}} void cj_key_free */
362
363 static void cj_tree_free(c_avl_tree_t *tree) /* {{{ */
364 {
365   char *name;
366   cj_tree_entry_t *e;
367
368   while (c_avl_pick(tree, (void *)&name, (void *)&e) == 0) {
369     sfree(name);
370
371     if (e->type == KEY)
372       cj_key_free(e->key);
373     else
374       cj_tree_free(e->tree);
375     sfree(e);
376   }
377
378   c_avl_destroy(tree);
379 } /* }}} void cj_tree_free */
380
381 static void cj_free(void *arg) /* {{{ */
382 {
383   cj_t *db;
384
385   DEBUG("curl_json plugin: cj_free (arg = %p);", arg);
386
387   db = (cj_t *)arg;
388
389   if (db == NULL)
390     return;
391
392   if (db->curl != NULL)
393     curl_easy_cleanup(db->curl);
394   db->curl = NULL;
395
396   if (db->tree != NULL)
397     cj_tree_free(db->tree);
398   db->tree = NULL;
399
400   sfree(db->instance);
401   sfree(db->plugin_name);
402   sfree(db->host);
403
404   sfree(db->sock);
405
406   sfree(db->url);
407   sfree(db->user);
408   sfree(db->pass);
409   sfree(db->credentials);
410   sfree(db->cacert);
411   sfree(db->post_body);
412   curl_slist_free_all(db->headers);
413   curl_stats_destroy(db->stats);
414
415   sfree(db);
416 } /* }}} void cj_free */
417
418 /* Configuration handling functions {{{ */
419
420 static c_avl_tree_t *cj_avl_create(void) {
421   return c_avl_create((int (*)(const void *, const void *))strcmp);
422 }
423
424 static int cj_config_append_string(const char *name,
425                                    struct curl_slist **dest, /* {{{ */
426                                    oconfig_item_t *ci) {
427   struct curl_slist *temp = NULL;
428   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
429     WARNING("curl_json plugin: `%s' needs exactly one string argument.", name);
430     return -1;
431   }
432
433   temp = curl_slist_append(*dest, ci->values[0].value.string);
434   if (temp == NULL)
435     return -1;
436
437   *dest = temp;
438
439   return 0;
440 } /* }}} int cj_config_append_string */
441
442 /* cj_append_key adds key to the configuration stored in db.
443  *
444  * For example:
445  * "httpd/requests/count",
446  * "httpd/requests/current" ->
447  * { "httpd": { "requests": { "count": $key, "current": $key } } }
448  */
449 static int cj_append_key(cj_t *db, cj_key_t *key) { /* {{{ */
450   if (db->tree == NULL)
451     db->tree = cj_avl_create();
452
453   c_avl_tree_t *tree = db->tree;
454
455   char const *start = key->path;
456   if (*start == '/')
457     ++start;
458
459   char const *end;
460   while ((end = strchr(start, '/')) != NULL) {
461     char name[PATH_MAX];
462
463     size_t len = end - start;
464     if (len == 0)
465       break;
466
467     len = COUCH_MIN(len, sizeof(name) - 1);
468     sstrncpy(name, start, len + 1);
469
470     cj_tree_entry_t *e;
471     if (c_avl_get(tree, name, (void *)&e) != 0) {
472       e = calloc(1, sizeof(*e));
473       if (e == NULL)
474         return ENOMEM;
475       e->type = TREE;
476       e->tree = cj_avl_create();
477
478       c_avl_insert(tree, strdup(name), e);
479     }
480
481     if (e->type != TREE)
482       return EINVAL;
483
484     tree = e->tree;
485     start = end + 1;
486   }
487
488   if (strlen(start) == 0) {
489     ERROR("curl_json plugin: invalid key: %s", key->path);
490     return -1;
491   }
492
493   cj_tree_entry_t *e = calloc(1, sizeof(*e));
494   if (e == NULL)
495     return ENOMEM;
496   e->type = KEY;
497   e->key = key;
498
499   c_avl_insert(tree, strdup(start), e);
500   return 0;
501 } /* }}} int cj_append_key */
502
503 static int cj_config_add_key(cj_t *db, /* {{{ */
504                              oconfig_item_t *ci) {
505   cj_key_t *key;
506   int status;
507
508   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
509     WARNING("curl_json plugin: The `Key' block "
510             "needs exactly one string argument.");
511     return -1;
512   }
513
514   key = calloc(1, sizeof(*key));
515   if (key == NULL) {
516     ERROR("curl_json plugin: calloc failed.");
517     return -1;
518   }
519
520   if (strcasecmp("Key", ci->key) == 0) {
521     status = cf_util_get_string(ci, &key->path);
522     if (status != 0) {
523       sfree(key);
524       return status;
525     }
526   } else {
527     ERROR("curl_json plugin: cj_config: "
528           "Invalid key: %s",
529           ci->key);
530     cj_key_free(key);
531     return -1;
532   }
533
534   status = 0;
535   for (int i = 0; i < ci->children_num; i++) {
536     oconfig_item_t *child = ci->children + i;
537
538     if (strcasecmp("Type", child->key) == 0)
539       status = cf_util_get_string(child, &key->type);
540     else if (strcasecmp("Instance", child->key) == 0)
541       status = cf_util_get_string(child, &key->instance);
542     else {
543       WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
544       status = -1;
545     }
546
547     if (status != 0)
548       break;
549   } /* for (i = 0; i < ci->children_num; i++) */
550
551   if (status != 0) {
552     cj_key_free(key);
553     return -1;
554   }
555
556   if (key->type == NULL) {
557     WARNING("curl_json plugin: `Type' missing in `Key' block.");
558     cj_key_free(key);
559     return -1;
560   }
561
562   status = cj_append_key(db, key);
563   if (status != 0) {
564     cj_key_free(key);
565     return -1;
566   }
567
568   return 0;
569 } /* }}} int cj_config_add_key */
570
571 static int cj_init_curl(cj_t *db) /* {{{ */
572 {
573   db->curl = curl_easy_init();
574   if (db->curl == NULL) {
575     ERROR("curl_json plugin: curl_easy_init failed.");
576     return -1;
577   }
578
579   curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
580   curl_easy_setopt(db->curl, CURLOPT_WRITEFUNCTION, cj_curl_callback);
581   curl_easy_setopt(db->curl, CURLOPT_WRITEDATA, db);
582   curl_easy_setopt(db->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
583   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
584   curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
585   curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
586   curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
587
588   if (db->user != NULL) {
589 #ifdef HAVE_CURLOPT_USERNAME
590     curl_easy_setopt(db->curl, CURLOPT_USERNAME, db->user);
591     curl_easy_setopt(db->curl, CURLOPT_PASSWORD,
592                      (db->pass == NULL) ? "" : db->pass);
593 #else
594     size_t credentials_size;
595
596     credentials_size = strlen(db->user) + 2;
597     if (db->pass != NULL)
598       credentials_size += strlen(db->pass);
599
600     db->credentials = malloc(credentials_size);
601     if (db->credentials == NULL) {
602       ERROR("curl_json plugin: malloc failed.");
603       return -1;
604     }
605
606     snprintf(db->credentials, credentials_size, "%s:%s", db->user,
607              (db->pass == NULL) ? "" : db->pass);
608     curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
609 #endif
610
611     if (db->digest)
612       curl_easy_setopt(db->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
613   }
614
615   curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYPEER, (long)db->verify_peer);
616   curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYHOST, db->verify_host ? 2L : 0L);
617   if (db->cacert != NULL)
618     curl_easy_setopt(db->curl, CURLOPT_CAINFO, db->cacert);
619   if (db->headers != NULL)
620     curl_easy_setopt(db->curl, CURLOPT_HTTPHEADER, db->headers);
621   if (db->post_body != NULL)
622     curl_easy_setopt(db->curl, CURLOPT_POSTFIELDS, db->post_body);
623
624 #ifdef HAVE_CURLOPT_TIMEOUT_MS
625   if (db->timeout >= 0)
626     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
627   else
628     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
629                      (long)CDTIME_T_TO_MS(plugin_get_interval()));
630 #endif
631
632   return 0;
633 } /* }}} int cj_init_curl */
634
635 static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
636 {
637   cj_t *db;
638   int status = 0;
639   cdtime_t interval = 0;
640
641   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
642     WARNING("curl_json plugin: The `URL' block "
643             "needs exactly one string argument.");
644     return -1;
645   }
646
647   db = calloc(1, sizeof(*db));
648   if (db == NULL) {
649     ERROR("curl_json plugin: calloc failed.");
650     return -1;
651   }
652
653   db->timeout = -1;
654   db->address_family = CURL_IPRESOLVE_WHATEVER;
655
656   if (strcasecmp("URL", ci->key) == 0)
657     status = cf_util_get_string(ci, &db->url);
658   else if (strcasecmp("Sock", ci->key) == 0)
659     status = cf_util_get_string(ci, &db->sock);
660   else {
661     ERROR("curl_json plugin: cj_config: "
662           "Invalid key: %s",
663           ci->key);
664     cj_free(db);
665     return -1;
666   }
667   if (status != 0) {
668     sfree(db);
669     return status;
670   }
671
672   /* Fill the `cj_t' structure.. */
673   for (int i = 0; i < ci->children_num; i++) {
674     oconfig_item_t *child = ci->children + i;
675
676     if (strcasecmp("Instance", child->key) == 0)
677       status = cf_util_get_string(child, &db->instance);
678     else if (strcasecmp("Plugin", child->key) == 0)
679       status = cf_util_get_string(child, &db->plugin_name);
680     else if (strcasecmp("Host", child->key) == 0)
681       status = cf_util_get_string(child, &db->host);
682     else if (db->url && strcasecmp("User", child->key) == 0)
683       status = cf_util_get_string(child, &db->user);
684     else if (db->url && strcasecmp("Password", child->key) == 0)
685       status = cf_util_get_string(child, &db->pass);
686     else if (strcasecmp("Digest", child->key) == 0)
687       status = cf_util_get_boolean(child, &db->digest);
688     else if (db->url && strcasecmp("VerifyPeer", child->key) == 0)
689       status = cf_util_get_boolean(child, &db->verify_peer);
690     else if (db->url && strcasecmp("VerifyHost", child->key) == 0)
691       status = cf_util_get_boolean(child, &db->verify_host);
692     else if (db->url && strcasecmp("CACert", child->key) == 0)
693       status = cf_util_get_string(child, &db->cacert);
694     else if (db->url && strcasecmp("Header", child->key) == 0)
695       status = cj_config_append_string("Header", &db->headers, child);
696     else if (db->url && strcasecmp("Post", child->key) == 0)
697       status = cf_util_get_string(child, &db->post_body);
698     else if (strcasecmp("Key", child->key) == 0)
699       status = cj_config_add_key(db, child);
700     else if (strcasecmp("Interval", child->key) == 0)
701       status = cf_util_get_cdtime(child, &interval);
702     else if (strcasecmp("Timeout", child->key) == 0)
703       status = cf_util_get_int(child, &db->timeout);
704     else if (strcasecmp("Statistics", child->key) == 0) {
705       db->stats = curl_stats_from_config(child);
706       if (db->stats == NULL)
707         status = -1;
708     } else if (db->url && strcasecmp("AddressFamily", child->key) == 0) {
709       char *af = NULL;
710       status = cf_util_get_string(child, &af);
711       if (status != 0 || af == NULL) {
712         WARNING("curl_json plugin: Cannot parse value of `%s' for URL `%s'.",
713                 child->key, db->url);
714       } else if (strcasecmp("any", af) == 0) {
715         db->address_family = CURL_IPRESOLVE_WHATEVER;
716       } else if (strcasecmp("ipv4", af) == 0) {
717         db->address_family = CURL_IPRESOLVE_V4;
718       } else if (strcasecmp("ipv6", af) == 0) {
719         /* If curl supports ipv6, use it. If not, log a warning and
720          * fall back to default - don't set status to non-zero.
721          */
722         curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
723         if (curl_info->features & CURL_VERSION_IPV6)
724           db->address_family = CURL_IPRESOLVE_V6;
725         else
726           WARNING("curl_json plugin: IPv6 not supported by this libCURL. "
727                   "Using fallback `any'.");
728       } else {
729         WARNING("curl_json plugin: Unsupported value of `%s' for URL `%s'.",
730                 child->key, db->url);
731         status = -1;
732       }
733     } else {
734       WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
735       status = -1;
736     }
737
738     if (status != 0)
739       break;
740   }
741
742   if (status == 0) {
743     if (db->tree == NULL) {
744       WARNING("curl_json plugin: No (valid) `Key' block within `%s' \"`%s'\".",
745               db->url ? "URL" : "Sock", db->url ? db->url : db->sock);
746       status = -1;
747     }
748     if (status == 0 && db->url)
749       status = cj_init_curl(db);
750   }
751
752   /* If all went well, register this database for reading */
753   if (status == 0) {
754     char *cb_name;
755
756     if (db->instance == NULL)
757       db->instance = strdup("default");
758
759     DEBUG("curl_json plugin: Registering new read callback: %s", db->instance);
760
761     cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
762                               db->url ? db->url : db->sock);
763
764     plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read, interval,
765                                  &(user_data_t){
766                                      .data = db,
767                                      .free_func = cj_free,
768                                  });
769     sfree(cb_name);
770   } else {
771     cj_free(db);
772     return -1;
773   }
774
775   return 0;
776 }
777 /* }}} int cj_config_add_database */
778
779 static int cj_config(oconfig_item_t *ci) /* {{{ */
780 {
781   int success;
782   int errors;
783   int status;
784
785   success = 0;
786   errors = 0;
787
788   for (int i = 0; i < ci->children_num; i++) {
789     oconfig_item_t *child = ci->children + i;
790
791     if (strcasecmp("Sock", child->key) == 0 ||
792         strcasecmp("URL", child->key) == 0) {
793       status = cj_config_add_url(child);
794       if (status == 0)
795         success++;
796       else
797         errors++;
798     } else {
799       WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
800       errors++;
801     }
802   }
803
804   if ((success == 0) && (errors > 0)) {
805     ERROR("curl_json plugin: All statements failed.");
806     return -1;
807   }
808
809   return 0;
810 } /* }}} int cj_config */
811
812 /* }}} End of configuration handling functions */
813
814 static const char *cj_host(cj_t *db) /* {{{ */
815 {
816   if ((db->host == NULL) || (strcmp("", db->host) == 0) ||
817       (strcmp(CJ_DEFAULT_HOST, db->host) == 0))
818     return hostname_g;
819   return db->host;
820 } /* }}} cj_host */
821
822 static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
823 {
824   value_list_t vl = VALUE_LIST_INIT;
825
826   vl.values = value;
827   vl.values_len = 1;
828
829   if (key->instance == NULL) {
830     int len = 0;
831     for (int i = 0; i < db->depth; i++)
832       len += snprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
833                       i ? "-%s" : "%s", db->state[i + 1].name);
834   } else
835     sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance));
836
837   sstrncpy(vl.host, cj_host(db), sizeof(vl.host));
838   sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_json",
839            sizeof(vl.plugin));
840   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
841   sstrncpy(vl.type, key->type, sizeof(vl.type));
842
843   plugin_dispatch_values(&vl);
844 } /* }}} int cj_submit_impl */
845
846 static int cj_sock_perform(cj_t *db) /* {{{ */
847 {
848   struct sockaddr_un sa_unix = {
849       .sun_family = AF_UNIX,
850   };
851   sstrncpy(sa_unix.sun_path, db->sock, sizeof(sa_unix.sun_path));
852
853   int fd = socket(AF_UNIX, SOCK_STREAM, 0);
854   if (fd < 0)
855     return -1;
856   if (connect(fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0) {
857     ERROR("curl_json plugin: connect(%s) failed: %s",
858           (db->sock != NULL) ? db->sock : "<null>", STRERRNO);
859     close(fd);
860     return -1;
861   }
862
863   ssize_t red;
864   do {
865     unsigned char buffer[4096];
866     red = read(fd, buffer, sizeof(buffer));
867     if (red < 0) {
868       ERROR("curl_json plugin: read(%s) failed: %s",
869             (db->sock != NULL) ? db->sock : "<null>", STRERRNO);
870       close(fd);
871       return -1;
872     }
873     if (!cj_curl_callback(buffer, red, 1, db))
874       break;
875   } while (red > 0);
876   close(fd);
877   return 0;
878 } /* }}} int cj_sock_perform */
879
880 static int cj_curl_perform(cj_t *db) /* {{{ */
881 {
882   int status;
883   long rc;
884   char *url;
885
886   curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
887
888   status = curl_easy_perform(db->curl);
889   if (status != CURLE_OK) {
890     ERROR("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
891           status, db->curl_errbuf, db->url);
892     return -1;
893   }
894   if (db->stats != NULL)
895     curl_stats_dispatch(db->stats, db->curl, cj_host(db), "curl_json",
896                         db->instance);
897
898   curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
899   curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc);
900
901   /* The response code is zero if a non-HTTP transport was used. */
902   if ((rc != 0) && (rc != 200)) {
903     ERROR("curl_json plugin: curl_easy_perform failed with "
904           "response code %ld (%s)",
905           rc, url);
906     return -1;
907   }
908   return 0;
909 } /* }}} int cj_curl_perform */
910
911 static int cj_perform(cj_t *db) /* {{{ */
912 {
913   int status;
914   yajl_handle yprev = db->yajl;
915
916   db->yajl = yajl_alloc(&ycallbacks,
917 #if HAVE_YAJL_V2
918                         /* alloc funcs = */ NULL,
919 #else
920                         /* alloc funcs = */ NULL, NULL,
921 #endif
922                         /* context = */ (void *)db);
923   if (db->yajl == NULL) {
924     ERROR("curl_json plugin: yajl_alloc failed.");
925     db->yajl = yprev;
926     return -1;
927   }
928
929   if (db->url)
930     status = cj_curl_perform(db);
931   else
932     status = cj_sock_perform(db);
933   if (status < 0) {
934     yajl_free(db->yajl);
935     db->yajl = yprev;
936     return -1;
937   }
938
939 #if HAVE_YAJL_V2
940   status = yajl_complete_parse(db->yajl);
941 #else
942   status = yajl_parse_complete(db->yajl);
943 #endif
944   if (status != yajl_status_ok) {
945     unsigned char *errmsg;
946
947     errmsg = yajl_get_error(db->yajl, /* verbose = */ 0,
948                             /* jsonText = */ NULL, /* jsonTextLen = */ 0);
949     ERROR("curl_json plugin: yajl_parse_complete failed: %s", (char *)errmsg);
950     yajl_free_error(db->yajl, errmsg);
951     yajl_free(db->yajl);
952     db->yajl = yprev;
953     return -1;
954   }
955
956   yajl_free(db->yajl);
957   db->yajl = yprev;
958   return 0;
959 } /* }}} int cj_perform */
960
961 static int cj_read(user_data_t *ud) /* {{{ */
962 {
963   cj_t *db;
964
965   if ((ud == NULL) || (ud->data == NULL)) {
966     ERROR("curl_json plugin: cj_read: Invalid user data.");
967     return -1;
968   }
969
970   db = (cj_t *)ud->data;
971
972   db->depth = 0;
973   memset(&db->state, 0, sizeof(db->state));
974
975   /* This is not a compound literal because EPEL6's GCC is not cool enough to
976    * handle anonymous unions within compound literals. */
977   cj_tree_entry_t root = {0};
978   root.type = TREE;
979   root.tree = db->tree;
980   db->state[0].entry = &root;
981
982   int status = cj_perform(db);
983
984   db->state[0].entry = NULL;
985
986   return status;
987 } /* }}} int cj_read */
988
989 static int cj_init(void) /* {{{ */
990 {
991   /* Call this while collectd is still single-threaded to avoid
992    * initialization issues in libgcrypt. */
993   curl_global_init(CURL_GLOBAL_SSL);
994   return 0;
995 } /* }}} int cj_init */
996
997 void module_register(void) {
998   plugin_register_complex_config("curl_json", cj_config);
999   plugin_register_init("curl_json", cj_init);
1000 } /* void module_register */