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