Merge branch 'collectd-5.7' into collectd-5.8
[collectd.git] / src / openldap.c
1 /**
2  * collectd - src/openldap.c
3  * Copyright (C) 2011       Kimo Rosenbaum
4  * Copyright (C) 2014-2015  Marc Fournier
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *
24  * Authors:
25  *   Kimo Rosenbaum <kimor79 at yahoo.com>
26  *   Marc Fournier <marc.fournier at camptocamp.com>
27  **/
28
29 #include "collectd.h"
30
31 #include "common.h"
32 #include "plugin.h"
33
34 #if defined(__APPLE__)
35 #pragma clang diagnostic push
36 #pragma clang diagnostic warning "-Wdeprecated-declarations"
37 #endif
38
39 #include <lber.h>
40 #include <ldap.h>
41
42 struct cldap_s /* {{{ */
43 {
44   char *name;
45
46   char *binddn;
47   char *password;
48   char *cacert;
49   char *host;
50   _Bool starttls;
51   int timeout;
52   char *url;
53   _Bool verifyhost;
54   int version;
55
56   LDAP *ld;
57 };
58 typedef struct cldap_s cldap_t; /* }}} */
59
60 static void cldap_free(void *arg) /* {{{ */
61 {
62   cldap_t *st = arg;
63
64   if (st == NULL)
65     return;
66
67   sfree(st->binddn);
68   sfree(st->password);
69   sfree(st->cacert);
70   sfree(st->host);
71   sfree(st->name);
72   sfree(st->url);
73   if (st->ld)
74     ldap_unbind_ext_s(st->ld, NULL, NULL);
75
76   sfree(st);
77 } /* }}} void cldap_free */
78
79 /* initialize ldap for each host */
80 static int cldap_init_host(cldap_t *st) /* {{{ */
81 {
82   int rc;
83
84   if (st->ld) {
85     DEBUG("openldap plugin: Already connected to %s", st->url);
86     return 0;
87   }
88
89   rc = ldap_initialize(&st->ld, st->url);
90   if (rc != LDAP_SUCCESS) {
91     ERROR("openldap plugin: ldap_initialize failed: %s", ldap_err2string(rc));
92     if (st->ld != NULL)
93       ldap_unbind_ext_s(st->ld, NULL, NULL);
94     st->ld = NULL;
95     return (-1);
96   }
97
98   ldap_set_option(st->ld, LDAP_OPT_PROTOCOL_VERSION, &st->version);
99
100   ldap_set_option(st->ld, LDAP_OPT_TIMEOUT,
101                   &(const struct timeval){st->timeout, 0});
102
103   ldap_set_option(st->ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
104
105   if (st->cacert != NULL)
106     ldap_set_option(st->ld, LDAP_OPT_X_TLS_CACERTFILE, st->cacert);
107
108   if (st->verifyhost == 0) {
109     int never = LDAP_OPT_X_TLS_NEVER;
110     ldap_set_option(st->ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &never);
111   }
112
113   if (st->starttls != 0) {
114     rc = ldap_start_tls_s(st->ld, NULL, NULL);
115     if (rc != LDAP_SUCCESS) {
116       ERROR("openldap plugin: Failed to start tls on %s: %s", st->url,
117             ldap_err2string(rc));
118       ldap_unbind_ext_s(st->ld, NULL, NULL);
119       st->ld = NULL;
120       return (-1);
121     }
122   }
123
124   struct berval cred;
125   if (st->password != NULL) {
126     cred.bv_val = st->password;
127     cred.bv_len = strlen(st->password);
128   } else {
129     cred.bv_val = "";
130     cred.bv_len = 0;
131   }
132
133   rc = ldap_sasl_bind_s(st->ld, st->binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL,
134                         NULL);
135   if (rc != LDAP_SUCCESS) {
136     ERROR("openldap plugin: Failed to bind to %s: %s", st->url,
137           ldap_err2string(rc));
138     ldap_unbind_ext_s(st->ld, NULL, NULL);
139     st->ld = NULL;
140     return (-1);
141   } else {
142     DEBUG("openldap plugin: Successfully connected to %s", st->url);
143     return 0;
144   }
145 } /* }}} static cldap_init_host */
146
147 static void cldap_submit_value(const char *type,
148                                const char *type_instance, /* {{{ */
149                                value_t value, cldap_t *st) {
150   value_list_t vl = VALUE_LIST_INIT;
151
152   vl.values = &value;
153   vl.values_len = 1;
154
155   if ((st->host != NULL) && (strcmp("localhost", st->host) != 0))
156     sstrncpy(vl.host, st->host, sizeof(vl.host));
157
158   sstrncpy(vl.plugin, "openldap", sizeof(vl.plugin));
159   if (st->name != NULL)
160     sstrncpy(vl.plugin_instance, st->name, sizeof(vl.plugin_instance));
161
162   sstrncpy(vl.type, type, sizeof(vl.type));
163   if (type_instance != NULL)
164     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
165
166   plugin_dispatch_values(&vl);
167 } /* }}} void cldap_submit_value */
168
169 static void cldap_submit_derive(const char *type,
170                                 const char *type_instance, /* {{{ */
171                                 derive_t d, cldap_t *st) {
172   cldap_submit_value(type, type_instance, (value_t){.derive = d}, st);
173 } /* }}} void cldap_submit_derive */
174
175 static void cldap_submit_gauge(const char *type,
176                                const char *type_instance, /* {{{ */
177                                gauge_t g, cldap_t *st) {
178   cldap_submit_value(type, type_instance, (value_t){.gauge = g}, st);
179 } /* }}} void cldap_submit_gauge */
180
181 static int cldap_read_host(user_data_t *ud) /* {{{ */
182 {
183   cldap_t *st;
184   LDAPMessage *result;
185   char *dn;
186   int rc;
187   int status;
188
189   char *attrs[9] = {
190       "monitorCounter", "monitorOpCompleted", "monitorOpInitiated",
191       "monitoredInfo",  "olmBDBEntryCache",   "olmBDBDNCache",
192       "olmBDBIDLCache", "namingContexts",     NULL};
193
194   if ((ud == NULL) || (ud->data == NULL)) {
195     ERROR("openldap plugin: cldap_read_host: Invalid user data.");
196     return -1;
197   }
198
199   st = (cldap_t *)ud->data;
200
201   status = cldap_init_host(st);
202   if (status != 0)
203     return -1;
204
205   rc = ldap_search_ext_s(st->ld, "cn=Monitor", LDAP_SCOPE_SUBTREE,
206                          "(|(!(cn=* *))(cn=Database*))", attrs, 0, NULL, NULL,
207                          NULL, 0, &result);
208
209   if (rc != LDAP_SUCCESS) {
210     ERROR("openldap plugin: Failed to execute search: %s", ldap_err2string(rc));
211     ldap_msgfree(result);
212     ldap_unbind_ext_s(st->ld, NULL, NULL);
213     st->ld = NULL;
214     return (-1);
215   }
216
217   for (LDAPMessage *e = ldap_first_entry(st->ld, result); e != NULL;
218        e = ldap_next_entry(st->ld, e)) {
219     if ((dn = ldap_get_dn(st->ld, e)) != NULL) {
220       unsigned long long counter = 0;
221       unsigned long long opc = 0;
222       unsigned long long opi = 0;
223       unsigned long long info = 0;
224
225       struct berval counter_data;
226       struct berval opc_data;
227       struct berval opi_data;
228       struct berval info_data;
229       struct berval olmbdb_data;
230       struct berval nc_data;
231
232       struct berval **counter_list;
233       struct berval **opc_list;
234       struct berval **opi_list;
235       struct berval **info_list;
236       struct berval **olmbdb_list;
237       struct berval **nc_list;
238
239       if ((counter_list = ldap_get_values_len(st->ld, e, "monitorCounter")) !=
240           NULL) {
241         counter_data = *counter_list[0];
242         counter = atoll(counter_data.bv_val);
243       }
244
245       if ((opc_list = ldap_get_values_len(st->ld, e, "monitorOpCompleted")) !=
246           NULL) {
247         opc_data = *opc_list[0];
248         opc = atoll(opc_data.bv_val);
249       }
250
251       if ((opi_list = ldap_get_values_len(st->ld, e, "monitorOpInitiated")) !=
252           NULL) {
253         opi_data = *opi_list[0];
254         opi = atoll(opi_data.bv_val);
255       }
256
257       if ((info_list = ldap_get_values_len(st->ld, e, "monitoredInfo")) !=
258           NULL) {
259         info_data = *info_list[0];
260         info = atoll(info_data.bv_val);
261       }
262
263       if (strcmp(dn, "cn=Total,cn=Connections,cn=Monitor") == 0) {
264         cldap_submit_derive("total_connections", NULL, counter, st);
265       } else if (strcmp(dn, "cn=Current,cn=Connections,cn=Monitor") == 0) {
266         cldap_submit_gauge("current_connections", NULL, counter, st);
267       } else if (strcmp(dn, "cn=Operations,cn=Monitor") == 0) {
268         cldap_submit_derive("operations", "completed", opc, st);
269         cldap_submit_derive("operations", "initiated", opi, st);
270       } else if (strcmp(dn, "cn=Bind,cn=Operations,cn=Monitor") == 0) {
271         cldap_submit_derive("operations", "bind-completed", opc, st);
272         cldap_submit_derive("operations", "bind-initiated", opi, st);
273       } else if (strcmp(dn, "cn=UnBind,cn=Operations,cn=Monitor") == 0) {
274         cldap_submit_derive("operations", "unbind-completed", opc, st);
275         cldap_submit_derive("operations", "unbind-initiated", opi, st);
276       } else if (strcmp(dn, "cn=Search,cn=Operations,cn=Monitor") == 0) {
277         cldap_submit_derive("operations", "search-completed", opc, st);
278         cldap_submit_derive("operations", "search-initiated", opi, st);
279       } else if (strcmp(dn, "cn=Compare,cn=Operations,cn=Monitor") == 0) {
280         cldap_submit_derive("operations", "compare-completed", opc, st);
281         cldap_submit_derive("operations", "compare-initiated", opi, st);
282       } else if (strcmp(dn, "cn=Modify,cn=Operations,cn=Monitor") == 0) {
283         cldap_submit_derive("operations", "modify-completed", opc, st);
284         cldap_submit_derive("operations", "modify-initiated", opi, st);
285       } else if (strcmp(dn, "cn=Modrdn,cn=Operations,cn=Monitor") == 0) {
286         cldap_submit_derive("operations", "modrdn-completed", opc, st);
287         cldap_submit_derive("operations", "modrdn-initiated", opi, st);
288       } else if (strcmp(dn, "cn=Add,cn=Operations,cn=Monitor") == 0) {
289         cldap_submit_derive("operations", "add-completed", opc, st);
290         cldap_submit_derive("operations", "add-initiated", opi, st);
291       } else if (strcmp(dn, "cn=Delete,cn=Operations,cn=Monitor") == 0) {
292         cldap_submit_derive("operations", "delete-completed", opc, st);
293         cldap_submit_derive("operations", "delete-initiated", opi, st);
294       } else if (strcmp(dn, "cn=Abandon,cn=Operations,cn=Monitor") == 0) {
295         cldap_submit_derive("operations", "abandon-completed", opc, st);
296         cldap_submit_derive("operations", "abandon-initiated", opi, st);
297       } else if (strcmp(dn, "cn=Extended,cn=Operations,cn=Monitor") == 0) {
298         cldap_submit_derive("operations", "extended-completed", opc, st);
299         cldap_submit_derive("operations", "extended-initiated", opi, st);
300       } else if ((strncmp(dn, "cn=Database", 11) == 0) &&
301                  ((nc_list = ldap_get_values_len(st->ld, e,
302                                                  "namingContexts")) != NULL)) {
303         nc_data = *nc_list[0];
304         char typeinst[DATA_MAX_NAME_LEN];
305
306         if ((olmbdb_list =
307                  ldap_get_values_len(st->ld, e, "olmBDBEntryCache")) != NULL) {
308           olmbdb_data = *olmbdb_list[0];
309           snprintf(typeinst, sizeof(typeinst), "bdbentrycache-%s",
310                    nc_data.bv_val);
311           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
312                              st);
313           ldap_value_free_len(olmbdb_list);
314         }
315
316         if ((olmbdb_list = ldap_get_values_len(st->ld, e, "olmBDBDNCache")) !=
317             NULL) {
318           olmbdb_data = *olmbdb_list[0];
319           snprintf(typeinst, sizeof(typeinst), "bdbdncache-%s", nc_data.bv_val);
320           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
321                              st);
322           ldap_value_free_len(olmbdb_list);
323         }
324
325         if ((olmbdb_list = ldap_get_values_len(st->ld, e, "olmBDBIDLCache")) !=
326             NULL) {
327           olmbdb_data = *olmbdb_list[0];
328           snprintf(typeinst, sizeof(typeinst), "bdbidlcache-%s",
329                    nc_data.bv_val);
330           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
331                              st);
332           ldap_value_free_len(olmbdb_list);
333         }
334
335         ldap_value_free_len(nc_list);
336       } else if (strcmp(dn, "cn=Bytes,cn=Statistics,cn=Monitor") == 0) {
337         cldap_submit_derive("derive", "statistics-bytes", counter, st);
338       } else if (strcmp(dn, "cn=PDU,cn=Statistics,cn=Monitor") == 0) {
339         cldap_submit_derive("derive", "statistics-pdu", counter, st);
340       } else if (strcmp(dn, "cn=Entries,cn=Statistics,cn=Monitor") == 0) {
341         cldap_submit_derive("derive", "statistics-entries", counter, st);
342       } else if (strcmp(dn, "cn=Referrals,cn=Statistics,cn=Monitor") == 0) {
343         cldap_submit_derive("derive", "statistics-referrals", counter, st);
344       } else if (strcmp(dn, "cn=Open,cn=Threads,cn=Monitor") == 0) {
345         cldap_submit_gauge("threads", "threads-open", info, st);
346       } else if (strcmp(dn, "cn=Starting,cn=Threads,cn=Monitor") == 0) {
347         cldap_submit_gauge("threads", "threads-starting", info, st);
348       } else if (strcmp(dn, "cn=Active,cn=Threads,cn=Monitor") == 0) {
349         cldap_submit_gauge("threads", "threads-active", info, st);
350       } else if (strcmp(dn, "cn=Pending,cn=Threads,cn=Monitor") == 0) {
351         cldap_submit_gauge("threads", "threads-pending", info, st);
352       } else if (strcmp(dn, "cn=Backload,cn=Threads,cn=Monitor") == 0) {
353         cldap_submit_gauge("threads", "threads-backload", info, st);
354       } else if (strcmp(dn, "cn=Read,cn=Waiters,cn=Monitor") == 0) {
355         cldap_submit_derive("derive", "waiters-read", counter, st);
356       } else if (strcmp(dn, "cn=Write,cn=Waiters,cn=Monitor") == 0) {
357         cldap_submit_derive("derive", "waiters-write", counter, st);
358       }
359
360       ldap_value_free_len(counter_list);
361       ldap_value_free_len(opc_list);
362       ldap_value_free_len(opi_list);
363       ldap_value_free_len(info_list);
364     }
365
366     ldap_memfree(dn);
367   }
368
369   ldap_msgfree(result);
370   return 0;
371 } /* }}} int cldap_read_host */
372
373 /* Configuration handling functions {{{
374  *
375  * <Plugin ldap>
376  *   <Instance "plugin_instance1">
377  *     URL "ldap://localhost"
378  *     ...
379  *   </Instance>
380  * </Plugin>
381  */
382
383 static int cldap_config_add(oconfig_item_t *ci) /* {{{ */
384 {
385   cldap_t *st;
386   int status;
387
388   st = calloc(1, sizeof(*st));
389   if (st == NULL) {
390     ERROR("openldap plugin: calloc failed.");
391     return -1;
392   }
393
394   status = cf_util_get_string(ci, &st->name);
395   if (status != 0) {
396     sfree(st);
397     return status;
398   }
399
400   st->starttls = 0;
401   st->timeout = (long)CDTIME_T_TO_TIME_T(plugin_get_interval());
402   st->verifyhost = 1;
403   st->version = LDAP_VERSION3;
404
405   for (int i = 0; i < ci->children_num; i++) {
406     oconfig_item_t *child = ci->children + i;
407
408     if (strcasecmp("BindDN", child->key) == 0)
409       status = cf_util_get_string(child, &st->binddn);
410     else if (strcasecmp("Password", child->key) == 0)
411       status = cf_util_get_string(child, &st->password);
412     else if (strcasecmp("CACert", child->key) == 0)
413       status = cf_util_get_string(child, &st->cacert);
414     else if (strcasecmp("StartTLS", child->key) == 0)
415       status = cf_util_get_boolean(child, &st->starttls);
416     else if (strcasecmp("Timeout", child->key) == 0)
417       status = cf_util_get_int(child, &st->timeout);
418     else if (strcasecmp("URL", child->key) == 0)
419       status = cf_util_get_string(child, &st->url);
420     else if (strcasecmp("VerifyHost", child->key) == 0)
421       status = cf_util_get_boolean(child, &st->verifyhost);
422     else if (strcasecmp("Version", child->key) == 0)
423       status = cf_util_get_int(child, &st->version);
424     else {
425       WARNING("openldap plugin: Option `%s' not allowed here.", child->key);
426       status = -1;
427     }
428
429     if (status != 0)
430       break;
431   }
432
433   /* Check if struct is complete.. */
434   if ((status == 0) && (st->url == NULL)) {
435     ERROR("openldap plugin: Instance `%s': "
436           "No URL has been configured.",
437           st->name);
438     status = -1;
439   }
440
441   /* Check if URL is valid */
442   if ((status == 0) && (st->url != NULL)) {
443     LDAPURLDesc *ludpp;
444
445     if (ldap_url_parse(st->url, &ludpp) != 0) {
446       ERROR("openldap plugin: Instance `%s': "
447             "Invalid URL: `%s'",
448             st->name, st->url);
449       status = -1;
450     }
451
452     if ((status == 0) && (ludpp->lud_host != NULL))
453       st->host = strdup(ludpp->lud_host);
454
455     ldap_free_urldesc(ludpp);
456   }
457
458   if (status != 0) {
459     cldap_free(st);
460     return -1;
461   }
462
463   char callback_name[3 * DATA_MAX_NAME_LEN] = {0};
464
465   snprintf(callback_name, sizeof(callback_name), "openldap/%s/%s",
466            (st->host != NULL) ? st->host : hostname_g,
467            (st->name != NULL) ? st->name : "default");
468
469   return plugin_register_complex_read(/* group = */ NULL,
470                                       /* name      = */ callback_name,
471                                       /* callback  = */ cldap_read_host,
472                                       /* interval  = */ 0,
473                                       &(user_data_t){
474                                           .data = st, .free_func = cldap_free,
475                                       });
476 } /* }}} int cldap_config_add */
477
478 static int cldap_config(oconfig_item_t *ci) /* {{{ */
479 {
480   int status = 0;
481
482   for (int i = 0; i < ci->children_num; i++) {
483     oconfig_item_t *child = ci->children + i;
484
485     if (strcasecmp("Instance", child->key) == 0)
486       cldap_config_add(child);
487     else
488       WARNING("openldap plugin: The configuration option "
489               "\"%s\" is not allowed here. Did you "
490               "forget to add an <Instance /> block "
491               "around the configuration?",
492               child->key);
493   } /* for (ci->children) */
494
495   return status;
496 } /* }}} int cldap_config */
497
498 /* }}} End of configuration handling functions */
499
500 static int cldap_init(void) /* {{{ */
501 {
502   /* Initialize LDAP library while still single-threaded as recommended in
503    * ldap_initialize(3) */
504   int debug_level;
505   ldap_get_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug_level);
506   return 0;
507 } /* }}} int cldap_init */
508
509 void module_register(void) /* {{{ */
510 {
511   plugin_register_complex_config("openldap", cldap_config);
512   plugin_register_init("openldap", cldap_init);
513 } /* }}} void module_register */
514
515 #if defined(__APPLE__)
516 #pragma clang diagnostic pop
517 #endif