Merge branch 'collectd-5.7'
[collectd.git] / src / snmp.c
1 /**
2  * collectd - src/snmp.c
3  * Copyright (C) 2007-2012  Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_complain.h"
32
33 #include <net-snmp/net-snmp-config.h>
34 #include <net-snmp/net-snmp-includes.h>
35
36 #include <fnmatch.h>
37
38 /*
39  * Private data structes
40  */
41 struct oid_s {
42   oid oid[MAX_OID_LEN];
43   size_t oid_len;
44 };
45 typedef struct oid_s oid_t;
46
47 union instance_u {
48   char string[DATA_MAX_NAME_LEN];
49   oid_t oid;
50 };
51 typedef union instance_u instance_t;
52
53 struct data_definition_s {
54   char *name; /* used to reference this from the `Collect' option */
55   char *type; /* used to find the data_set */
56   _Bool is_table;
57   instance_t instance;
58   char *instance_prefix;
59   oid_t *values;
60   size_t values_len;
61   double scale;
62   double shift;
63   struct data_definition_s *next;
64   char **ignores;
65   size_t ignores_len;
66   int invert_match;
67 };
68 typedef struct data_definition_s data_definition_t;
69
70 struct host_definition_s {
71   char *name;
72   char *address;
73   int version;
74
75   /* snmpv1/2 options */
76   char *community;
77
78   /* snmpv3 security options */
79   char *username;
80   oid *auth_protocol;
81   size_t auth_protocol_len;
82   char *auth_passphrase;
83   oid *priv_protocol;
84   size_t priv_protocol_len;
85   char *priv_passphrase;
86   int security_level;
87   char *context;
88
89   void *sess_handle;
90   c_complain_t complaint;
91   cdtime_t interval;
92   data_definition_t **data_list;
93   int data_list_len;
94 };
95 typedef struct host_definition_s host_definition_t;
96
97 /* These two types are used to cache values in `csnmp_read_table' to handle
98  * gaps in tables. */
99 struct csnmp_list_instances_s {
100   oid_t suffix;
101   char instance[DATA_MAX_NAME_LEN];
102   struct csnmp_list_instances_s *next;
103 };
104 typedef struct csnmp_list_instances_s csnmp_list_instances_t;
105
106 struct csnmp_table_values_s {
107   oid_t suffix;
108   value_t value;
109   struct csnmp_table_values_s *next;
110 };
111 typedef struct csnmp_table_values_s csnmp_table_values_t;
112
113 /*
114  * Private variables
115  */
116 static data_definition_t *data_head = NULL;
117
118 /*
119  * Prototypes
120  */
121 static int csnmp_read_host(user_data_t *ud);
122
123 /*
124  * Private functions
125  */
126 static void csnmp_oid_init(oid_t *dst, oid const *src, size_t n) {
127   assert(n <= STATIC_ARRAY_SIZE(dst->oid));
128   memcpy(dst->oid, src, sizeof(*src) * n);
129   dst->oid_len = n;
130 }
131
132 static int csnmp_oid_compare(oid_t const *left, oid_t const *right) {
133   return snmp_oid_compare(left->oid, left->oid_len, right->oid,
134                           right->oid_len);
135 }
136
137 static int csnmp_oid_suffix(oid_t *dst, oid_t const *src, oid_t const *root) {
138   /* Make sure "src" is in "root"s subtree. */
139   if (src->oid_len <= root->oid_len)
140     return EINVAL;
141   if (snmp_oid_ncompare(root->oid, root->oid_len, src->oid, src->oid_len,
142                         /* n = */ root->oid_len) != 0)
143     return EINVAL;
144
145   memset(dst, 0, sizeof(*dst));
146   dst->oid_len = src->oid_len - root->oid_len;
147   memcpy(dst->oid, &src->oid[root->oid_len],
148          dst->oid_len * sizeof(dst->oid[0]));
149   return 0;
150 }
151
152 static int csnmp_oid_to_string(char *buffer, size_t buffer_size,
153                                oid_t const *o) {
154   char oid_str[MAX_OID_LEN][16];
155   char *oid_str_ptr[MAX_OID_LEN];
156
157   for (size_t i = 0; i < o->oid_len; i++) {
158     ssnprintf(oid_str[i], sizeof(oid_str[i]), "%lu", (unsigned long)o->oid[i]);
159     oid_str_ptr[i] = oid_str[i];
160   }
161
162   return strjoin(buffer, buffer_size, oid_str_ptr, o->oid_len, ".");
163 }
164
165 static void csnmp_host_close_session(host_definition_t *host) /* {{{ */
166 {
167   if (host->sess_handle == NULL)
168     return;
169
170   snmp_sess_close(host->sess_handle);
171   host->sess_handle = NULL;
172 } /* }}} void csnmp_host_close_session */
173
174 static void csnmp_host_definition_destroy(void *arg) /* {{{ */
175 {
176   host_definition_t *hd;
177
178   hd = arg;
179
180   if (hd == NULL)
181     return;
182
183   if (hd->name != NULL) {
184     DEBUG("snmp plugin: Destroying host definition for host `%s'.", hd->name);
185   }
186
187   csnmp_host_close_session(hd);
188
189   sfree(hd->name);
190   sfree(hd->address);
191   sfree(hd->community);
192   sfree(hd->username);
193   sfree(hd->auth_passphrase);
194   sfree(hd->priv_passphrase);
195   sfree(hd->context);
196   sfree(hd->data_list);
197
198   sfree(hd);
199 } /* }}} void csnmp_host_definition_destroy */
200
201 /* Many functions to handle the configuration. {{{ */
202 /* First there are many functions which do configuration stuff. It's a big
203  * bloated and messy, I'm afraid. */
204
205 /*
206  * Callgraph for the config stuff:
207  *  csnmp_config
208  *  +-> call_snmp_init_once
209  *  +-> csnmp_config_add_data
210  *  !   +-> csnmp_config_add_data_instance
211  *  !   +-> csnmp_config_add_data_instance_prefix
212  *  !   +-> csnmp_config_add_data_values
213  *  +-> csnmp_config_add_host
214  *      +-> csnmp_config_add_host_version
215  *      +-> csnmp_config_add_host_collect
216  *      +-> csnmp_config_add_host_auth_protocol
217  *      +-> csnmp_config_add_host_priv_protocol
218  *      +-> csnmp_config_add_host_security_level
219  */
220 static void call_snmp_init_once(void) {
221   static int have_init = 0;
222
223   if (have_init == 0)
224     init_snmp(PACKAGE_NAME);
225   have_init = 1;
226 } /* void call_snmp_init_once */
227
228 static int csnmp_config_add_data_instance(data_definition_t *dd,
229                                           oconfig_item_t *ci) {
230   char buffer[DATA_MAX_NAME_LEN];
231   int status;
232
233   status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
234   if (status != 0)
235     return status;
236
237   if (dd->is_table) {
238     /* Instance is an OID */
239     dd->instance.oid.oid_len = MAX_OID_LEN;
240
241     if (!read_objid(buffer, dd->instance.oid.oid, &dd->instance.oid.oid_len)) {
242       ERROR("snmp plugin: read_objid (%s) failed.", buffer);
243       return -1;
244     }
245   } else {
246     /* Instance is a simple string */
247     sstrncpy(dd->instance.string, buffer, sizeof(dd->instance.string));
248   }
249
250   return 0;
251 } /* int csnmp_config_add_data_instance */
252
253 static int csnmp_config_add_data_instance_prefix(data_definition_t *dd,
254                                                  oconfig_item_t *ci) {
255   int status;
256
257   if (!dd->is_table) {
258     WARNING("snmp plugin: data %s: InstancePrefix is ignored when `Table' "
259             "is set to `false'.",
260             dd->name);
261     return -1;
262   }
263
264   status = cf_util_get_string(ci, &dd->instance_prefix);
265   return status;
266 } /* int csnmp_config_add_data_instance_prefix */
267
268 static int csnmp_config_add_data_values(data_definition_t *dd,
269                                         oconfig_item_t *ci) {
270   if (ci->values_num < 1) {
271     WARNING("snmp plugin: `Values' needs at least one argument.");
272     return -1;
273   }
274
275   for (int i = 0; i < ci->values_num; i++)
276     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
277       WARNING("snmp plugin: `Values' needs only string argument.");
278       return -1;
279     }
280
281   sfree(dd->values);
282   dd->values_len = 0;
283   dd->values = malloc(sizeof(*dd->values) * ci->values_num);
284   if (dd->values == NULL)
285     return -1;
286   dd->values_len = (size_t)ci->values_num;
287
288   for (int i = 0; i < ci->values_num; i++) {
289     dd->values[i].oid_len = MAX_OID_LEN;
290
291     if (NULL == snmp_parse_oid(ci->values[i].value.string, dd->values[i].oid,
292                                &dd->values[i].oid_len)) {
293       ERROR("snmp plugin: snmp_parse_oid (%s) failed.",
294             ci->values[i].value.string);
295       free(dd->values);
296       dd->values = NULL;
297       dd->values_len = 0;
298       return -1;
299     }
300   }
301
302   return 0;
303 } /* int csnmp_config_add_data_instance */
304
305 static int csnmp_config_add_data_blacklist(data_definition_t *dd,
306                                            oconfig_item_t *ci) {
307   if (ci->values_num < 1)
308     return 0;
309
310   for (int i = 0; i < ci->values_num; i++) {
311     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
312       WARNING("snmp plugin: `Ignore' needs only string argument.");
313       return -1;
314     }
315   }
316
317   dd->ignores_len = 0;
318   dd->ignores = NULL;
319
320   for (int i = 0; i < ci->values_num; ++i) {
321     if (strarray_add(&(dd->ignores), &(dd->ignores_len),
322                      ci->values[i].value.string) != 0) {
323       ERROR("snmp plugin: Can't allocate memory");
324       strarray_free(dd->ignores, dd->ignores_len);
325       return ENOMEM;
326     }
327   }
328   return 0;
329 } /* int csnmp_config_add_data_blacklist */
330
331 static int csnmp_config_add_data_blacklist_match_inverted(data_definition_t *dd,
332                                                           oconfig_item_t *ci) {
333   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
334     WARNING("snmp plugin: `InvertMatch' needs exactly one boolean argument.");
335     return -1;
336   }
337
338   dd->invert_match = ci->values[0].value.boolean ? 1 : 0;
339
340   return 0;
341 } /* int csnmp_config_add_data_blacklist_match_inverted */
342
343 static int csnmp_config_add_data(oconfig_item_t *ci) {
344   data_definition_t *dd;
345   int status = 0;
346
347   dd = calloc(1, sizeof(*dd));
348   if (dd == NULL)
349     return -1;
350
351   status = cf_util_get_string(ci, &dd->name);
352   if (status != 0) {
353     free(dd);
354     return -1;
355   }
356
357   dd->scale = 1.0;
358   dd->shift = 0.0;
359
360   for (int i = 0; i < ci->children_num; i++) {
361     oconfig_item_t *option = ci->children + i;
362
363     if (strcasecmp("Type", option->key) == 0)
364       status = cf_util_get_string(option, &dd->type);
365     else if (strcasecmp("Table", option->key) == 0)
366       status = cf_util_get_boolean(option, &dd->is_table);
367     else if (strcasecmp("Instance", option->key) == 0)
368       status = csnmp_config_add_data_instance(dd, option);
369     else if (strcasecmp("InstancePrefix", option->key) == 0)
370       status = csnmp_config_add_data_instance_prefix(dd, option);
371     else if (strcasecmp("Values", option->key) == 0)
372       status = csnmp_config_add_data_values(dd, option);
373     else if (strcasecmp("Shift", option->key) == 0)
374       status = cf_util_get_double(option, &dd->shift);
375     else if (strcasecmp("Scale", option->key) == 0)
376       status = cf_util_get_double(option, &dd->scale);
377     else if (strcasecmp("Ignore", option->key) == 0)
378       status = csnmp_config_add_data_blacklist(dd, option);
379     else if (strcasecmp("InvertMatch", option->key) == 0)
380       status = csnmp_config_add_data_blacklist_match_inverted(dd, option);
381     else {
382       WARNING("snmp plugin: Option `%s' not allowed here.", option->key);
383       status = -1;
384     }
385
386     if (status != 0)
387       break;
388   } /* for (ci->children) */
389
390   while (status == 0) {
391     if (dd->type == NULL) {
392       WARNING("snmp plugin: `Type' not given for data `%s'", dd->name);
393       status = -1;
394       break;
395     }
396     if (dd->values == NULL) {
397       WARNING("snmp plugin: No `Value' given for data `%s'", dd->name);
398       status = -1;
399       break;
400     }
401
402     break;
403   } /* while (status == 0) */
404
405   if (status != 0) {
406     sfree(dd->name);
407     sfree(dd->instance_prefix);
408     sfree(dd->values);
409     sfree(dd->ignores);
410     sfree(dd);
411     return -1;
412   }
413
414   DEBUG("snmp plugin: dd = { name = %s, type = %s, is_table = %s, values_len = "
415         "%zu }",
416         dd->name, dd->type, (dd->is_table != 0) ? "true" : "false",
417         dd->values_len);
418
419   if (data_head == NULL)
420     data_head = dd;
421   else {
422     data_definition_t *last;
423     last = data_head;
424     while (last->next != NULL)
425       last = last->next;
426     last->next = dd;
427   }
428
429   return 0;
430 } /* int csnmp_config_add_data */
431
432 static int csnmp_config_add_host_version(host_definition_t *hd,
433                                          oconfig_item_t *ci) {
434   int version;
435
436   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
437     WARNING("snmp plugin: The `Version' config option needs exactly one number "
438             "argument.");
439     return -1;
440   }
441
442   version = (int)ci->values[0].value.number;
443   if ((version < 1) || (version > 3)) {
444     WARNING("snmp plugin: `Version' must either be `1', `2', or `3'.");
445     return -1;
446   }
447
448   hd->version = version;
449
450   return 0;
451 } /* int csnmp_config_add_host_address */
452
453 static int csnmp_config_add_host_collect(host_definition_t *host,
454                                          oconfig_item_t *ci) {
455   data_definition_t *data;
456   data_definition_t **data_list;
457   int data_list_len;
458
459   if (ci->values_num < 1) {
460     WARNING("snmp plugin: `Collect' needs at least one argument.");
461     return -1;
462   }
463
464   for (int i = 0; i < ci->values_num; i++)
465     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
466       WARNING("snmp plugin: All arguments to `Collect' must be strings.");
467       return -1;
468     }
469
470   data_list_len = host->data_list_len + ci->values_num;
471   data_list =
472       realloc(host->data_list, sizeof(data_definition_t *) * data_list_len);
473   if (data_list == NULL)
474     return -1;
475   host->data_list = data_list;
476
477   for (int i = 0; i < ci->values_num; i++) {
478     for (data = data_head; data != NULL; data = data->next)
479       if (strcasecmp(ci->values[i].value.string, data->name) == 0)
480         break;
481
482     if (data == NULL) {
483       WARNING("snmp plugin: No such data configured: `%s'",
484               ci->values[i].value.string);
485       continue;
486     }
487
488     DEBUG("snmp plugin: Collect: host = %s, data[%i] = %s;", host->name,
489           host->data_list_len, data->name);
490
491     host->data_list[host->data_list_len] = data;
492     host->data_list_len++;
493   } /* for (values_num) */
494
495   return 0;
496 } /* int csnmp_config_add_host_collect */
497
498 static int csnmp_config_add_host_auth_protocol(host_definition_t *hd,
499                                                oconfig_item_t *ci) {
500   char buffer[4];
501   int status;
502
503   status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
504   if (status != 0)
505     return status;
506
507   if (strcasecmp("MD5", buffer) == 0) {
508     hd->auth_protocol = usmHMACMD5AuthProtocol;
509     hd->auth_protocol_len = sizeof(usmHMACMD5AuthProtocol) / sizeof(oid);
510   } else if (strcasecmp("SHA", buffer) == 0) {
511     hd->auth_protocol = usmHMACSHA1AuthProtocol;
512     hd->auth_protocol_len = sizeof(usmHMACSHA1AuthProtocol) / sizeof(oid);
513   } else {
514     WARNING("snmp plugin: The `AuthProtocol' config option must be `MD5' or "
515             "`SHA'.");
516     return -1;
517   }
518
519   DEBUG("snmp plugin: host = %s; host->auth_protocol = %s;", hd->name,
520         hd->auth_protocol == usmHMACMD5AuthProtocol ? "MD5" : "SHA");
521
522   return 0;
523 } /* int csnmp_config_add_host_auth_protocol */
524
525 static int csnmp_config_add_host_priv_protocol(host_definition_t *hd,
526                                                oconfig_item_t *ci) {
527   char buffer[4];
528   int status;
529
530   status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
531   if (status != 0)
532     return status;
533
534   if (strcasecmp("AES", buffer) == 0) {
535     hd->priv_protocol = usmAESPrivProtocol;
536     hd->priv_protocol_len = sizeof(usmAESPrivProtocol) / sizeof(oid);
537   } else if (strcasecmp("DES", buffer) == 0) {
538     hd->priv_protocol = usmDESPrivProtocol;
539     hd->priv_protocol_len = sizeof(usmDESPrivProtocol) / sizeof(oid);
540   } else {
541     WARNING("snmp plugin: The `PrivProtocol' config option must be `AES' or "
542             "`DES'.");
543     return -1;
544   }
545
546   DEBUG("snmp plugin: host = %s; host->priv_protocol = %s;", hd->name,
547         hd->priv_protocol == usmAESPrivProtocol ? "AES" : "DES");
548
549   return 0;
550 } /* int csnmp_config_add_host_priv_protocol */
551
552 static int csnmp_config_add_host_security_level(host_definition_t *hd,
553                                                 oconfig_item_t *ci) {
554   char buffer[16];
555   int status;
556
557   status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
558   if (status != 0)
559     return status;
560
561   if (strcasecmp("noAuthNoPriv", buffer) == 0)
562     hd->security_level = SNMP_SEC_LEVEL_NOAUTH;
563   else if (strcasecmp("authNoPriv", buffer) == 0)
564     hd->security_level = SNMP_SEC_LEVEL_AUTHNOPRIV;
565   else if (strcasecmp("authPriv", buffer) == 0)
566     hd->security_level = SNMP_SEC_LEVEL_AUTHPRIV;
567   else {
568     WARNING("snmp plugin: The `SecurityLevel' config option must be "
569             "`noAuthNoPriv', `authNoPriv', or `authPriv'.");
570     return -1;
571   }
572
573   DEBUG("snmp plugin: host = %s; host->security_level = %d;", hd->name,
574         hd->security_level);
575
576   return 0;
577 } /* int csnmp_config_add_host_security_level */
578
579 static int csnmp_config_add_host(oconfig_item_t *ci) {
580   host_definition_t *hd;
581   int status = 0;
582
583   /* Registration stuff. */
584   char cb_name[DATA_MAX_NAME_LEN];
585
586   hd = calloc(1, sizeof(*hd));
587   if (hd == NULL)
588     return -1;
589   hd->version = 2;
590   C_COMPLAIN_INIT(&hd->complaint);
591
592   status = cf_util_get_string(ci, &hd->name);
593   if (status != 0) {
594     sfree(hd);
595     return status;
596   }
597
598   hd->sess_handle = NULL;
599   hd->interval = 0;
600
601   for (int i = 0; i < ci->children_num; i++) {
602     oconfig_item_t *option = ci->children + i;
603     status = 0;
604
605     if (strcasecmp("Address", option->key) == 0)
606       status = cf_util_get_string(option, &hd->address);
607     else if (strcasecmp("Community", option->key) == 0)
608       status = cf_util_get_string(option, &hd->community);
609     else if (strcasecmp("Version", option->key) == 0)
610       status = csnmp_config_add_host_version(hd, option);
611     else if (strcasecmp("Collect", option->key) == 0)
612       csnmp_config_add_host_collect(hd, option);
613     else if (strcasecmp("Interval", option->key) == 0)
614       cf_util_get_cdtime(option, &hd->interval);
615     else if (strcasecmp("Username", option->key) == 0)
616       status = cf_util_get_string(option, &hd->username);
617     else if (strcasecmp("AuthProtocol", option->key) == 0)
618       status = csnmp_config_add_host_auth_protocol(hd, option);
619     else if (strcasecmp("PrivacyProtocol", option->key) == 0)
620       status = csnmp_config_add_host_priv_protocol(hd, option);
621     else if (strcasecmp("AuthPassphrase", option->key) == 0)
622       status = cf_util_get_string(option, &hd->auth_passphrase);
623     else if (strcasecmp("PrivacyPassphrase", option->key) == 0)
624       status = cf_util_get_string(option, &hd->priv_passphrase);
625     else if (strcasecmp("SecurityLevel", option->key) == 0)
626       status = csnmp_config_add_host_security_level(hd, option);
627     else if (strcasecmp("Context", option->key) == 0)
628       status = cf_util_get_string(option, &hd->context);
629     else {
630       WARNING(
631           "snmp plugin: csnmp_config_add_host: Option `%s' not allowed here.",
632           option->key);
633       status = -1;
634     }
635
636     if (status != 0)
637       break;
638   } /* for (ci->children) */
639
640   while (status == 0) {
641     if (hd->address == NULL) {
642       WARNING("snmp plugin: `Address' not given for host `%s'", hd->name);
643       status = -1;
644       break;
645     }
646     if (hd->community == NULL && hd->version < 3) {
647       WARNING("snmp plugin: `Community' not given for host `%s'", hd->name);
648       status = -1;
649       break;
650     }
651     if (hd->version == 3) {
652       if (hd->username == NULL) {
653         WARNING("snmp plugin: `Username' not given for host `%s'", hd->name);
654         status = -1;
655         break;
656       }
657       if (hd->security_level == 0) {
658         WARNING("snmp plugin: `SecurityLevel' not given for host `%s'",
659                 hd->name);
660         status = -1;
661         break;
662       }
663       if (hd->security_level == SNMP_SEC_LEVEL_AUTHNOPRIV ||
664           hd->security_level == SNMP_SEC_LEVEL_AUTHPRIV) {
665         if (hd->auth_protocol == NULL) {
666           WARNING("snmp plugin: `AuthProtocol' not given for host `%s'",
667                   hd->name);
668           status = -1;
669           break;
670         }
671         if (hd->auth_passphrase == NULL) {
672           WARNING("snmp plugin: `AuthPassphrase' not given for host `%s'",
673                   hd->name);
674           status = -1;
675           break;
676         }
677       }
678       if (hd->security_level == SNMP_SEC_LEVEL_AUTHPRIV) {
679         if (hd->priv_protocol == NULL) {
680           WARNING("snmp plugin: `PrivacyProtocol' not given for host `%s'",
681                   hd->name);
682           status = -1;
683           break;
684         }
685         if (hd->priv_passphrase == NULL) {
686           WARNING("snmp plugin: `PrivacyPassphrase' not given for host `%s'",
687                   hd->name);
688           status = -1;
689           break;
690         }
691       }
692     }
693
694     break;
695   } /* while (status == 0) */
696
697   if (status != 0) {
698     csnmp_host_definition_destroy(hd);
699     return -1;
700   }
701
702   DEBUG("snmp plugin: hd = { name = %s, address = %s, community = %s, version "
703         "= %i }",
704         hd->name, hd->address, hd->community, hd->version);
705
706   ssnprintf(cb_name, sizeof(cb_name), "snmp-%s", hd->name);
707
708   status = plugin_register_complex_read(
709       /* group = */ NULL, cb_name, csnmp_read_host, hd->interval,
710       &(user_data_t){
711           .data = hd, .free_func = csnmp_host_definition_destroy,
712       });
713   if (status != 0) {
714     ERROR("snmp plugin: Registering complex read function failed.");
715     csnmp_host_definition_destroy(hd);
716     return -1;
717   }
718
719   return 0;
720 } /* int csnmp_config_add_host */
721
722 static int csnmp_config(oconfig_item_t *ci) {
723   call_snmp_init_once();
724
725   for (int i = 0; i < ci->children_num; i++) {
726     oconfig_item_t *child = ci->children + i;
727     if (strcasecmp("Data", child->key) == 0)
728       csnmp_config_add_data(child);
729     else if (strcasecmp("Host", child->key) == 0)
730       csnmp_config_add_host(child);
731     else {
732       WARNING("snmp plugin: Ignoring unknown config option `%s'.", child->key);
733     }
734   } /* for (ci->children) */
735
736   return 0;
737 } /* int csnmp_config */
738
739 /* }}} End of the config stuff. Now the interesting part begins */
740
741 static void csnmp_host_open_session(host_definition_t *host) {
742   struct snmp_session sess;
743   int error;
744
745   if (host->sess_handle != NULL)
746     csnmp_host_close_session(host);
747
748   snmp_sess_init(&sess);
749   sess.peername = host->address;
750   switch (host->version) {
751   case 1:
752     sess.version = SNMP_VERSION_1;
753     break;
754   case 3:
755     sess.version = SNMP_VERSION_3;
756     break;
757   default:
758     sess.version = SNMP_VERSION_2c;
759     break;
760   }
761
762   if (host->version == 3) {
763     sess.securityName = host->username;
764     sess.securityNameLen = strlen(host->username);
765     sess.securityLevel = host->security_level;
766
767     if (sess.securityLevel == SNMP_SEC_LEVEL_AUTHNOPRIV ||
768         sess.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
769       sess.securityAuthProto = host->auth_protocol;
770       sess.securityAuthProtoLen = host->auth_protocol_len;
771       sess.securityAuthKeyLen = USM_AUTH_KU_LEN;
772       error = generate_Ku(sess.securityAuthProto, sess.securityAuthProtoLen,
773                           (u_char *)host->auth_passphrase,
774                           strlen(host->auth_passphrase), sess.securityAuthKey,
775                           &sess.securityAuthKeyLen);
776       if (error != SNMPERR_SUCCESS) {
777         ERROR("snmp plugin: host %s: Error generating Ku from auth_passphrase. "
778               "(Error %d)",
779               host->name, error);
780       }
781     }
782
783     if (sess.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
784       sess.securityPrivProto = host->priv_protocol;
785       sess.securityPrivProtoLen = host->priv_protocol_len;
786       sess.securityPrivKeyLen = USM_PRIV_KU_LEN;
787       error = generate_Ku(sess.securityAuthProto, sess.securityAuthProtoLen,
788                           (u_char *)host->priv_passphrase,
789                           strlen(host->priv_passphrase), sess.securityPrivKey,
790                           &sess.securityPrivKeyLen);
791       if (error != SNMPERR_SUCCESS) {
792         ERROR("snmp plugin: host %s: Error generating Ku from priv_passphrase. "
793               "(Error %d)",
794               host->name, error);
795       }
796     }
797
798     if (host->context != NULL) {
799       sess.contextName = host->context;
800       sess.contextNameLen = strlen(host->context);
801     }
802   } else /* SNMPv1/2 "authenticates" with community string */
803   {
804     sess.community = (u_char *)host->community;
805     sess.community_len = strlen(host->community);
806   }
807
808   /* snmp_sess_open will copy the `struct snmp_session *'. */
809   host->sess_handle = snmp_sess_open(&sess);
810
811   if (host->sess_handle == NULL) {
812     char *errstr = NULL;
813
814     snmp_error(&sess, NULL, NULL, &errstr);
815
816     ERROR("snmp plugin: host %s: snmp_sess_open failed: %s", host->name,
817           (errstr == NULL) ? "Unknown problem" : errstr);
818     sfree(errstr);
819   }
820 } /* void csnmp_host_open_session */
821
822 /* TODO: Check if negative values wrap around. Problem: negative temperatures.
823  */
824 static value_t csnmp_value_list_to_value(struct variable_list *vl, int type,
825                                          double scale, double shift,
826                                          const char *host_name,
827                                          const char *data_name) {
828   value_t ret;
829   uint64_t tmp_unsigned = 0;
830   int64_t tmp_signed = 0;
831   _Bool defined = 1;
832   /* Set to true when the original SNMP type appears to have been signed. */
833   _Bool prefer_signed = 0;
834
835   if ((vl->type == ASN_INTEGER) || (vl->type == ASN_UINTEGER) ||
836       (vl->type == ASN_COUNTER)
837 #ifdef ASN_TIMETICKS
838       || (vl->type == ASN_TIMETICKS)
839 #endif
840       || (vl->type == ASN_GAUGE)) {
841     tmp_unsigned = (uint32_t)*vl->val.integer;
842     tmp_signed = (int32_t)*vl->val.integer;
843
844     if (vl->type == ASN_INTEGER)
845       prefer_signed = 1;
846
847     DEBUG("snmp plugin: Parsed int32 value is %" PRIu64 ".", tmp_unsigned);
848   } else if (vl->type == ASN_COUNTER64) {
849     tmp_unsigned = (uint32_t)vl->val.counter64->high;
850     tmp_unsigned = tmp_unsigned << 32;
851     tmp_unsigned += (uint32_t)vl->val.counter64->low;
852     tmp_signed = (int64_t)tmp_unsigned;
853     DEBUG("snmp plugin: Parsed int64 value is %" PRIu64 ".", tmp_unsigned);
854   } else if (vl->type == ASN_OCTET_STR) {
855     /* We'll handle this later.. */
856   } else {
857     char oid_buffer[1024] = {0};
858
859     snprint_objid(oid_buffer, sizeof(oid_buffer) - 1, vl->name,
860                   vl->name_length);
861
862 #ifdef ASN_NULL
863     if (vl->type == ASN_NULL)
864       INFO("snmp plugin: OID \"%s\" is undefined (type ASN_NULL)", oid_buffer);
865     else
866 #endif
867       WARNING("snmp plugin: I don't know the ASN type #%i "
868               "(OID: \"%s\", data block \"%s\", host block \"%s\")",
869               (int)vl->type, oid_buffer,
870               (data_name != NULL) ? data_name : "UNKNOWN",
871               (host_name != NULL) ? host_name : "UNKNOWN");
872
873     defined = 0;
874   }
875
876   if (vl->type == ASN_OCTET_STR) {
877     int status = -1;
878
879     if (vl->val.string != NULL) {
880       char string[64];
881       size_t string_length;
882
883       string_length = sizeof(string) - 1;
884       if (vl->val_len < string_length)
885         string_length = vl->val_len;
886
887       /* The strings we get from the Net-SNMP library may not be null
888        * terminated. That is why we're using `memcpy' here and not `strcpy'.
889        * `string_length' is set to `vl->val_len' which holds the length of the
890        * string.  -octo */
891       memcpy(string, vl->val.string, string_length);
892       string[string_length] = 0;
893
894       status = parse_value(string, &ret, type);
895       if (status != 0) {
896         ERROR("snmp plugin: host %s: csnmp_value_list_to_value: Parsing string "
897               "as %s failed: %s",
898               (host_name != NULL) ? host_name : "UNKNOWN",
899               DS_TYPE_TO_STRING(type), string);
900       }
901     }
902
903     if (status != 0) {
904       switch (type) {
905       case DS_TYPE_COUNTER:
906       case DS_TYPE_DERIVE:
907       case DS_TYPE_ABSOLUTE:
908         memset(&ret, 0, sizeof(ret));
909         break;
910
911       case DS_TYPE_GAUGE:
912         ret.gauge = NAN;
913         break;
914
915       default:
916         ERROR("snmp plugin: csnmp_value_list_to_value: Unknown "
917               "data source type: %i.",
918               type);
919         ret.gauge = NAN;
920       }
921     }
922   } /* if (vl->type == ASN_OCTET_STR) */
923   else if (type == DS_TYPE_COUNTER) {
924     ret.counter = tmp_unsigned;
925   } else if (type == DS_TYPE_GAUGE) {
926     if (!defined)
927       ret.gauge = NAN;
928     else if (prefer_signed)
929       ret.gauge = (scale * tmp_signed) + shift;
930     else
931       ret.gauge = (scale * tmp_unsigned) + shift;
932   } else if (type == DS_TYPE_DERIVE) {
933     if (prefer_signed)
934       ret.derive = (derive_t)tmp_signed;
935     else
936       ret.derive = (derive_t)tmp_unsigned;
937   } else if (type == DS_TYPE_ABSOLUTE) {
938     ret.absolute = (absolute_t)tmp_unsigned;
939   } else {
940     ERROR("snmp plugin: csnmp_value_list_to_value: Unknown data source "
941           "type: %i.",
942           type);
943     ret.gauge = NAN;
944   }
945
946   return ret;
947 } /* value_t csnmp_value_list_to_value */
948
949 /* csnmp_strvbcopy_hexstring converts the bit string contained in "vb" to a hex
950  * representation and writes it to dst. Returns zero on success and ENOMEM if
951  * dst is not large enough to hold the string. dst is guaranteed to be
952  * nul-terminated. */
953 static int csnmp_strvbcopy_hexstring(char *dst, /* {{{ */
954                                      const struct variable_list *vb,
955                                      size_t dst_size) {
956   char *buffer_ptr;
957   size_t buffer_free;
958
959   dst[0] = 0;
960
961   buffer_ptr = dst;
962   buffer_free = dst_size;
963
964   for (size_t i = 0; i < vb->val_len; i++) {
965     int status;
966
967     status = snprintf(buffer_ptr, buffer_free, (i == 0) ? "%02x" : ":%02x",
968                       (unsigned int)vb->val.bitstring[i]);
969     assert(status >= 0);
970
971     if (((size_t)status) >= buffer_free) /* truncated */
972     {
973       dst[dst_size - 1] = 0;
974       return ENOMEM;
975     } else /* if (status < buffer_free) */
976     {
977       buffer_ptr += (size_t)status;
978       buffer_free -= (size_t)status;
979     }
980   }
981
982   return 0;
983 } /* }}} int csnmp_strvbcopy_hexstring */
984
985 /* csnmp_strvbcopy copies the octet string or bit string contained in vb to
986  * dst. If non-printable characters are detected, it will switch to a hex
987  * representation of the string. Returns zero on success, EINVAL if vb does not
988  * contain a string and ENOMEM if dst is not large enough to contain the
989  * string. */
990 static int csnmp_strvbcopy(char *dst, /* {{{ */
991                            const struct variable_list *vb, size_t dst_size) {
992   char *src;
993   size_t num_chars;
994
995   if (vb->type == ASN_OCTET_STR)
996     src = (char *)vb->val.string;
997   else if (vb->type == ASN_BIT_STR)
998     src = (char *)vb->val.bitstring;
999   else if (vb->type == ASN_IPADDRESS) {
1000     return ssnprintf(dst, dst_size,
1001                      "%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "",
1002                      (uint8_t)vb->val.string[0], (uint8_t)vb->val.string[1],
1003                      (uint8_t)vb->val.string[2], (uint8_t)vb->val.string[3]);
1004   } else {
1005     dst[0] = 0;
1006     return EINVAL;
1007   }
1008
1009   num_chars = dst_size - 1;
1010   if (num_chars > vb->val_len)
1011     num_chars = vb->val_len;
1012
1013   for (size_t i = 0; i < num_chars; i++) {
1014     /* Check for control characters. */
1015     if ((unsigned char)src[i] < 32)
1016       return csnmp_strvbcopy_hexstring(dst, vb, dst_size);
1017     dst[i] = src[i];
1018   }
1019   dst[num_chars] = 0;
1020   dst[dst_size - 1] = 0;
1021
1022   if (dst_size <= vb->val_len)
1023     return ENOMEM;
1024
1025   return 0;
1026 } /* }}} int csnmp_strvbcopy */
1027
1028 static int csnmp_instance_list_add(csnmp_list_instances_t **head,
1029                                    csnmp_list_instances_t **tail,
1030                                    const struct snmp_pdu *res,
1031                                    const host_definition_t *hd,
1032                                    const data_definition_t *dd) {
1033   csnmp_list_instances_t *il;
1034   struct variable_list *vb;
1035   oid_t vb_name;
1036   int status;
1037   uint32_t is_matched;
1038
1039   /* Set vb on the last variable */
1040   for (vb = res->variables; (vb != NULL) && (vb->next_variable != NULL);
1041        vb = vb->next_variable)
1042     /* do nothing */;
1043   if (vb == NULL)
1044     return -1;
1045
1046   csnmp_oid_init(&vb_name, vb->name, vb->name_length);
1047
1048   il = calloc(1, sizeof(*il));
1049   if (il == NULL) {
1050     ERROR("snmp plugin: calloc failed.");
1051     return -1;
1052   }
1053   il->next = NULL;
1054
1055   status = csnmp_oid_suffix(&il->suffix, &vb_name, &dd->instance.oid);
1056   if (status != 0) {
1057     sfree(il);
1058     return status;
1059   }
1060
1061   /* Get instance name */
1062   if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR) ||
1063       (vb->type == ASN_IPADDRESS)) {
1064     char *ptr;
1065
1066     csnmp_strvbcopy(il->instance, vb, sizeof(il->instance));
1067     is_matched = 0;
1068     for (uint32_t i = 0; i < dd->ignores_len; i++) {
1069       status = fnmatch(dd->ignores[i], il->instance, 0);
1070       if (status == 0) {
1071         if (dd->invert_match == 0) {
1072           sfree(il);
1073           return 0;
1074         } else {
1075           is_matched = 1;
1076           break;
1077         }
1078       }
1079     }
1080     if (dd->invert_match != 0 && is_matched == 0) {
1081       sfree(il);
1082       return 0;
1083     }
1084     for (ptr = il->instance; *ptr != '\0'; ptr++) {
1085       if ((*ptr > 0) && (*ptr < 32))
1086         *ptr = ' ';
1087       else if (*ptr == '/')
1088         *ptr = '_';
1089     }
1090     DEBUG("snmp plugin: il->instance = `%s';", il->instance);
1091   } else {
1092     value_t val = csnmp_value_list_to_value(
1093         vb, DS_TYPE_COUNTER,
1094         /* scale = */ 1.0, /* shift = */ 0.0, hd->name, dd->name);
1095     ssnprintf(il->instance, sizeof(il->instance), "%llu", val.counter);
1096   }
1097
1098   /* TODO: Debugging output */
1099
1100   if (*head == NULL)
1101     *head = il;
1102   else
1103     (*tail)->next = il;
1104   *tail = il;
1105
1106   return 0;
1107 } /* int csnmp_instance_list_add */
1108
1109 static int csnmp_dispatch_table(host_definition_t *host,
1110                                 data_definition_t *data,
1111                                 csnmp_list_instances_t *instance_list,
1112                                 csnmp_table_values_t **value_table) {
1113   const data_set_t *ds;
1114   value_list_t vl = VALUE_LIST_INIT;
1115
1116   csnmp_list_instances_t *instance_list_ptr;
1117   csnmp_table_values_t *value_table_ptr[data->values_len];
1118
1119   size_t i;
1120   _Bool have_more;
1121   oid_t current_suffix;
1122
1123   ds = plugin_get_ds(data->type);
1124   if (!ds) {
1125     ERROR("snmp plugin: DataSet `%s' not defined.", data->type);
1126     return -1;
1127   }
1128   assert(ds->ds_num == data->values_len);
1129   assert(data->values_len > 0);
1130
1131   instance_list_ptr = instance_list;
1132
1133   for (i = 0; i < data->values_len; i++)
1134     value_table_ptr[i] = value_table[i];
1135
1136   sstrncpy(vl.host, host->name, sizeof(vl.host));
1137   sstrncpy(vl.plugin, "snmp", sizeof(vl.plugin));
1138
1139   vl.interval = host->interval;
1140
1141   have_more = 1;
1142   while (have_more) {
1143     _Bool suffix_skipped = 0;
1144
1145     /* Determine next suffix to handle. */
1146     if (instance_list != NULL) {
1147       if (instance_list_ptr == NULL) {
1148         have_more = 0;
1149         continue;
1150       }
1151
1152       memcpy(&current_suffix, &instance_list_ptr->suffix,
1153              sizeof(current_suffix));
1154     } else {
1155       /* no instance configured */
1156       csnmp_table_values_t *ptr = value_table_ptr[0];
1157       if (ptr == NULL) {
1158         have_more = 0;
1159         continue;
1160       }
1161
1162       memcpy(&current_suffix, &ptr->suffix, sizeof(current_suffix));
1163     }
1164
1165     /* Update all the value_table_ptr to point at the entry with the same
1166      * trailing partial OID */
1167     for (i = 0; i < data->values_len; i++) {
1168       while (
1169           (value_table_ptr[i] != NULL) &&
1170           (csnmp_oid_compare(&value_table_ptr[i]->suffix, &current_suffix) < 0))
1171         value_table_ptr[i] = value_table_ptr[i]->next;
1172
1173       if (value_table_ptr[i] == NULL) {
1174         have_more = 0;
1175         break;
1176       } else if (csnmp_oid_compare(&value_table_ptr[i]->suffix,
1177                                    &current_suffix) > 0) {
1178         /* This suffix is missing in the subtree. Indicate this with the
1179          * "suffix_skipped" flag and try the next instance / suffix. */
1180         suffix_skipped = 1;
1181         break;
1182       }
1183     } /* for (i = 0; i < columns; i++) */
1184
1185     if (!have_more)
1186       break;
1187
1188     /* Matching the values failed. Start from the beginning again. */
1189     if (suffix_skipped) {
1190       if (instance_list != NULL)
1191         instance_list_ptr = instance_list_ptr->next;
1192       else
1193         value_table_ptr[0] = value_table_ptr[0]->next;
1194
1195       continue;
1196     }
1197
1198 /* if we reach this line, all value_table_ptr[i] are non-NULL and are set
1199  * to the same subid. instance_list_ptr is either NULL or points to the
1200  * same subid, too. */
1201 #if COLLECT_DEBUG
1202     for (i = 1; i < data->values_len; i++) {
1203       assert(value_table_ptr[i] != NULL);
1204       assert(csnmp_oid_compare(&value_table_ptr[i - 1]->suffix,
1205                                &value_table_ptr[i]->suffix) == 0);
1206     }
1207     assert((instance_list_ptr == NULL) ||
1208            (csnmp_oid_compare(&instance_list_ptr->suffix,
1209                               &value_table_ptr[0]->suffix) == 0));
1210 #endif
1211
1212     sstrncpy(vl.type, data->type, sizeof(vl.type));
1213
1214     {
1215       char temp[DATA_MAX_NAME_LEN];
1216
1217       if (instance_list_ptr == NULL)
1218         csnmp_oid_to_string(temp, sizeof(temp), &current_suffix);
1219       else
1220         sstrncpy(temp, instance_list_ptr->instance, sizeof(temp));
1221
1222       if (data->instance_prefix == NULL)
1223         sstrncpy(vl.type_instance, temp, sizeof(vl.type_instance));
1224       else
1225         ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s%s",
1226                   data->instance_prefix, temp);
1227     }
1228
1229     vl.values_len = data->values_len;
1230     value_t values[vl.values_len];
1231     vl.values = values;
1232
1233     for (i = 0; i < data->values_len; i++)
1234       vl.values[i] = value_table_ptr[i]->value;
1235
1236     /* If we get here `vl.type_instance' and all `vl.values' have been set
1237      * vl.type_instance can be empty, i.e. a blank port description on a
1238      * switch if you're using IF-MIB::ifDescr as Instance.
1239      */
1240     if (vl.type_instance[0] != '\0')
1241       plugin_dispatch_values(&vl);
1242
1243     /* prevent leakage of pointer to local variable. */
1244     vl.values_len = 0;
1245     vl.values = NULL;
1246
1247     if (instance_list != NULL)
1248       instance_list_ptr = instance_list_ptr->next;
1249     else
1250       value_table_ptr[0] = value_table_ptr[0]->next;
1251   } /* while (have_more) */
1252
1253   return (0);
1254 } /* int csnmp_dispatch_table */
1255
1256 static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
1257   struct snmp_pdu *req;
1258   struct snmp_pdu *res = NULL;
1259   struct variable_list *vb;
1260
1261   const data_set_t *ds;
1262
1263   size_t oid_list_len = data->values_len + 1;
1264   /* Holds the last OID returned by the device. We use this in the GETNEXT
1265    * request to proceed. */
1266   oid_t oid_list[oid_list_len];
1267   /* Set to false when an OID has left its subtree so we don't re-request it
1268    * again. */
1269   _Bool oid_list_todo[oid_list_len];
1270
1271   int status;
1272   size_t i;
1273
1274   /* `value_list_head' and `value_list_tail' implement a linked list for each
1275    * value. `instance_list_head' and `instance_list_tail' implement a linked
1276    * list of
1277    * instance names. This is used to jump gaps in the table. */
1278   csnmp_list_instances_t *instance_list_head;
1279   csnmp_list_instances_t *instance_list_tail;
1280   csnmp_table_values_t **value_list_head;
1281   csnmp_table_values_t **value_list_tail;
1282
1283   DEBUG("snmp plugin: csnmp_read_table (host = %s, data = %s)", host->name,
1284         data->name);
1285
1286   if (host->sess_handle == NULL) {
1287     DEBUG("snmp plugin: csnmp_read_table: host->sess_handle == NULL");
1288     return -1;
1289   }
1290
1291   ds = plugin_get_ds(data->type);
1292   if (!ds) {
1293     ERROR("snmp plugin: DataSet `%s' not defined.", data->type);
1294     return -1;
1295   }
1296
1297   if (ds->ds_num != data->values_len) {
1298     ERROR("snmp plugin: DataSet `%s' requires %zu values, but config talks "
1299           "about %zu",
1300           data->type, ds->ds_num, data->values_len);
1301     return -1;
1302   }
1303   assert(data->values_len > 0);
1304
1305   /* We need a copy of all the OIDs, because GETNEXT will destroy them. */
1306   memcpy(oid_list, data->values, data->values_len * sizeof(oid_t));
1307   if (data->instance.oid.oid_len > 0)
1308     memcpy(oid_list + data->values_len, &data->instance.oid, sizeof(oid_t));
1309   else /* no InstanceFrom option specified. */
1310     oid_list_len--;
1311
1312   for (i = 0; i < oid_list_len; i++)
1313     oid_list_todo[i] = 1;
1314
1315   /* We're going to construct n linked lists, one for each "value".
1316    * value_list_head will contain pointers to the heads of these linked lists,
1317    * value_list_tail will contain pointers to the tail of the lists. */
1318   value_list_head = calloc(data->values_len, sizeof(*value_list_head));
1319   value_list_tail = calloc(data->values_len, sizeof(*value_list_tail));
1320   if ((value_list_head == NULL) || (value_list_tail == NULL)) {
1321     ERROR("snmp plugin: csnmp_read_table: calloc failed.");
1322     sfree(value_list_head);
1323     sfree(value_list_tail);
1324     return -1;
1325   }
1326
1327   instance_list_head = NULL;
1328   instance_list_tail = NULL;
1329
1330   status = 0;
1331   while (status == 0) {
1332     int oid_list_todo_num;
1333
1334     req = snmp_pdu_create(SNMP_MSG_GETNEXT);
1335     if (req == NULL) {
1336       ERROR("snmp plugin: snmp_pdu_create failed.");
1337       status = -1;
1338       break;
1339     }
1340
1341     oid_list_todo_num = 0;
1342     for (i = 0; i < oid_list_len; i++) {
1343       /* Do not rerequest already finished OIDs */
1344       if (!oid_list_todo[i])
1345         continue;
1346       oid_list_todo_num++;
1347       snmp_add_null_var(req, oid_list[i].oid, oid_list[i].oid_len);
1348     }
1349
1350     if (oid_list_todo_num == 0) {
1351       /* The request is still empty - so we are finished */
1352       DEBUG("snmp plugin: all variables have left their subtree");
1353       status = 0;
1354       break;
1355     }
1356
1357     res = NULL;
1358     status = snmp_sess_synch_response(host->sess_handle, req, &res);
1359     if ((status != STAT_SUCCESS) || (res == NULL)) {
1360       char *errstr = NULL;
1361
1362       snmp_sess_error(host->sess_handle, NULL, NULL, &errstr);
1363
1364       c_complain(LOG_ERR, &host->complaint,
1365                  "snmp plugin: host %s: snmp_sess_synch_response failed: %s",
1366                  host->name, (errstr == NULL) ? "Unknown problem" : errstr);
1367
1368       if (res != NULL)
1369         snmp_free_pdu(res);
1370       res = NULL;
1371
1372       /* snmp_synch_response already freed our PDU */
1373       req = NULL;
1374       sfree(errstr);
1375       csnmp_host_close_session(host);
1376
1377       status = -1;
1378       break;
1379     }
1380
1381     status = 0;
1382     assert(res != NULL);
1383     c_release(LOG_INFO, &host->complaint,
1384               "snmp plugin: host %s: snmp_sess_synch_response successful.",
1385               host->name);
1386
1387     vb = res->variables;
1388     if (vb == NULL) {
1389       status = -1;
1390       break;
1391     }
1392
1393     for (vb = res->variables, i = 0; (vb != NULL);
1394          vb = vb->next_variable, i++) {
1395       /* Calculate value index from todo list */
1396       while ((i < oid_list_len) && !oid_list_todo[i])
1397         i++;
1398
1399       /* An instance is configured and the res variable we process is the
1400        * instance value (last index) */
1401       if ((data->instance.oid.oid_len > 0) && (i == data->values_len)) {
1402         if ((vb->type == SNMP_ENDOFMIBVIEW) ||
1403             (snmp_oid_ncompare(
1404                  data->instance.oid.oid, data->instance.oid.oid_len, vb->name,
1405                  vb->name_length, data->instance.oid.oid_len) != 0)) {
1406           DEBUG("snmp plugin: host = %s; data = %s; Instance left its subtree.",
1407                 host->name, data->name);
1408           oid_list_todo[i] = 0;
1409           continue;
1410         }
1411
1412         /* Allocate a new `csnmp_list_instances_t', insert the instance name and
1413          * add it to the list */
1414         if (csnmp_instance_list_add(&instance_list_head, &instance_list_tail,
1415                                     res, host, data) != 0) {
1416           ERROR("snmp plugin: host %s: csnmp_instance_list_add failed.",
1417                 host->name);
1418           status = -1;
1419           break;
1420         }
1421       } else /* The variable we are processing is a normal value */
1422       {
1423         csnmp_table_values_t *vt;
1424         oid_t vb_name;
1425         oid_t suffix;
1426         int ret;
1427
1428         csnmp_oid_init(&vb_name, vb->name, vb->name_length);
1429
1430         /* Calculate the current suffix. This is later used to check that the
1431          * suffix is increasing. This also checks if we left the subtree */
1432         ret = csnmp_oid_suffix(&suffix, &vb_name, data->values + i);
1433         if (ret != 0) {
1434           DEBUG("snmp plugin: host = %s; data = %s; i = %zu; "
1435                 "Value probably left its subtree.",
1436                 host->name, data->name, i);
1437           oid_list_todo[i] = 0;
1438           continue;
1439         }
1440
1441         /* Make sure the OIDs returned by the agent are increasing. Otherwise
1442          * our
1443          * table matching algorithm will get confused. */
1444         if ((value_list_tail[i] != NULL) &&
1445             (csnmp_oid_compare(&suffix, &value_list_tail[i]->suffix) <= 0)) {
1446           DEBUG("snmp plugin: host = %s; data = %s; i = %zu; "
1447                 "Suffix is not increasing.",
1448                 host->name, data->name, i);
1449           oid_list_todo[i] = 0;
1450           continue;
1451         }
1452
1453         vt = calloc(1, sizeof(*vt));
1454         if (vt == NULL) {
1455           ERROR("snmp plugin: calloc failed.");
1456           status = -1;
1457           break;
1458         }
1459
1460         vt->value =
1461             csnmp_value_list_to_value(vb, ds->ds[i].type, data->scale,
1462                                       data->shift, host->name, data->name);
1463         memcpy(&vt->suffix, &suffix, sizeof(vt->suffix));
1464         vt->next = NULL;
1465
1466         if (value_list_tail[i] == NULL)
1467           value_list_head[i] = vt;
1468         else
1469           value_list_tail[i]->next = vt;
1470         value_list_tail[i] = vt;
1471       }
1472
1473       /* Copy OID to oid_list[i] */
1474       memcpy(oid_list[i].oid, vb->name, sizeof(oid) * vb->name_length);
1475       oid_list[i].oid_len = vb->name_length;
1476
1477     } /* for (vb = res->variables ...) */
1478
1479     if (res != NULL)
1480       snmp_free_pdu(res);
1481     res = NULL;
1482   } /* while (status == 0) */
1483
1484   if (res != NULL)
1485     snmp_free_pdu(res);
1486   res = NULL;
1487
1488   if (req != NULL)
1489     snmp_free_pdu(req);
1490   req = NULL;
1491
1492   if (status == 0)
1493     csnmp_dispatch_table(host, data, instance_list_head, value_list_head);
1494
1495   /* Free all allocated variables here */
1496   while (instance_list_head != NULL) {
1497     csnmp_list_instances_t *next = instance_list_head->next;
1498     sfree(instance_list_head);
1499     instance_list_head = next;
1500   }
1501
1502   for (i = 0; i < data->values_len; i++) {
1503     while (value_list_head[i] != NULL) {
1504       csnmp_table_values_t *next = value_list_head[i]->next;
1505       sfree(value_list_head[i]);
1506       value_list_head[i] = next;
1507     }
1508   }
1509
1510   sfree(value_list_head);
1511   sfree(value_list_tail);
1512
1513   return 0;
1514 } /* int csnmp_read_table */
1515
1516 static int csnmp_read_value(host_definition_t *host, data_definition_t *data) {
1517   struct snmp_pdu *req;
1518   struct snmp_pdu *res = NULL;
1519   struct variable_list *vb;
1520
1521   const data_set_t *ds;
1522   value_list_t vl = VALUE_LIST_INIT;
1523
1524   int status;
1525   size_t i;
1526
1527   DEBUG("snmp plugin: csnmp_read_value (host = %s, data = %s)", host->name,
1528         data->name);
1529
1530   if (host->sess_handle == NULL) {
1531     DEBUG("snmp plugin: csnmp_read_value: host->sess_handle == NULL");
1532     return -1;
1533   }
1534
1535   ds = plugin_get_ds(data->type);
1536   if (!ds) {
1537     ERROR("snmp plugin: DataSet `%s' not defined.", data->type);
1538     return -1;
1539   }
1540
1541   if (ds->ds_num != data->values_len) {
1542     ERROR("snmp plugin: DataSet `%s' requires %zu values, but config talks "
1543           "about %zu",
1544           data->type, ds->ds_num, data->values_len);
1545     return -1;
1546   }
1547
1548   vl.values_len = ds->ds_num;
1549   vl.values = malloc(sizeof(*vl.values) * vl.values_len);
1550   if (vl.values == NULL)
1551     return -1;
1552   for (i = 0; i < vl.values_len; i++) {
1553     if (ds->ds[i].type == DS_TYPE_COUNTER)
1554       vl.values[i].counter = 0;
1555     else
1556       vl.values[i].gauge = NAN;
1557   }
1558
1559   sstrncpy(vl.host, host->name, sizeof(vl.host));
1560   sstrncpy(vl.plugin, "snmp", sizeof(vl.plugin));
1561   sstrncpy(vl.type, data->type, sizeof(vl.type));
1562   sstrncpy(vl.type_instance, data->instance.string, sizeof(vl.type_instance));
1563
1564   vl.interval = host->interval;
1565
1566   req = snmp_pdu_create(SNMP_MSG_GET);
1567   if (req == NULL) {
1568     ERROR("snmp plugin: snmp_pdu_create failed.");
1569     sfree(vl.values);
1570     return -1;
1571   }
1572
1573   for (i = 0; i < data->values_len; i++)
1574     snmp_add_null_var(req, data->values[i].oid, data->values[i].oid_len);
1575
1576   status = snmp_sess_synch_response(host->sess_handle, req, &res);
1577
1578   if ((status != STAT_SUCCESS) || (res == NULL)) {
1579     char *errstr = NULL;
1580
1581     snmp_sess_error(host->sess_handle, NULL, NULL, &errstr);
1582     ERROR("snmp plugin: host %s: snmp_sess_synch_response failed: %s",
1583           host->name, (errstr == NULL) ? "Unknown problem" : errstr);
1584
1585     if (res != NULL)
1586       snmp_free_pdu(res);
1587
1588     sfree(errstr);
1589     sfree(vl.values);
1590     csnmp_host_close_session(host);
1591
1592     return -1;
1593   }
1594
1595   for (vb = res->variables; vb != NULL; vb = vb->next_variable) {
1596 #if COLLECT_DEBUG
1597     char buffer[1024];
1598     snprint_variable(buffer, sizeof(buffer), vb->name, vb->name_length, vb);
1599     DEBUG("snmp plugin: Got this variable: %s", buffer);
1600 #endif /* COLLECT_DEBUG */
1601
1602     for (i = 0; i < data->values_len; i++)
1603       if (snmp_oid_compare(data->values[i].oid, data->values[i].oid_len,
1604                            vb->name, vb->name_length) == 0)
1605         vl.values[i] =
1606             csnmp_value_list_to_value(vb, ds->ds[i].type, data->scale,
1607                                       data->shift, host->name, data->name);
1608   } /* for (res->variables) */
1609
1610   snmp_free_pdu(res);
1611
1612   DEBUG("snmp plugin: -> plugin_dispatch_values (&vl);");
1613   plugin_dispatch_values(&vl);
1614   sfree(vl.values);
1615
1616   return 0;
1617 } /* int csnmp_read_value */
1618
1619 static int csnmp_read_host(user_data_t *ud) {
1620   host_definition_t *host;
1621   int status;
1622   int success;
1623   int i;
1624
1625   host = ud->data;
1626
1627   if (host->interval == 0)
1628     host->interval = plugin_get_interval();
1629
1630   if (host->sess_handle == NULL)
1631     csnmp_host_open_session(host);
1632
1633   if (host->sess_handle == NULL)
1634     return -1;
1635
1636   success = 0;
1637   for (i = 0; i < host->data_list_len; i++) {
1638     data_definition_t *data = host->data_list[i];
1639
1640     if (data->is_table)
1641       status = csnmp_read_table(host, data);
1642     else
1643       status = csnmp_read_value(host, data);
1644
1645     if (status == 0)
1646       success++;
1647   }
1648
1649   if (success == 0)
1650     return -1;
1651
1652   return 0;
1653 } /* int csnmp_read_host */
1654
1655 static int csnmp_init(void) {
1656   call_snmp_init_once();
1657
1658   return 0;
1659 } /* int csnmp_init */
1660
1661 static int csnmp_shutdown(void) {
1662   data_definition_t *data_this;
1663   data_definition_t *data_next;
1664
1665   /* When we get here, the read threads have been stopped and all the
1666    * `host_definition_t' will be freed. */
1667   DEBUG("snmp plugin: Destroying all data definitions.");
1668
1669   data_this = data_head;
1670   data_head = NULL;
1671   while (data_this != NULL) {
1672     data_next = data_this->next;
1673
1674     sfree(data_this->name);
1675     sfree(data_this->type);
1676     sfree(data_this->values);
1677     sfree(data_this->ignores);
1678     sfree(data_this);
1679
1680     data_this = data_next;
1681   }
1682
1683   return 0;
1684 } /* int csnmp_shutdown */
1685
1686 void module_register(void) {
1687   plugin_register_complex_config("snmp", csnmp_config);
1688   plugin_register_init("snmp", csnmp_init);
1689   plugin_register_shutdown("snmp", csnmp_shutdown);
1690 } /* void module_register */