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