6127c0426d2ba4fc36ffdd0d654f564b95b3a829
[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   /* `command_len + 1' because `command_len' does not include the
394    * trailing null byte. Neither does `send_buffer_fill'. */
395   memcpy(cb->send_buffer + cb->send_buffer_fill, command, command_len + 1);
396   cb->send_buffer_fill += command_len;
397   cb->send_buffer_free -= command_len;
398
399   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%) \"%s\"", cb->location,
400         cb->send_buffer_fill, cb->send_buffer_size,
401         100.0 * ((double)cb->send_buffer_fill) / ((double)cb->send_buffer_size),
402         command);
403
404   /* Check if we have enough space for this command. */
405   pthread_mutex_unlock(&cb->send_lock);
406
407   return (0);
408 } /* }}} int wh_write_command */
409
410 static int wh_write_json(const data_set_t *ds, const value_list_t *vl, /* {{{ */
411                          wh_callback_t *cb) {
412   int status;
413
414   pthread_mutex_lock(&cb->send_lock);
415   if (wh_callback_init(cb) != 0) {
416     ERROR("write_http plugin: wh_callback_init failed.");
417     pthread_mutex_unlock(&cb->send_lock);
418     return (-1);
419   }
420
421   status =
422       format_json_value_list(cb->send_buffer, &cb->send_buffer_fill,
423                              &cb->send_buffer_free, ds, vl, cb->store_rates);
424   if (status == -ENOMEM) {
425     status = wh_flush_nolock(/* timeout = */ 0, cb);
426     if (status != 0) {
427       wh_reset_buffer(cb);
428       pthread_mutex_unlock(&cb->send_lock);
429       return (status);
430     }
431
432     status =
433         format_json_value_list(cb->send_buffer, &cb->send_buffer_fill,
434                                &cb->send_buffer_free, ds, vl, cb->store_rates);
435   }
436   if (status != 0) {
437     pthread_mutex_unlock(&cb->send_lock);
438     return (status);
439   }
440
441   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%)", cb->location,
442         cb->send_buffer_fill, cb->send_buffer_size,
443         100.0 * ((double)cb->send_buffer_fill) /
444             ((double)cb->send_buffer_size));
445
446   /* Check if we have enough space for this command. */
447   pthread_mutex_unlock(&cb->send_lock);
448
449   return (0);
450 } /* }}} int wh_write_json */
451
452 static int wh_write_kairosdb(const data_set_t *ds,
453                              const value_list_t *vl, /* {{{ */
454                              wh_callback_t *cb) {
455   int status;
456
457   pthread_mutex_lock(&cb->send_lock);
458
459   if (cb->curl == NULL) {
460     status = wh_callback_init(cb);
461     if (status != 0) {
462       ERROR("write_http plugin: wh_callback_init failed.");
463       pthread_mutex_unlock(&cb->send_lock);
464       return (-1);
465     }
466   }
467
468   status = format_kairosdb_value_list(cb->send_buffer, &cb->send_buffer_fill,
469                                       &cb->send_buffer_free, ds, vl,
470                                       cb->store_rates);
471   if (status == -ENOMEM) {
472     status = wh_flush_nolock(/* timeout = */ 0, cb);
473     if (status != 0) {
474       wh_reset_buffer(cb);
475       pthread_mutex_unlock(&cb->send_lock);
476       return (status);
477     }
478
479     status = format_kairosdb_value_list(cb->send_buffer, &cb->send_buffer_fill,
480                                         &cb->send_buffer_free, ds, vl,
481                                         cb->store_rates);
482   }
483   if (status != 0) {
484     pthread_mutex_unlock(&cb->send_lock);
485     return (status);
486   }
487
488   DEBUG("write_http plugin: <%s> buffer %zu/%zu (%g%%)", cb->location,
489         cb->send_buffer_fill, cb->send_buffer_size,
490         100.0 * ((double)cb->send_buffer_fill) /
491             ((double)cb->send_buffer_size));
492
493   /* Check if we have enough space for this command. */
494   pthread_mutex_unlock(&cb->send_lock);
495
496   return (0);
497 } /* }}} int wh_write_kairosdb */
498
499 static int wh_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
500                     user_data_t *user_data) {
501   wh_callback_t *cb;
502   int status;
503
504   if (user_data == NULL)
505     return (-EINVAL);
506
507   cb = user_data->data;
508   assert(cb->send_metrics);
509
510   switch (cb->format) {
511   case WH_FORMAT_JSON:
512     status = wh_write_json(ds, vl, cb);
513     break;
514   case WH_FORMAT_KAIROSDB:
515     status = wh_write_kairosdb(ds, vl, cb);
516     break;
517   default:
518     status = wh_write_command(ds, vl, cb);
519     break;
520   }
521   return (status);
522 } /* }}} int wh_write */
523
524 static int wh_notify(notification_t const *n, user_data_t *ud) /* {{{ */
525 {
526   wh_callback_t *cb;
527   char alert[4096];
528   int status;
529
530   if ((ud == NULL) || (ud->data == NULL))
531     return (EINVAL);
532
533   cb = ud->data;
534   assert(cb->send_notifications);
535
536   status = format_json_notification(alert, sizeof(alert), n);
537   if (status != 0) {
538     ERROR("write_http plugin: formatting notification failed");
539     return status;
540   }
541
542   pthread_mutex_lock(&cb->send_lock);
543   if (wh_callback_init(cb) != 0) {
544     ERROR("write_http plugin: wh_callback_init failed.");
545     pthread_mutex_unlock(&cb->send_lock);
546     return (-1);
547   }
548
549   status = wh_post_nolock(cb, alert);
550   pthread_mutex_unlock(&cb->send_lock);
551
552   return (status);
553 } /* }}} int wh_notify */
554
555 static int config_set_format(wh_callback_t *cb, /* {{{ */
556                              oconfig_item_t *ci) {
557   char *string;
558
559   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
560     WARNING("write_http plugin: The `%s' config option "
561             "needs exactly one string argument.",
562             ci->key);
563     return (-1);
564   }
565
566   string = ci->values[0].value.string;
567   if (strcasecmp("Command", string) == 0)
568     cb->format = WH_FORMAT_COMMAND;
569   else if (strcasecmp("JSON", string) == 0)
570     cb->format = WH_FORMAT_JSON;
571   else if (strcasecmp("KAIROSDB", string) == 0)
572     cb->format = WH_FORMAT_KAIROSDB;
573   else {
574     ERROR("write_http plugin: Invalid format string: %s", string);
575     return (-1);
576   }
577
578   return (0);
579 } /* }}} int config_set_format */
580
581 static int wh_config_append_string(const char *name,
582                                    struct curl_slist **dest, /* {{{ */
583                                    oconfig_item_t *ci) {
584   struct curl_slist *temp = NULL;
585   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
586     WARNING("write_http plugin: `%s' needs exactly one string argument.", name);
587     return (-1);
588   }
589
590   temp = curl_slist_append(*dest, ci->values[0].value.string);
591   if (temp == NULL)
592     return (-1);
593
594   *dest = temp;
595
596   return (0);
597 } /* }}} int wh_config_append_string */
598
599 static int wh_config_node(oconfig_item_t *ci) /* {{{ */
600 {
601   wh_callback_t *cb;
602   int buffer_size = 0;
603   char callback_name[DATA_MAX_NAME_LEN];
604   int status = 0;
605
606   cb = calloc(1, sizeof(*cb));
607   if (cb == NULL) {
608     ERROR("write_http plugin: calloc failed.");
609     return (-1);
610   }
611   cb->verify_peer = 1;
612   cb->verify_host = 1;
613   cb->format = WH_FORMAT_COMMAND;
614   cb->sslversion = CURL_SSLVERSION_DEFAULT;
615   cb->low_speed_limit = 0;
616   cb->timeout = 0;
617   cb->log_http_error = 0;
618   cb->headers = NULL;
619   cb->send_metrics = 1;
620   cb->send_notifications = 0;
621
622   pthread_mutex_init(&cb->send_lock, /* attr = */ NULL);
623
624   cf_util_get_string(ci, &cb->name);
625
626   /* FIXME: Remove this legacy mode in version 6. */
627   if (strcasecmp("URL", ci->key) == 0)
628     cf_util_get_string(ci, &cb->location);
629
630   for (int i = 0; i < ci->children_num; i++) {
631     oconfig_item_t *child = ci->children + i;
632
633     if (strcasecmp("URL", child->key) == 0)
634       status = cf_util_get_string(child, &cb->location);
635     else if (strcasecmp("User", child->key) == 0)
636       status = cf_util_get_string(child, &cb->user);
637     else if (strcasecmp("Password", child->key) == 0)
638       status = cf_util_get_string(child, &cb->pass);
639     else if (strcasecmp("VerifyPeer", child->key) == 0)
640       status = cf_util_get_boolean(child, &cb->verify_peer);
641     else if (strcasecmp("VerifyHost", child->key) == 0)
642       status = cf_util_get_boolean(child, &cb->verify_host);
643     else if (strcasecmp("CACert", child->key) == 0)
644       status = cf_util_get_string(child, &cb->cacert);
645     else if (strcasecmp("CAPath", child->key) == 0)
646       status = cf_util_get_string(child, &cb->capath);
647     else if (strcasecmp("ClientKey", child->key) == 0)
648       status = cf_util_get_string(child, &cb->clientkey);
649     else if (strcasecmp("ClientCert", child->key) == 0)
650       status = cf_util_get_string(child, &cb->clientcert);
651     else if (strcasecmp("ClientKeyPass", child->key) == 0)
652       status = cf_util_get_string(child, &cb->clientkeypass);
653     else if (strcasecmp("SSLVersion", child->key) == 0) {
654       char *value = NULL;
655
656       status = cf_util_get_string(child, &value);
657       if (status != 0)
658         break;
659
660       if (value == NULL || strcasecmp("default", value) == 0)
661         cb->sslversion = CURL_SSLVERSION_DEFAULT;
662       else if (strcasecmp("SSLv2", value) == 0)
663         cb->sslversion = CURL_SSLVERSION_SSLv2;
664       else if (strcasecmp("SSLv3", value) == 0)
665         cb->sslversion = CURL_SSLVERSION_SSLv3;
666       else if (strcasecmp("TLSv1", value) == 0)
667         cb->sslversion = CURL_SSLVERSION_TLSv1;
668 #if (LIBCURL_VERSION_MAJOR > 7) ||                                             \
669     (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 34)
670       else if (strcasecmp("TLSv1_0", value) == 0)
671         cb->sslversion = CURL_SSLVERSION_TLSv1_0;
672       else if (strcasecmp("TLSv1_1", value) == 0)
673         cb->sslversion = CURL_SSLVERSION_TLSv1_1;
674       else if (strcasecmp("TLSv1_2", value) == 0)
675         cb->sslversion = CURL_SSLVERSION_TLSv1_2;
676 #endif
677       else {
678         ERROR("write_http plugin: Invalid SSLVersion "
679               "option: %s.",
680               value);
681         status = EINVAL;
682       }
683
684       sfree(value);
685     } else if (strcasecmp("Format", child->key) == 0)
686       status = config_set_format(cb, child);
687     else if (strcasecmp("Metrics", child->key) == 0)
688       cf_util_get_boolean(child, &cb->send_metrics);
689     else if (strcasecmp("Notifications", child->key) == 0)
690       cf_util_get_boolean(child, &cb->send_notifications);
691     else if (strcasecmp("StoreRates", child->key) == 0)
692       status = cf_util_get_boolean(child, &cb->store_rates);
693     else if (strcasecmp("BufferSize", child->key) == 0)
694       status = cf_util_get_int(child, &buffer_size);
695     else if (strcasecmp("LowSpeedLimit", child->key) == 0)
696       status = cf_util_get_int(child, &cb->low_speed_limit);
697     else if (strcasecmp("Timeout", child->key) == 0)
698       status = cf_util_get_int(child, &cb->timeout);
699     else if (strcasecmp("LogHttpError", child->key) == 0)
700       status = cf_util_get_boolean(child, &cb->log_http_error);
701     else if (strcasecmp("Header", child->key) == 0)
702       status = wh_config_append_string("Header", &cb->headers, child);
703     else {
704       ERROR("write_http plugin: Invalid configuration "
705             "option: %s.",
706             child->key);
707       status = EINVAL;
708     }
709
710     if (status != 0)
711       break;
712   }
713
714   if (status != 0) {
715     wh_callback_free(cb);
716     return (status);
717   }
718
719   if (cb->location == NULL) {
720     ERROR("write_http plugin: no URL defined for instance '%s'", cb->name);
721     wh_callback_free(cb);
722     return (-1);
723   }
724
725   if (!cb->send_metrics && !cb->send_notifications) {
726     ERROR("write_http plugin: Neither metrics nor notifications "
727           "are enabled for \"%s\".",
728           cb->name);
729     wh_callback_free(cb);
730     return (-1);
731   }
732
733   if (cb->low_speed_limit > 0)
734     cb->low_speed_time = CDTIME_T_TO_TIME_T(plugin_get_interval());
735
736   /* Determine send_buffer_size. */
737   cb->send_buffer_size = WRITE_HTTP_DEFAULT_BUFFER_SIZE;
738   if (buffer_size >= 1024)
739     cb->send_buffer_size = (size_t)buffer_size;
740   else if (buffer_size != 0)
741     ERROR("write_http plugin: Ignoring invalid BufferSize setting (%d).",
742           buffer_size);
743
744   /* Allocate the buffer. */
745   cb->send_buffer = malloc(cb->send_buffer_size);
746   if (cb->send_buffer == NULL) {
747     ERROR("write_http plugin: malloc(%zu) failed.", cb->send_buffer_size);
748     wh_callback_free(cb);
749     return (-1);
750   }
751   /* Nulls the buffer and sets ..._free and ..._fill. */
752   wh_reset_buffer(cb);
753
754   ssnprintf(callback_name, sizeof(callback_name), "write_http/%s", cb->name);
755   DEBUG("write_http: Registering write callback '%s' with URL '%s'",
756         callback_name, cb->location);
757
758   user_data_t user_data = {
759       .data = cb, .free_func = wh_callback_free,
760   };
761
762   if (cb->send_metrics) {
763     plugin_register_write(callback_name, wh_write, &user_data);
764     user_data.free_func = NULL;
765
766     plugin_register_flush(callback_name, wh_flush, &user_data);
767   }
768
769   if (cb->send_notifications) {
770     plugin_register_notification(callback_name, wh_notify, &user_data);
771     user_data.free_func = NULL;
772   }
773
774   return (0);
775 } /* }}} int wh_config_node */
776
777 static int wh_config(oconfig_item_t *ci) /* {{{ */
778 {
779   for (int i = 0; i < ci->children_num; i++) {
780     oconfig_item_t *child = ci->children + i;
781
782     if (strcasecmp("Node", child->key) == 0)
783       wh_config_node(child);
784     /* FIXME: Remove this legacy mode in version 6. */
785     else if (strcasecmp("URL", child->key) == 0) {
786       WARNING("write_http plugin: Legacy <URL> block found. "
787               "Please use <Node> instead.");
788       wh_config_node(child);
789     } else {
790       ERROR("write_http plugin: Invalid configuration "
791             "option: %s.",
792             child->key);
793     }
794   }
795
796   return (0);
797 } /* }}} int wh_config */
798
799 static int wh_init(void) /* {{{ */
800 {
801   /* Call this while collectd is still single-threaded to avoid
802    * initialization issues in libgcrypt. */
803   curl_global_init(CURL_GLOBAL_SSL);
804   return (0);
805 } /* }}} int wh_init */
806
807 void module_register(void) /* {{{ */
808 {
809   plugin_register_complex_config("write_http", wh_config);
810   plugin_register_init("write_http", wh_init);
811 } /* }}} void module_register */
812
813 /* vim: set fdm=marker sw=8 ts=8 tw=78 et : */