openldap: check ld structure before passing it to ldap_unbind()
[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   int state;
51   _Bool starttls;
52   int timeout;
53   char *url;
54   _Bool verifyhost;
55   int version;
56
57   LDAP *ld;
58 };
59 typedef struct cldap_s cldap_t; /* }}} */
60
61 static cldap_t **databases = NULL;
62 static size_t databases_num = 0;
63
64 static void cldap_free(cldap_t *st) /* {{{ */
65 {
66   if (st == NULL)
67     return;
68
69   sfree(st->binddn);
70   sfree(st->password);
71   sfree(st->cacert);
72   sfree(st->host);
73   sfree(st->name);
74   sfree(st->url);
75   if (st->ld)
76     ldap_memfree(st->ld);
77   sfree(st);
78 } /* }}} void cldap_free */
79
80 /* initialize ldap for each host */
81 static int cldap_init_host(cldap_t *st) /* {{{ */
82 {
83   LDAP *ld;
84   int rc;
85
86   if (st->state && st->ld) {
87     DEBUG("openldap plugin: Already connected to %s", st->url);
88     return (0);
89   }
90
91   rc = ldap_initialize(&ld, st->url);
92   if (rc != LDAP_SUCCESS) {
93     ERROR("openldap plugin: ldap_initialize failed: %s", ldap_err2string(rc));
94     st->state = 0;
95     if (ld != NULL)
96       ldap_unbind_ext_s(ld, NULL, NULL);
97     return (-1);
98   }
99
100   st->ld = ld;
101
102   ldap_set_option(st->ld, LDAP_OPT_PROTOCOL_VERSION, &st->version);
103
104   ldap_set_option(st->ld, LDAP_OPT_TIMEOUT,
105                   &(const struct timeval){st->timeout, 0});
106
107   ldap_set_option(st->ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
108
109   if (st->cacert != NULL)
110     ldap_set_option(st->ld, LDAP_OPT_X_TLS_CACERTFILE, st->cacert);
111
112   if (st->verifyhost == 0) {
113     int never = LDAP_OPT_X_TLS_NEVER;
114     ldap_set_option(st->ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &never);
115   }
116
117   if (st->starttls != 0) {
118     rc = ldap_start_tls_s(ld, NULL, NULL);
119     if (rc != LDAP_SUCCESS) {
120       ERROR("openldap plugin: Failed to start tls on %s: %s", st->url,
121             ldap_err2string(rc));
122       st->state = 0;
123       if (st->ld != NULL)
124         ldap_unbind_ext_s(st->ld, NULL, NULL);
125       return (-1);
126     }
127   }
128
129   struct berval cred;
130   if (st->password != NULL) {
131     cred.bv_val = st->password;
132     cred.bv_len = strlen(st->password);
133   } else {
134     cred.bv_val = "";
135     cred.bv_len = 0;
136   }
137
138   rc = ldap_sasl_bind_s(st->ld, st->binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL,
139                         NULL);
140   if (rc != LDAP_SUCCESS) {
141     ERROR("openldap plugin: Failed to bind to %s: %s", st->url,
142           ldap_err2string(rc));
143     st->state = 0;
144     if (st->ld != NULL)
145       ldap_unbind_ext_s(st->ld, NULL, NULL);
146     return (-1);
147   } else {
148     DEBUG("openldap plugin: Successfully connected to %s", st->url);
149     st->state = 1;
150     return (0);
151   }
152 } /* }}} static cldap_init_host */
153
154 static void cldap_submit_value(const char *type,
155                                const char *type_instance, /* {{{ */
156                                value_t value, cldap_t *st) {
157   value_list_t vl = VALUE_LIST_INIT;
158
159   vl.values = &value;
160   vl.values_len = 1;
161
162   if ((st->host == NULL) || (strcmp("", st->host) == 0) ||
163       (strcmp("localhost", st->host) == 0))
164     sstrncpy(vl.host, hostname_g, sizeof(vl.host));
165   else
166     sstrncpy(vl.host, st->host, sizeof(vl.host));
167
168   sstrncpy(vl.plugin, "openldap", sizeof(vl.plugin));
169   if (st->name != NULL)
170     sstrncpy(vl.plugin_instance, st->name, sizeof(vl.plugin_instance));
171
172   sstrncpy(vl.type, type, sizeof(vl.type));
173   if (type_instance != NULL)
174     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
175
176   plugin_dispatch_values(&vl);
177 } /* }}} void cldap_submit_value */
178
179 static void cldap_submit_derive(const char *type,
180                                 const char *type_instance, /* {{{ */
181                                 derive_t d, cldap_t *st) {
182   value_t v;
183   v.derive = d;
184   cldap_submit_value(type, type_instance, v, st);
185 } /* }}} void cldap_submit_derive */
186
187 static void cldap_submit_gauge(const char *type,
188                                const char *type_instance, /* {{{ */
189                                gauge_t g, cldap_t *st) {
190   value_t v;
191   v.gauge = g;
192   cldap_submit_value(type, type_instance, v, st);
193 } /* }}} void cldap_submit_gauge */
194
195 static int cldap_read_host(user_data_t *ud) /* {{{ */
196 {
197   cldap_t *st;
198   LDAPMessage *result;
199   char *dn;
200   int rc;
201   int status;
202
203   char *attrs[9] = {
204       "monitorCounter", "monitorOpCompleted", "monitorOpInitiated",
205       "monitoredInfo",  "olmBDBEntryCache",   "olmBDBDNCache",
206       "olmBDBIDLCache", "namingContexts",     NULL};
207
208   if ((ud == NULL) || (ud->data == NULL)) {
209     ERROR("openldap plugin: cldap_read_host: Invalid user data.");
210     return (-1);
211   }
212
213   st = (cldap_t *)ud->data;
214
215   status = cldap_init_host(st);
216   if (status != 0)
217     return (-1);
218
219   rc = ldap_search_ext_s(st->ld, "cn=Monitor", LDAP_SCOPE_SUBTREE,
220                          "(|(!(cn=* *))(cn=Database*))", attrs, 0, NULL, NULL,
221                          NULL, 0, &result);
222
223   if (rc != LDAP_SUCCESS) {
224     ERROR("openldap plugin: Failed to execute search: %s", ldap_err2string(rc));
225     ldap_msgfree(result);
226     st->state = 0;
227     if (st->ld != NULL)
228       ldap_unbind_ext_s(st->ld, NULL, NULL);
229     return (-1);
230   }
231
232   for (LDAPMessage *e = ldap_first_entry(st->ld, result); e != NULL;
233        e = ldap_next_entry(st->ld, e)) {
234     if ((dn = ldap_get_dn(st->ld, e)) != NULL) {
235       unsigned long long counter = 0;
236       unsigned long long opc = 0;
237       unsigned long long opi = 0;
238       unsigned long long info = 0;
239
240       struct berval counter_data;
241       struct berval opc_data;
242       struct berval opi_data;
243       struct berval info_data;
244       struct berval olmbdb_data;
245       struct berval nc_data;
246
247       struct berval **counter_list;
248       struct berval **opc_list;
249       struct berval **opi_list;
250       struct berval **info_list;
251       struct berval **olmbdb_list;
252       struct berval **nc_list;
253
254       if ((counter_list = ldap_get_values_len(st->ld, e, "monitorCounter")) !=
255           NULL) {
256         counter_data = *counter_list[0];
257         counter = atoll(counter_data.bv_val);
258       }
259
260       if ((opc_list = ldap_get_values_len(st->ld, e, "monitorOpCompleted")) !=
261           NULL) {
262         opc_data = *opc_list[0];
263         opc = atoll(opc_data.bv_val);
264       }
265
266       if ((opi_list = ldap_get_values_len(st->ld, e, "monitorOpInitiated")) !=
267           NULL) {
268         opi_data = *opi_list[0];
269         opi = atoll(opi_data.bv_val);
270       }
271
272       if ((info_list = ldap_get_values_len(st->ld, e, "monitoredInfo")) !=
273           NULL) {
274         info_data = *info_list[0];
275         info = atoll(info_data.bv_val);
276       }
277
278       if (strcmp(dn, "cn=Total,cn=Connections,cn=Monitor") == 0) {
279         cldap_submit_derive("total_connections", NULL, counter, st);
280       } else if (strcmp(dn, "cn=Current,cn=Connections,cn=Monitor") == 0) {
281         cldap_submit_gauge("current_connections", NULL, counter, st);
282       } else if (strcmp(dn, "cn=Operations,cn=Monitor") == 0) {
283         cldap_submit_derive("operations", "completed", opc, st);
284         cldap_submit_derive("operations", "initiated", opi, st);
285       } else if (strcmp(dn, "cn=Bind,cn=Operations,cn=Monitor") == 0) {
286         cldap_submit_derive("operations", "bind-completed", opc, st);
287         cldap_submit_derive("operations", "bind-initiated", opi, st);
288       } else if (strcmp(dn, "cn=UnBind,cn=Operations,cn=Monitor") == 0) {
289         cldap_submit_derive("operations", "unbind-completed", opc, st);
290         cldap_submit_derive("operations", "unbind-initiated", opi, st);
291       } else if (strcmp(dn, "cn=Search,cn=Operations,cn=Monitor") == 0) {
292         cldap_submit_derive("operations", "search-completed", opc, st);
293         cldap_submit_derive("operations", "search-initiated", opi, st);
294       } else if (strcmp(dn, "cn=Compare,cn=Operations,cn=Monitor") == 0) {
295         cldap_submit_derive("operations", "compare-completed", opc, st);
296         cldap_submit_derive("operations", "compare-initiated", opi, st);
297       } else if (strcmp(dn, "cn=Modify,cn=Operations,cn=Monitor") == 0) {
298         cldap_submit_derive("operations", "modify-completed", opc, st);
299         cldap_submit_derive("operations", "modify-initiated", opi, st);
300       } else if (strcmp(dn, "cn=Modrdn,cn=Operations,cn=Monitor") == 0) {
301         cldap_submit_derive("operations", "modrdn-completed", opc, st);
302         cldap_submit_derive("operations", "modrdn-initiated", opi, st);
303       } else if (strcmp(dn, "cn=Add,cn=Operations,cn=Monitor") == 0) {
304         cldap_submit_derive("operations", "add-completed", opc, st);
305         cldap_submit_derive("operations", "add-initiated", opi, st);
306       } else if (strcmp(dn, "cn=Delete,cn=Operations,cn=Monitor") == 0) {
307         cldap_submit_derive("operations", "delete-completed", opc, st);
308         cldap_submit_derive("operations", "delete-initiated", opi, st);
309       } else if (strcmp(dn, "cn=Abandon,cn=Operations,cn=Monitor") == 0) {
310         cldap_submit_derive("operations", "abandon-completed", opc, st);
311         cldap_submit_derive("operations", "abandon-initiated", opi, st);
312       } else if (strcmp(dn, "cn=Extended,cn=Operations,cn=Monitor") == 0) {
313         cldap_submit_derive("operations", "extended-completed", opc, st);
314         cldap_submit_derive("operations", "extended-initiated", opi, st);
315       } else if ((strncmp(dn, "cn=Database", 11) == 0) &&
316                  ((nc_list = ldap_get_values_len(st->ld, e,
317                                                  "namingContexts")) != NULL)) {
318         nc_data = *nc_list[0];
319         char typeinst[DATA_MAX_NAME_LEN];
320
321         if ((olmbdb_list =
322                  ldap_get_values_len(st->ld, e, "olmBDBEntryCache")) != NULL) {
323           olmbdb_data = *olmbdb_list[0];
324           ssnprintf(typeinst, sizeof(typeinst), "bdbentrycache-%s",
325                     nc_data.bv_val);
326           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
327                              st);
328           ldap_value_free_len(olmbdb_list);
329         }
330
331         if ((olmbdb_list = ldap_get_values_len(st->ld, e, "olmBDBDNCache")) !=
332             NULL) {
333           olmbdb_data = *olmbdb_list[0];
334           ssnprintf(typeinst, sizeof(typeinst), "bdbdncache-%s",
335                     nc_data.bv_val);
336           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
337                              st);
338           ldap_value_free_len(olmbdb_list);
339         }
340
341         if ((olmbdb_list = ldap_get_values_len(st->ld, e, "olmBDBIDLCache")) !=
342             NULL) {
343           olmbdb_data = *olmbdb_list[0];
344           ssnprintf(typeinst, sizeof(typeinst), "bdbidlcache-%s",
345                     nc_data.bv_val);
346           cldap_submit_gauge("cache_size", typeinst, atoll(olmbdb_data.bv_val),
347                              st);
348           ldap_value_free_len(olmbdb_list);
349         }
350
351         ldap_value_free_len(nc_list);
352       } else if (strcmp(dn, "cn=Bytes,cn=Statistics,cn=Monitor") == 0) {
353         cldap_submit_derive("derive", "statistics-bytes", counter, st);
354       } else if (strcmp(dn, "cn=PDU,cn=Statistics,cn=Monitor") == 0) {
355         cldap_submit_derive("derive", "statistics-pdu", counter, st);
356       } else if (strcmp(dn, "cn=Entries,cn=Statistics,cn=Monitor") == 0) {
357         cldap_submit_derive("derive", "statistics-entries", counter, st);
358       } else if (strcmp(dn, "cn=Referrals,cn=Statistics,cn=Monitor") == 0) {
359         cldap_submit_derive("derive", "statistics-referrals", counter, st);
360       } else if (strcmp(dn, "cn=Open,cn=Threads,cn=Monitor") == 0) {
361         cldap_submit_gauge("threads", "threads-open", info, st);
362       } else if (strcmp(dn, "cn=Starting,cn=Threads,cn=Monitor") == 0) {
363         cldap_submit_gauge("threads", "threads-starting", info, st);
364       } else if (strcmp(dn, "cn=Active,cn=Threads,cn=Monitor") == 0) {
365         cldap_submit_gauge("threads", "threads-active", info, st);
366       } else if (strcmp(dn, "cn=Pending,cn=Threads,cn=Monitor") == 0) {
367         cldap_submit_gauge("threads", "threads-pending", info, st);
368       } else if (strcmp(dn, "cn=Backload,cn=Threads,cn=Monitor") == 0) {
369         cldap_submit_gauge("threads", "threads-backload", info, st);
370       } else if (strcmp(dn, "cn=Read,cn=Waiters,cn=Monitor") == 0) {
371         cldap_submit_derive("derive", "waiters-read", counter, st);
372       } else if (strcmp(dn, "cn=Write,cn=Waiters,cn=Monitor") == 0) {
373         cldap_submit_derive("derive", "waiters-write", counter, st);
374       }
375
376       ldap_value_free_len(counter_list);
377       ldap_value_free_len(opc_list);
378       ldap_value_free_len(opi_list);
379       ldap_value_free_len(info_list);
380     }
381
382     ldap_memfree(dn);
383   }
384
385   ldap_msgfree(result);
386   return (0);
387 } /* }}} int cldap_read_host */
388
389 /* Configuration handling functions {{{
390  *
391  * <Plugin ldap>
392  *   <Instance "plugin_instance1">
393  *     URL "ldap://localhost"
394  *     ...
395  *   </Instance>
396  * </Plugin>
397  */
398
399 static int cldap_config_add(oconfig_item_t *ci) /* {{{ */
400 {
401   cldap_t *st;
402   int status;
403
404   st = calloc(1, sizeof(*st));
405   if (st == NULL) {
406     ERROR("openldap plugin: calloc failed.");
407     return (-1);
408   }
409
410   status = cf_util_get_string(ci, &st->name);
411   if (status != 0) {
412     sfree(st);
413     return (status);
414   }
415
416   st->starttls = 0;
417   st->timeout = (long)(CDTIME_T_TO_MS(plugin_get_interval()) / 1000);
418   st->verifyhost = 1;
419   st->version = LDAP_VERSION3;
420
421   for (int i = 0; i < ci->children_num; i++) {
422     oconfig_item_t *child = ci->children + i;
423
424     if (strcasecmp("BindDN", child->key) == 0)
425       status = cf_util_get_string(child, &st->binddn);
426     else if (strcasecmp("Password", child->key) == 0)
427       status = cf_util_get_string(child, &st->password);
428     else if (strcasecmp("CACert", child->key) == 0)
429       status = cf_util_get_string(child, &st->cacert);
430     else if (strcasecmp("StartTLS", child->key) == 0)
431       status = cf_util_get_boolean(child, &st->starttls);
432     else if (strcasecmp("Timeout", child->key) == 0)
433       status = cf_util_get_int(child, &st->timeout);
434     else if (strcasecmp("URL", child->key) == 0)
435       status = cf_util_get_string(child, &st->url);
436     else if (strcasecmp("VerifyHost", child->key) == 0)
437       status = cf_util_get_boolean(child, &st->verifyhost);
438     else if (strcasecmp("Version", child->key) == 0)
439       status = cf_util_get_int(child, &st->version);
440     else {
441       WARNING("openldap plugin: Option `%s' not allowed here.", child->key);
442       status = -1;
443     }
444
445     if (status != 0)
446       break;
447   }
448
449   /* Check if struct is complete.. */
450   if ((status == 0) && (st->url == NULL)) {
451     ERROR("openldap plugin: Instance `%s': "
452           "No URL has been configured.",
453           st->name);
454     status = -1;
455   }
456
457   /* Check if URL is valid */
458   if ((status == 0) && (st->url != NULL)) {
459     LDAPURLDesc *ludpp;
460
461     if (ldap_url_parse(st->url, &ludpp) != 0) {
462       ERROR("openldap plugin: Instance `%s': "
463             "Invalid URL: `%s'",
464             st->name, st->url);
465       status = -1;
466     }
467
468     if ((status == 0) && (ludpp->lud_host != NULL))
469       st->host = strdup(ludpp->lud_host);
470
471     ldap_free_urldesc(ludpp);
472   }
473
474   if (status == 0) {
475     cldap_t **temp;
476
477     temp = (cldap_t **)realloc(databases,
478                                sizeof(*databases) * (databases_num + 1));
479
480     if (temp == NULL) {
481       ERROR("openldap plugin: realloc failed");
482       status = -1;
483     } else {
484       char callback_name[3 * DATA_MAX_NAME_LEN] = {0};
485
486       databases = temp;
487       databases[databases_num] = st;
488       databases_num++;
489
490       ssnprintf(callback_name, sizeof(callback_name), "openldap/%s/%s",
491                 (st->host != NULL) ? st->host : hostname_g,
492                 (st->name != NULL) ? st->name : "default");
493
494       user_data_t ud = {.data = st};
495
496       status = plugin_register_complex_read(/* group = */ NULL,
497                                             /* name      = */ callback_name,
498                                             /* callback  = */ cldap_read_host,
499                                             /* interval  = */ 0,
500                                             /* user_data = */ &ud);
501     }
502   }
503
504   if (status != 0) {
505     cldap_free(st);
506     return (-1);
507   }
508
509   return (0);
510 } /* }}} int cldap_config_add */
511
512 static int cldap_config(oconfig_item_t *ci) /* {{{ */
513 {
514   int status = 0;
515
516   for (int i = 0; i < ci->children_num; i++) {
517     oconfig_item_t *child = ci->children + i;
518
519     if (strcasecmp("Instance", child->key) == 0)
520       cldap_config_add(child);
521     else
522       WARNING("openldap plugin: The configuration option "
523               "\"%s\" is not allowed here. Did you "
524               "forget to add an <Instance /> block "
525               "around the configuration?",
526               child->key);
527   } /* for (ci->children) */
528
529   return (status);
530 } /* }}} int cldap_config */
531
532 /* }}} End of configuration handling functions */
533
534 static int cldap_init(void) /* {{{ */
535 {
536   /* Initialize LDAP library while still single-threaded as recommended in
537    * ldap_initialize(3) */
538   int debug_level;
539   ldap_get_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug_level);
540   return (0);
541 } /* }}} int cldap_init */
542
543 static int cldap_shutdown(void) /* {{{ */
544 {
545   for (size_t i = 0; i < databases_num; i++)
546     if (databases[i]->ld != NULL)
547       ldap_unbind_ext_s(databases[i]->ld, NULL, NULL);
548   sfree(databases);
549   databases_num = 0;
550
551   return (0);
552 } /* }}} int cldap_shutdown */
553
554 void module_register(void) /* {{{ */
555 {
556   plugin_register_complex_config("openldap", cldap_config);
557   plugin_register_init("openldap", cldap_init);
558   plugin_register_shutdown("openldap", cldap_shutdown);
559 } /* }}} void module_register */
560
561 #if defined(__APPLE__)
562 #pragma clang diagnostic pop
563 #endif