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