Merge branch 'collectd-5.7'
[collectd.git] / src / write_http.c
1 /**
2  * collectd - src/write_http.c
3  * Copyright (C) 2009       Paul Sadauskas
4  * Copyright (C) 2009       Doug MacEachern
5  * Copyright (C) 2007-2014  Florian octo Forster
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; only version 2 of the License is applicable.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Authors:
21  *   Florian octo Forster <octo at collectd.org>
22  *   Doug MacEachern <dougm@hyperic.com>
23  *   Paul Sadauskas <psadauskas@gmail.com>
24  **/
25
26 #include "collectd.h"
27
28 #include "common.h"
29 #include "plugin.h"
30 #include "utils_format_json.h"
31 #include "utils_format_kairosdb.h"
32
33 #include <curl/curl.h>
34
35 #ifndef WRITE_HTTP_DEFAULT_BUFFER_SIZE
36 #define WRITE_HTTP_DEFAULT_BUFFER_SIZE 4096
37 #endif
38
39 /*
40  * Private variables
41  */
42 struct wh_callback_s {
43   char *name;
44
45   char *location;
46   char *user;
47   char *pass;
48   char *credentials;
49   _Bool verify_peer;
50   _Bool verify_host;
51   char *cacert;
52   char *capath;
53   char *clientkey;
54   char *clientcert;
55   char *clientkeypass;
56   long sslversion;
57   _Bool store_rates;
58   _Bool log_http_error;
59   int low_speed_limit;
60   time_t low_speed_time;
61   int timeout;
62
63 #define WH_FORMAT_COMMAND 0
64 #define WH_FORMAT_JSON 1
65 #define WH_FORMAT_KAIROSDB 2
66   int format;
67   _Bool send_metrics;
68   _Bool send_notifications;
69
70   CURL *curl;
71   struct curl_slist *headers;
72   char curl_errbuf[CURL_ERROR_SIZE];
73
74   char *send_buffer;
75   size_t send_buffer_size;
76   size_t send_buffer_free;
77   size_t send_buffer_fill;
78   cdtime_t send_buffer_init_time;
79
80   pthread_mutex_t send_lock;
81 };
82 typedef struct wh_callback_s wh_callback_t;
83
84 static void wh_log_http_error(wh_callback_t *cb) {
85   if (!cb->log_http_error)
86     return;
87
88   long http_code = 0;
89
90   curl_easy_getinfo(cb->curl, CURLINFO_RESPONSE_CODE, &http_code);
91
92   if (http_code != 200)
93     INFO("write_http plugin: HTTP Error code: %lu", http_code);
94 }
95
96 static void wh_reset_buffer(wh_callback_t *cb) /* {{{ */
97 {
98   if ((cb == NULL) || (cb->send_buffer == NULL))
99     return;
100
101   memset(cb->send_buffer, 0, cb->send_buffer_size);
102   cb->send_buffer_free = cb->send_buffer_size;
103   cb->send_buffer_fill = 0;
104   cb->send_buffer_init_time = cdtime();
105
106   if (cb->format == WH_FORMAT_JSON || cb->format == WH_FORMAT_KAIROSDB) {
107     format_json_initialize(cb->send_buffer, &cb->send_buffer_fill,
108                            &cb->send_buffer_free);
109   }
110 } /* }}} wh_reset_buffer */
111
112 /* must hold cb->send_lock when calling */
113 static int wh_post_nolock(wh_callback_t *cb, char const *data) /* {{{ */
114 {
115   int status = 0;
116
117   curl_easy_setopt(cb->curl, CURLOPT_POSTFIELDS, data);
118   status = curl_easy_perform(cb->curl);
119
120   wh_log_http_error(cb);
121
122   if (status != CURLE_OK) {
123     ERROR("write_http plugin: curl_easy_perform failed with "
124           "status %i: %s",
125           status, cb->curl_errbuf);
126   }
127   return (status);
128 } /* }}} wh_post_nolock */
129
130 static int wh_callback_init(wh_callback_t *cb) /* {{{ */
131 {
132   if (cb->curl != NULL)
133     return (0);
134
135   cb->curl = curl_easy_init();
136   if (cb->curl == NULL) {
137     ERROR("curl plugin: curl_easy_init failed.");
138     return (-1);
139   }
140
141   if (cb->low_speed_limit > 0 && cb->low_speed_time > 0) {
142     curl_easy_setopt(cb->curl, CURLOPT_LOW_SPEED_LIMIT,
143                      (long)(cb->low_speed_limit * cb->low_speed_time));
144     curl_easy_setopt(cb->curl, CURLOPT_LOW_SPEED_TIME,
145                      (long)cb->low_speed_time);
146   }
147
148 #ifdef HAVE_CURLOPT_TIMEOUT_MS
149   if (cb->timeout > 0)
150     curl_easy_setopt(cb->curl, CURLOPT_TIMEOUT_MS, (long)cb->timeout);
151 #endif
152
153   curl_easy_setopt(cb->curl, CURLOPT_NOSIGNAL, 1L);
154   curl_easy_setopt(cb->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
155
156   cb->headers = curl_slist_append(cb->headers, "Accept:  */*");
157   if (cb->format == WH_FORMAT_JSON || cb->format == WH_FORMAT_KAIROSDB)
158     cb->headers =
159         curl_slist_append(cb->headers, "Content-Type: application/json");
160   else
161     cb->headers = curl_slist_append(cb->headers, "Content-Type: text/plain");
162   cb->headers = curl_slist_append(cb->headers, "Expect:");
163   curl_easy_setopt(cb->curl, CURLOPT_HTTPHEADER, cb->headers);
164
165   curl_easy_setopt(cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf);
166   curl_easy_setopt(cb->curl, CURLOPT_URL, cb->location);
167   curl_easy_setopt(cb->curl, CURLOPT_FOLLOWLOCATION, 1L);
168   curl_easy_setopt(cb->curl, CURLOPT_MAXREDIRS, 50L);
169
170   if (cb->user != NULL) {
171 #ifdef HAVE_CURLOPT_USERNAME
172     curl_easy_setopt(cb->curl, CURLOPT_USERNAME, cb->user);
173     curl_easy_setopt(cb->curl, CURLOPT_PASSWORD,
174                      (cb->pass == NULL) ? "" : cb->pass);
175 #else
176     size_t credentials_size;
177
178     credentials_size = strlen(cb->user) + 2;
179     if (cb->pass != NULL)
180       credentials_size += strlen(cb->pass);
181
182     cb->credentials = malloc(credentials_size);
183     if (cb->credentials == NULL) {
184       ERROR("curl plugin: malloc failed.");
185       return (-1);
186     }
187
188     ssnprintf(cb->credentials, credentials_size, "%s:%s", cb->user,
189               (cb->pass == NULL) ? "" : cb->pass);
190     curl_easy_setopt(cb->curl, CURLOPT_USERPWD, cb->credentials);
191 #endif
192     curl_easy_setopt(cb->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
193   }
194
195   curl_easy_setopt(cb->curl, CURLOPT_SSL_VERIFYPEER, (long)cb->verify_peer);
196   curl_easy_setopt(cb->curl, CURLOPT_SSL_VERIFYHOST, cb->verify_host ? 2L : 0L);
197   curl_easy_setopt(cb->curl, CURLOPT_SSLVERSION, cb->sslversion);
198   if (cb->cacert != NULL)
199     curl_easy_setopt(cb->curl, CURLOPT_CAINFO, cb->cacert);
200   if (cb->capath != NULL)
201     curl_easy_setopt(cb->curl, CURLOPT_CAPATH, cb->capath);
202
203   if (cb->clientkey != NULL && cb->clientcert != NULL) {
204     curl_easy_setopt(cb->curl, CURLOPT_SSLKEY, cb->clientkey);
205     curl_easy_setopt(cb->curl, CURLOPT_SSLCERT, cb->clientcert);
206
207     if (cb->clientkeypass != NULL)
208       curl_easy_setopt(cb->curl, CURLOPT_SSLKEYPASSWD, cb->clientkeypass);
209   }
210
211   wh_reset_buffer(cb);
212
213   return (0);
214 } /* }}} int wh_callback_init */
215
216 static int wh_flush_nolock(cdtime_t timeout, wh_callback_t *cb) /* {{{ */
217 {
218   int status;
219
220   DEBUG("write_http plugin: wh_flush_nolock: timeout = %.3f; "
221         "send_buffer_fill = %zu;",
222         CDTIME_T_TO_DOUBLE(timeout), cb->send_buffer_fill);
223
224   /* timeout == 0  => flush unconditionally */
225   if (timeout > 0) {
226     cdtime_t now;
227
228     now = cdtime();
229     if ((cb->send_buffer_init_time + timeout) > now)
230       return (0);
231   }
232
233   if (cb->format == WH_FORMAT_COMMAND) {
234     if (cb->send_buffer_fill == 0) {
235       cb->send_buffer_init_time = cdtime();
236       return (0);
237     }
238
239     status = wh_post_nolock(cb, cb->send_buffer);
240     wh_reset_buffer(cb);
241   } else if (cb->format == WH_FORMAT_JSON || cb->format == WH_FORMAT_KAIROSDB) {
242     if (cb->send_buffer_fill <= 2) {
243       cb->send_buffer_init_time = cdtime();
244       return (0);
245     }
246
247     status = format_json_finalize(cb->send_buffer, &cb->send_buffer_fill,
248                                   &cb->send_buffer_free);
249     if (status != 0) {
250       ERROR("write_http: wh_flush_nolock: "
251             "format_json_finalize failed.");
252       wh_reset_buffer(cb);
253       return (status);
254     }
255
256     status = wh_post_nolock(cb, cb->send_buffer);
257     wh_reset_buffer(cb);
258   } else {
259     ERROR("write_http: wh_flush_nolock: "
260           "Unknown format: %i",
261           cb->format);
262     return (-1);
263   }
264
265   return (status);
266 } /* }}} wh_flush_nolock */
267
268 static int wh_flush(cdtime_t timeout, /* {{{ */
269                     const char *identifier __attribute__((unused)),
270                     user_data_t *user_data) {
271   wh_callback_t *cb;
272   int status;
273
274   if (user_data == NULL)
275     return (-EINVAL);
276
277   cb = user_data->data;
278
279   pthread_mutex_lock(&cb->send_lock);
280
281   if (wh_callback_init(cb) != 0) {
282     ERROR("write_http plugin: wh_callback_init failed.");
283     pthread_mutex_unlock(&cb->send_lock);
284     return (-1);
285   }
286
287   status = wh_flush_nolock(timeout, cb);
288   pthread_mutex_unlock(&cb->send_lock);
289
290   return (status);
291 } /* }}} int wh_flush */
292
293 static void wh_callback_free(void *data) /* {{{ */
294 {
295   wh_callback_t *cb;
296
297   if (data == NULL)
298     return;
299
300   cb = data;
301
302   if (cb->send_buffer != NULL)
303     wh_flush_nolock(/* timeout = */ 0, cb);
304
305   if (cb->curl != NULL) {
306     curl_easy_cleanup(cb->curl);
307     cb->curl = NULL;
308   }
309
310   if (cb->headers != NULL) {
311     curl_slist_free_all(cb->headers);
312     cb->headers = NULL;
313   }
314
315   sfree(cb->name);
316   sfree(cb->location);
317   sfree(cb->user);
318   sfree(cb->pass);
319   sfree(cb->credentials);
320   sfree(cb->cacert);
321   sfree(cb->capath);
322   sfree(cb->clientkey);
323   sfree(cb->clientcert);
324   sfree(cb->clientkeypass);
325   sfree(cb->send_buffer);
326
327   sfree(cb);
328 } /* }}} void wh_callback_free */
329
330 static int wh_write_command(const data_set_t *ds,
331                             const value_list_t *vl, /* {{{ */
332                             wh_callback_t *cb) {
333   char key[10 * DATA_MAX_NAME_LEN];
334   char values[512];
335   char command[1024];
336   size_t command_len;
337
338   int status;
339
340   /* sanity checks, primarily to make static analyzers happy. */
341   if ((cb == NULL) || (cb->send_buffer == NULL))
342     return -1;
343
344   if (strcmp(ds->type, vl->type) != 0) {
345     ERROR("write_http plugin: DS type does not match "
346           "value list type");
347     return -1;
348   }
349
350   /* Copy the identifier to `key' and escape it. */
351   status = FORMAT_VL(key, sizeof(key), vl);
352   if (status != 0) {
353     ERROR("write_http plugin: error with format_name");
354     return (status);
355   }
356   escape_string(key, sizeof(key));
357
358   /* Convert the values to an ASCII representation and put that into
359    * `values'. */
360   status = format_values(values, sizeof(values), ds, vl, cb->store_rates);
361   if (status != 0) {
362     ERROR("write_http plugin: error with "
363           "wh_value_list_to_string");
364     return (status);
365   }
366
367   command_len = (size_t)ssnprintf(command, sizeof(command),
368                                   "PUTVAL %s interval=%.3f %s\r\n", key,
369                                   CDTIME_T_TO_DOUBLE(vl->interval), values);
370   if (command_len >= sizeof(command)) {
371     ERROR("write_http plugin: Command buffer too small: "
372           "Need %zu bytes.",
373           command_len + 1);
374     return (-1);
375   }
376
377   pthread_mutex_lock(&cb->send_lock);
378   if (wh_callback_init(cb) != 0) {
379     ERROR("write_http plugin: wh_callback_init failed.");
380     pthread_mutex_unlock(&cb->send_lock);
381     return (-1);
382   }
383
384   if (command_len >= cb->send_buffer_free) {
385     status = wh_flush_nolock(/* timeout = */ 0, cb);
386     if (status != 0) {
387       pthread_mutex_unlock(&cb->send_lock);
388       return (status);
389     }
390   }
391   assert(command_len < cb->send_buffer_free);
392
393   /* Make scan-build happy. */
394   assert(cb->send_buffer != NULL);
395
396   /* `command_len + 1' because `command_len' does not include the
397    * trailing null byte. Neither does `send_buffer_fill'. */
398   memcpy(cb->send_buffer + cb->send_buffer_fill, command, command_len + 1);
399   cb->send_buffer_fill += command_len;
400   cb->send_buffer_free -= command_len;
401
402   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%) \"%s\"", cb->location,
403         cb->send_buffer_fill, cb->send_buffer_size,
404         100.0 * ((double)cb->send_buffer_fill) / ((double)cb->send_buffer_size),
405         command);
406
407   /* Check if we have enough space for this command. */
408   pthread_mutex_unlock(&cb->send_lock);
409
410   return (0);
411 } /* }}} int wh_write_command */
412
413 static int wh_write_json(const data_set_t *ds, const value_list_t *vl, /* {{{ */
414                          wh_callback_t *cb) {
415   int status;
416
417   pthread_mutex_lock(&cb->send_lock);
418   if (wh_callback_init(cb) != 0) {
419     ERROR("write_http plugin: wh_callback_init failed.");
420     pthread_mutex_unlock(&cb->send_lock);
421     return (-1);
422   }
423
424   status =
425       format_json_value_list(cb->send_buffer, &cb->send_buffer_fill,
426                              &cb->send_buffer_free, ds, vl, cb->store_rates);
427   if (status == -ENOMEM) {
428     status = wh_flush_nolock(/* timeout = */ 0, cb);
429     if (status != 0) {
430       wh_reset_buffer(cb);
431       pthread_mutex_unlock(&cb->send_lock);
432       return (status);
433     }
434
435     status =
436         format_json_value_list(cb->send_buffer, &cb->send_buffer_fill,
437                                &cb->send_buffer_free, ds, vl, cb->store_rates);
438   }
439   if (status != 0) {
440     pthread_mutex_unlock(&cb->send_lock);
441     return (status);
442   }
443
444   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%)", cb->location,
445         cb->send_buffer_fill, cb->send_buffer_size,
446         100.0 * ((double)cb->send_buffer_fill) /
447             ((double)cb->send_buffer_size));
448
449   /* Check if we have enough space for this command. */
450   pthread_mutex_unlock(&cb->send_lock);
451
452   return (0);
453 } /* }}} int wh_write_json */
454
455 static int wh_write_kairosdb(const data_set_t *ds,
456                              const value_list_t *vl, /* {{{ */
457                              wh_callback_t *cb) {
458   int status;
459
460   pthread_mutex_lock(&cb->send_lock);
461
462   if (cb->curl == NULL) {
463     status = wh_callback_init(cb);
464     if (status != 0) {
465       ERROR("write_http plugin: wh_callback_init failed.");
466       pthread_mutex_unlock(&cb->send_lock);
467       return (-1);
468     }
469   }
470
471   status = format_kairosdb_value_list(cb->send_buffer, &cb->send_buffer_fill,
472                                       &cb->send_buffer_free, ds, vl,
473                                       cb->store_rates);
474   if (status == -ENOMEM) {
475     status = wh_flush_nolock(/* timeout = */ 0, cb);
476     if (status != 0) {
477       wh_reset_buffer(cb);
478       pthread_mutex_unlock(&cb->send_lock);
479       return (status);
480     }
481
482     status = format_kairosdb_value_list(cb->send_buffer, &cb->send_buffer_fill,
483                                         &cb->send_buffer_free, ds, vl,
484                                         cb->store_rates);
485   }
486   if (status != 0) {
487     pthread_mutex_unlock(&cb->send_lock);
488     return (status);
489   }
490
491   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%)", cb->location,
492         cb->send_buffer_fill, cb->send_buffer_size,
493         100.0 * ((double)cb->send_buffer_fill) /
494             ((double)cb->send_buffer_size));
495
496   /* Check if we have enough space for this command. */
497   pthread_mutex_unlock(&cb->send_lock);
498
499   return (0);
500 } /* }}} int wh_write_kairosdb */
501
502 static int wh_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
503                     user_data_t *user_data) {
504   wh_callback_t *cb;
505   int status;
506
507   if (user_data == NULL)
508     return (-EINVAL);
509
510   cb = user_data->data;
511   assert(cb->send_metrics);
512
513   switch (cb->format) {
514   case WH_FORMAT_JSON:
515     status = wh_write_json(ds, vl, cb);
516     break;
517   case WH_FORMAT_KAIROSDB:
518     status = wh_write_kairosdb(ds, vl, cb);
519     break;
520   default:
521     status = wh_write_command(ds, vl, cb);
522     break;
523   }
524   return (status);
525 } /* }}} int wh_write */
526
527 static int wh_notify(notification_t const *n, user_data_t *ud) /* {{{ */
528 {
529   wh_callback_t *cb;
530   char alert[4096];
531   int status;
532
533   if ((ud == NULL) || (ud->data == NULL))
534     return (EINVAL);
535
536   cb = ud->data;
537   assert(cb->send_notifications);
538
539   status = format_json_notification(alert, sizeof(alert), n);
540   if (status != 0) {
541     ERROR("write_http plugin: formatting notification failed");
542     return status;
543   }
544
545   pthread_mutex_lock(&cb->send_lock);
546   if (wh_callback_init(cb) != 0) {
547     ERROR("write_http plugin: wh_callback_init failed.");
548     pthread_mutex_unlock(&cb->send_lock);
549     return (-1);
550   }
551
552   status = wh_post_nolock(cb, alert);
553   pthread_mutex_unlock(&cb->send_lock);
554
555   return (status);
556 } /* }}} int wh_notify */
557
558 static int config_set_format(wh_callback_t *cb, /* {{{ */
559                              oconfig_item_t *ci) {
560   char *string;
561
562   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
563     WARNING("write_http plugin: The `%s' config option "
564             "needs exactly one string argument.",
565             ci->key);
566     return (-1);
567   }
568
569   string = ci->values[0].value.string;
570   if (strcasecmp("Command", string) == 0)
571     cb->format = WH_FORMAT_COMMAND;
572   else if (strcasecmp("JSON", string) == 0)
573     cb->format = WH_FORMAT_JSON;
574   else if (strcasecmp("KAIROSDB", string) == 0)
575     cb->format = WH_FORMAT_KAIROSDB;
576   else {
577     ERROR("write_http plugin: Invalid format string: %s", string);
578     return (-1);
579   }
580
581   return (0);
582 } /* }}} int config_set_format */
583
584 static int wh_config_append_string(const char *name,
585                                    struct curl_slist **dest, /* {{{ */
586                                    oconfig_item_t *ci) {
587   struct curl_slist *temp = NULL;
588   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
589     WARNING("write_http plugin: `%s' needs exactly one string argument.", name);
590     return (-1);
591   }
592
593   temp = curl_slist_append(*dest, ci->values[0].value.string);
594   if (temp == NULL)
595     return (-1);
596
597   *dest = temp;
598
599   return (0);
600 } /* }}} int wh_config_append_string */
601
602 static int wh_config_node(oconfig_item_t *ci) /* {{{ */
603 {
604   wh_callback_t *cb;
605   int buffer_size = 0;
606   char callback_name[DATA_MAX_NAME_LEN];
607   int status = 0;
608
609   cb = calloc(1, sizeof(*cb));
610   if (cb == NULL) {
611     ERROR("write_http plugin: calloc failed.");
612     return (-1);
613   }
614   cb->verify_peer = 1;
615   cb->verify_host = 1;
616   cb->format = WH_FORMAT_COMMAND;
617   cb->sslversion = CURL_SSLVERSION_DEFAULT;
618   cb->low_speed_limit = 0;
619   cb->timeout = 0;
620   cb->log_http_error = 0;
621   cb->headers = NULL;
622   cb->send_metrics = 1;
623   cb->send_notifications = 0;
624
625   pthread_mutex_init(&cb->send_lock, /* attr = */ NULL);
626
627   cf_util_get_string(ci, &cb->name);
628
629   /* FIXME: Remove this legacy mode in version 6. */
630   if (strcasecmp("URL", ci->key) == 0)
631     cf_util_get_string(ci, &cb->location);
632
633   for (int i = 0; i < ci->children_num; i++) {
634     oconfig_item_t *child = ci->children + i;
635
636     if (strcasecmp("URL", child->key) == 0)
637       status = cf_util_get_string(child, &cb->location);
638     else if (strcasecmp("User", child->key) == 0)
639       status = cf_util_get_string(child, &cb->user);
640     else if (strcasecmp("Password", child->key) == 0)
641       status = cf_util_get_string(child, &cb->pass);
642     else if (strcasecmp("VerifyPeer", child->key) == 0)
643       status = cf_util_get_boolean(child, &cb->verify_peer);
644     else if (strcasecmp("VerifyHost", child->key) == 0)
645       status = cf_util_get_boolean(child, &cb->verify_host);
646     else if (strcasecmp("CACert", child->key) == 0)
647       status = cf_util_get_string(child, &cb->cacert);
648     else if (strcasecmp("CAPath", child->key) == 0)
649       status = cf_util_get_string(child, &cb->capath);
650     else if (strcasecmp("ClientKey", child->key) == 0)
651       status = cf_util_get_string(child, &cb->clientkey);
652     else if (strcasecmp("ClientCert", child->key) == 0)
653       status = cf_util_get_string(child, &cb->clientcert);
654     else if (strcasecmp("ClientKeyPass", child->key) == 0)
655       status = cf_util_get_string(child, &cb->clientkeypass);
656     else if (strcasecmp("SSLVersion", child->key) == 0) {
657       char *value = NULL;
658
659       status = cf_util_get_string(child, &value);
660       if (status != 0)
661         break;
662
663       if (value == NULL || strcasecmp("default", value) == 0)
664         cb->sslversion = CURL_SSLVERSION_DEFAULT;
665       else if (strcasecmp("SSLv2", value) == 0)
666         cb->sslversion = CURL_SSLVERSION_SSLv2;
667       else if (strcasecmp("SSLv3", value) == 0)
668         cb->sslversion = CURL_SSLVERSION_SSLv3;
669       else if (strcasecmp("TLSv1", value) == 0)
670         cb->sslversion = CURL_SSLVERSION_TLSv1;
671 #if (LIBCURL_VERSION_MAJOR > 7) ||                                             \
672     (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 34)
673       else if (strcasecmp("TLSv1_0", value) == 0)
674         cb->sslversion = CURL_SSLVERSION_TLSv1_0;
675       else if (strcasecmp("TLSv1_1", value) == 0)
676         cb->sslversion = CURL_SSLVERSION_TLSv1_1;
677       else if (strcasecmp("TLSv1_2", value) == 0)
678         cb->sslversion = CURL_SSLVERSION_TLSv1_2;
679 #endif
680       else {
681         ERROR("write_http plugin: Invalid SSLVersion "
682               "option: %s.",
683               value);
684         status = EINVAL;
685       }
686
687       sfree(value);
688     } else if (strcasecmp("Format", child->key) == 0)
689       status = config_set_format(cb, child);
690     else if (strcasecmp("Metrics", child->key) == 0)
691       cf_util_get_boolean(child, &cb->send_metrics);
692     else if (strcasecmp("Notifications", child->key) == 0)
693       cf_util_get_boolean(child, &cb->send_notifications);
694     else if (strcasecmp("StoreRates", child->key) == 0)
695       status = cf_util_get_boolean(child, &cb->store_rates);
696     else if (strcasecmp("BufferSize", child->key) == 0)
697       status = cf_util_get_int(child, &buffer_size);
698     else if (strcasecmp("LowSpeedLimit", child->key) == 0)
699       status = cf_util_get_int(child, &cb->low_speed_limit);
700     else if (strcasecmp("Timeout", child->key) == 0)
701       status = cf_util_get_int(child, &cb->timeout);
702     else if (strcasecmp("LogHttpError", child->key) == 0)
703       status = cf_util_get_boolean(child, &cb->log_http_error);
704     else if (strcasecmp("Header", child->key) == 0)
705       status = wh_config_append_string("Header", &cb->headers, child);
706     else {
707       ERROR("write_http plugin: Invalid configuration "
708             "option: %s.",
709             child->key);
710       status = EINVAL;
711     }
712
713     if (status != 0)
714       break;
715   }
716
717   if (status != 0) {
718     wh_callback_free(cb);
719     return (status);
720   }
721
722   if (cb->location == NULL) {
723     ERROR("write_http plugin: no URL defined for instance '%s'", cb->name);
724     wh_callback_free(cb);
725     return (-1);
726   }
727
728   if (!cb->send_metrics && !cb->send_notifications) {
729     ERROR("write_http plugin: Neither metrics nor notifications "
730           "are enabled for \"%s\".",
731           cb->name);
732     wh_callback_free(cb);
733     return (-1);
734   }
735
736   if (cb->low_speed_limit > 0)
737     cb->low_speed_time = CDTIME_T_TO_TIME_T(plugin_get_interval());
738
739   /* Determine send_buffer_size. */
740   cb->send_buffer_size = WRITE_HTTP_DEFAULT_BUFFER_SIZE;
741   if (buffer_size >= 1024)
742     cb->send_buffer_size = (size_t)buffer_size;
743   else if (buffer_size != 0)
744     ERROR("write_http plugin: Ignoring invalid BufferSize setting (%d).",
745           buffer_size);
746
747   /* Allocate the buffer. */
748   cb->send_buffer = malloc(cb->send_buffer_size);
749   if (cb->send_buffer == NULL) {
750     ERROR("write_http plugin: malloc(%zu) failed.", cb->send_buffer_size);
751     wh_callback_free(cb);
752     return (-1);
753   }
754   /* Nulls the buffer and sets ..._free and ..._fill. */
755   wh_reset_buffer(cb);
756
757   ssnprintf(callback_name, sizeof(callback_name), "write_http/%s", cb->name);
758   DEBUG("write_http: Registering write callback '%s' with URL '%s'",
759         callback_name, cb->location);
760
761   user_data_t user_data = {
762       .data = cb, .free_func = wh_callback_free,
763   };
764
765   if (cb->send_metrics) {
766     plugin_register_write(callback_name, wh_write, &user_data);
767     user_data.free_func = NULL;
768
769     plugin_register_flush(callback_name, wh_flush, &user_data);
770   }
771
772   if (cb->send_notifications) {
773     plugin_register_notification(callback_name, wh_notify, &user_data);
774     user_data.free_func = NULL;
775   }
776
777   return (0);
778 } /* }}} int wh_config_node */
779
780 static int wh_config(oconfig_item_t *ci) /* {{{ */
781 {
782   for (int i = 0; i < ci->children_num; i++) {
783     oconfig_item_t *child = ci->children + i;
784
785     if (strcasecmp("Node", child->key) == 0)
786       wh_config_node(child);
787     /* FIXME: Remove this legacy mode in version 6. */
788     else if (strcasecmp("URL", child->key) == 0) {
789       WARNING("write_http plugin: Legacy <URL> block found. "
790               "Please use <Node> instead.");
791       wh_config_node(child);
792     } else {
793       ERROR("write_http plugin: Invalid configuration "
794             "option: %s.",
795             child->key);
796     }
797   }
798
799   return (0);
800 } /* }}} int wh_config */
801
802 static int wh_init(void) /* {{{ */
803 {
804   /* Call this while collectd is still single-threaded to avoid
805    * initialization issues in libgcrypt. */
806   curl_global_init(CURL_GLOBAL_SSL);
807   return (0);
808 } /* }}} int wh_init */
809
810 void module_register(void) /* {{{ */
811 {
812   plugin_register_complex_config("write_http", wh_config);
813   plugin_register_init("write_http", wh_init);
814 } /* }}} void module_register */