SNMP Agent plugin:
[collectd.git] / src / snmp_agent.c
1 /**
2  * collectd - src/snmp_agent.c
3  *
4  * Copyright(c) 2017 Intel Corporation. All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * Authors:
25  *   Roman Korynkevych <romanx.korynkevych@intel.com>
26  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
27  **/
28
29 #include "collectd.h"
30
31 #include "common.h"
32 #include "utils_avltree.h"
33 #include "utils_cache.h"
34 #include "utils_llist.h"
35
36 #include <net-snmp/net-snmp-config.h>
37
38 #include <net-snmp/net-snmp-includes.h>
39
40 #include <net-snmp/agent/net-snmp-agent-includes.h>
41
42 #define PLUGIN_NAME "snmp_agent"
43 #define ERR_BUF_SIZE 1024
44 #define TYPE_STRING -1
45
46 struct oid_s {
47   oid oid[MAX_OID_LEN];
48   size_t oid_len;
49   u_char type;
50 };
51 typedef struct oid_s oid_t;
52
53 struct table_definition_s {
54   char *name;
55   oid_t index_oid;
56   oid_t size_oid;
57   llist_t *columns;
58   c_avl_tree_t *instance_index;
59   c_avl_tree_t *index_instance;
60 };
61 typedef struct table_definition_s table_definition_t;
62
63 struct data_definition_s {
64   char *name;
65   char *plugin;
66   char *plugin_instance;
67   char *type;
68   char *type_instance;
69   const table_definition_t *table;
70   bool is_instance;
71   oid_t *oids;
72   size_t oids_len;
73   double scale;
74   double shift;
75 };
76 typedef struct data_definition_s data_definition_t;
77
78 struct snmp_agent_ctx_s {
79   pthread_t thread;
80   pthread_mutex_t lock;
81   pthread_mutex_t agentx_lock;
82   struct tree *tp;
83
84   llist_t *tables;
85   llist_t *scalars;
86 };
87 typedef struct snmp_agent_ctx_s snmp_agent_ctx_t;
88
89 static snmp_agent_ctx_t *g_agent;
90
91 #define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti)                                   \
92   (_dd->plugin ? !strcmp(_dd->plugin, _p) : 0) &&                              \
93       (_dd->plugin_instance ? !strcmp(_dd->plugin_instance, _pi) : 1) &&       \
94       (_dd->type ? !strcmp(_dd->type, _t) : 0) &&                              \
95       (_dd->type_instance ? !strcmp(_dd->type_instance, _ti) : 1)
96
97 static int snmp_agent_shutdown(void);
98 static void *snmp_agent_thread_run(void *arg);
99 static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler);
100 static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len,
101                                   u_char asn_type, double scale, double shift,
102                                   const void *value, size_t len, int type);
103 static int snmp_agent_unregister_oid_index(oid_t *oid, int index);
104
105 static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) {
106   struct tree *node = get_tree(oid, oid_len, g_agent->tp);
107
108   return (node != NULL) ? mib_to_asn_type(node->type) : 0;
109 }
110
111 static char *snmp_agent_get_oid_name(oid *oid, size_t oid_len) {
112   struct tree *node = get_tree(oid, oid_len, g_agent->tp);
113
114   return (node != NULL) ? node->label : NULL;
115 }
116
117 static int snmp_agent_oid_to_string(char *buf, size_t buf_size,
118                                     oid_t const *o) {
119   char oid_str[MAX_OID_LEN][16];
120   char *oid_str_ptr[MAX_OID_LEN];
121
122   for (size_t i = 0; i < o->oid_len; i++) {
123     snprintf(oid_str[i], sizeof(oid_str[i]), "%lu", (unsigned long)o->oid[i]);
124     oid_str_ptr[i] = oid_str[i];
125   }
126
127   return strjoin(buf, buf_size, oid_str_ptr, o->oid_len, ".");
128 }
129
130 /* Prints a configuration storing list. It handles both table columns list
131    and scalars list */
132 #if COLLECT_DEBUG
133 static void snmp_agent_dump_data(llist_t *list) {
134   char oid_str[DATA_MAX_NAME_LEN];
135   for (llentry_t *de = llist_head(list); de != NULL; de = de->next) {
136     data_definition_t *dd = de->value;
137
138     if (dd->table != NULL)
139       DEBUG(PLUGIN_NAME ":   Column:");
140     else
141       DEBUG(PLUGIN_NAME ": Scalar:");
142
143     DEBUG(PLUGIN_NAME ":     Name: %s", dd->name);
144     if (dd->plugin)
145       DEBUG(PLUGIN_NAME ":     Plugin: %s", dd->plugin);
146     if (dd->plugin_instance)
147       DEBUG(PLUGIN_NAME ":     PluginInstance: %s", dd->plugin_instance);
148     if (dd->is_instance)
149       DEBUG(PLUGIN_NAME ":     Instance: true");
150     if (dd->type)
151       DEBUG(PLUGIN_NAME ":     Type: %s", dd->type);
152     if (dd->type_instance)
153       DEBUG(PLUGIN_NAME ":     TypeInstance: %s", dd->type_instance);
154     for (size_t i = 0; i < dd->oids_len; i++) {
155       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]);
156       DEBUG(PLUGIN_NAME ":     OID[%" PRIsz "]: %s", i, oid_str);
157     }
158     DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
159     DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
160   }
161 }
162
163 /* Prints parsed configuration */
164 static void snmp_agent_dump_config(void) {
165   char oid_str[DATA_MAX_NAME_LEN];
166
167   /* Printing tables */
168   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
169     table_definition_t *td = te->value;
170
171     DEBUG(PLUGIN_NAME ": Table:");
172     DEBUG(PLUGIN_NAME ":   Name: %s", td->name);
173     if (td->index_oid.oid_len != 0) {
174       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->index_oid);
175       DEBUG(PLUGIN_NAME ":   IndexOID: %s", oid_str);
176     }
177     if (td->size_oid.oid_len != 0) {
178       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->size_oid);
179       DEBUG(PLUGIN_NAME ":   SizeOID: %s", oid_str);
180     }
181
182     snmp_agent_dump_data(td->columns);
183   }
184
185   /* Printing scalars */
186   snmp_agent_dump_data(g_agent->scalars);
187 }
188 #endif /* COLLECT_DEBUG */
189
190 static int snmp_agent_validate_config(void) {
191
192 #if COLLECT_DEBUG
193   snmp_agent_dump_config();
194 #endif
195
196   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
197     table_definition_t *td = te->value;
198
199     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
200       data_definition_t *dd = de->value;
201
202       if (!dd->plugin) {
203         ERROR(PLUGIN_NAME ": Plugin not defined for '%s'.'%s'", td->name,
204               dd->name);
205         return -EINVAL;
206       }
207
208       if (dd->plugin_instance) {
209         ERROR(PLUGIN_NAME ": PluginInstance should not be defined for table "
210                           "data type '%s'.'%s'",
211               td->name, dd->name);
212         return -EINVAL;
213       }
214
215       if (dd->oids_len == 0) {
216         ERROR(PLUGIN_NAME ": No OIDs defined for '%s'.'%s'", td->name,
217               dd->name);
218         return -EINVAL;
219       }
220
221       if (dd->is_instance) {
222
223         if (dd->type || dd->type_instance) {
224           ERROR(PLUGIN_NAME ": Type and TypeInstance are not valid for "
225                             "instance data '%s'.'%s'",
226                 td->name, dd->name);
227           return -EINVAL;
228         }
229
230         if (dd->oids_len > 1) {
231           ERROR(
232               PLUGIN_NAME
233               ": Only one OID should be specified for instance data '%s'.'%s'",
234               td->name, dd->name);
235           return -EINVAL;
236         }
237       } else {
238
239         if (!dd->type) {
240           ERROR(PLUGIN_NAME ": Type not defined for data '%s'.'%s'", td->name,
241                 dd->name);
242           return -EINVAL;
243         }
244       }
245     }
246   }
247
248   for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
249     data_definition_t *dd = e->value;
250
251     if (!dd->plugin) {
252       ERROR(PLUGIN_NAME ": Plugin not defined for '%s'", dd->name);
253       return -EINVAL;
254     }
255
256     if (dd->oids_len == 0) {
257       ERROR(PLUGIN_NAME ": No OIDs defined for '%s'", dd->name);
258       return -EINVAL;
259     }
260
261     if (dd->is_instance) {
262       ERROR(PLUGIN_NAME
263             ": Instance flag can't be specified for scalar data '%s'",
264             dd->name);
265       return -EINVAL;
266     }
267
268     if (!dd->type) {
269       ERROR(PLUGIN_NAME ": Type not defined for data '%s'", dd->name);
270       return -EINVAL;
271     }
272   }
273
274   return 0;
275 }
276
277 static void snmp_agent_generate_oid2string(oid_t *oid, size_t offset,
278                                            char *key) {
279   size_t key_len = oid->oid[offset];
280   size_t i;
281
282   for (i = 0; i < key_len && offset < oid->oid_len; i++)
283     key[i] = oid->oid[++offset];
284
285   key[i] = '\0';
286 }
287
288 static int snmp_agent_generate_string2oid(oid_t *oid, const char *key) {
289   size_t key_len = strlen(key);
290
291   oid->oid[oid->oid_len++] = key_len;
292   for (size_t i = 0; i < key_len; i++) {
293     oid->oid[oid->oid_len++] = key[i];
294     if (oid->oid_len >= MAX_OID_LEN) {
295       ERROR(PLUGIN_NAME ": Conversion key string %s to OID failed", key);
296       return -EINVAL;
297     }
298   }
299
300   return 0;
301 }
302
303 static int snmp_agent_register_oid_string(oid_t *oid, const char *key,
304                                           Netsnmp_Node_Handler *handler) {
305   oid_t new_oid;
306
307   memcpy(&new_oid, oid, sizeof(*oid));
308   int ret = snmp_agent_generate_string2oid(&new_oid, key);
309   if (ret != 0)
310     return ret;
311
312   return snmp_agent_register_oid(&new_oid, handler);
313 }
314
315 static int snmp_agent_unregister_oid_string(oid_t *oid, const char *key) {
316   oid_t new_oid;
317
318   memcpy(&new_oid, oid, sizeof(*oid));
319   int ret = snmp_agent_generate_string2oid(&new_oid, key);
320   if (ret != 0)
321     return ret;
322
323   return unregister_mib(new_oid.oid, new_oid.oid_len);
324 }
325
326 static int snmp_agent_table_row_remove(table_definition_t *td,
327                                        const char *instance) {
328   int *index = NULL;
329   char *ins = NULL;
330
331   if (td->index_oid.oid_len) {
332     if ((c_avl_get(td->instance_index, instance, (void **)&index) != 0) ||
333         (c_avl_get(td->index_instance, index, (void **)&ins) != 0))
334       return 0;
335   } else {
336     if (c_avl_get(td->instance_index, instance, (void **)&ins) != 0)
337       return 0;
338   }
339
340   pthread_mutex_lock(&g_agent->agentx_lock);
341
342   if (td->index_oid.oid_len)
343     snmp_agent_unregister_oid_index(&td->index_oid, *index);
344
345   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
346     data_definition_t *dd = de->value;
347
348     for (size_t i = 0; i < dd->oids_len; i++)
349       if (td->index_oid.oid_len)
350         snmp_agent_unregister_oid_index(&dd->oids[i], *index);
351       else
352         snmp_agent_unregister_oid_string(&dd->oids[i], ins);
353   }
354
355   pthread_mutex_unlock(&g_agent->agentx_lock);
356
357   DEBUG(PLUGIN_NAME ": Removed row for '%s' table [%d, %s]", td->name,
358         (index != NULL) ? *index : -1, ins);
359
360   notification_t n = {
361       .severity = NOTIF_WARNING, .time = cdtime(), .plugin = PLUGIN_NAME};
362   sstrncpy(n.host, hostname_g, sizeof(n.host));
363   sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
364   snprintf(n.message, sizeof(n.message),
365            "Removed data row from table %s instance %s index %d", td->name, ins,
366            (index != NULL) ? *index : -1);
367   plugin_dispatch_notification(&n);
368
369   if (td->index_oid.oid_len) {
370     c_avl_remove(td->index_instance, index, NULL, (void **)&ins);
371     c_avl_remove(td->instance_index, instance, NULL, (void **)&index);
372     sfree(index);
373     sfree(ins);
374   } else {
375     c_avl_remove(td->instance_index, instance, NULL, (void **)&ins);
376     sfree(ins);
377   }
378
379   return 0;
380 }
381
382 static int snmp_agent_clear_missing(const value_list_t *vl,
383                                     __attribute__((unused)) user_data_t *ud) {
384   if (vl == NULL)
385     return -EINVAL;
386
387   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
388     table_definition_t *td = te->value;
389
390     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
391       data_definition_t *dd = de->value;
392
393       if (!dd->is_instance) {
394         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
395                           vl->type_instance)) {
396           snmp_agent_table_row_remove(td, vl->plugin_instance);
397           return 0;
398         }
399       }
400     }
401   }
402
403   return 0;
404 }
405
406 static void snmp_agent_free_data(data_definition_t **dd) {
407
408   if (dd == NULL || *dd == NULL)
409     return;
410
411   /* unregister scalar type OID */
412   if ((*dd)->table == NULL) {
413     for (size_t i = 0; i < (*dd)->oids_len; i++)
414       unregister_mib((*dd)->oids[i].oid, (*dd)->oids[i].oid_len);
415   }
416
417   sfree((*dd)->name);
418   sfree((*dd)->plugin);
419   sfree((*dd)->plugin_instance);
420   sfree((*dd)->type);
421   sfree((*dd)->type_instance);
422   sfree((*dd)->oids);
423
424   sfree(*dd);
425
426   return;
427 }
428
429 static void snmp_agent_free_table_columns(table_definition_t *td) {
430   if (td->columns == NULL)
431     return;
432
433   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
434     data_definition_t *dd = de->value;
435
436     if (td->index_oid.oid_len) {
437       int *index;
438       char *instance;
439
440       c_avl_iterator_t *iter = c_avl_get_iterator(td->index_instance);
441       while (c_avl_iterator_next(iter, (void *)&index, (void *)&instance) ==
442              0) {
443         for (size_t i = 0; i < dd->oids_len; i++)
444           snmp_agent_unregister_oid_index(&dd->oids[i], *index);
445       }
446       c_avl_iterator_destroy(iter);
447     } else {
448       char *instance;
449
450       c_avl_iterator_t *iter = c_avl_get_iterator(dd->table->instance_index);
451       while (c_avl_iterator_next(iter, (void *)&instance, (void *)&instance) ==
452              0) {
453         for (size_t i = 0; i < dd->oids_len; i++)
454           snmp_agent_unregister_oid_string(&dd->oids[i], instance);
455       }
456       c_avl_iterator_destroy(iter);
457     }
458
459     snmp_agent_free_data(&dd);
460   }
461
462   llist_destroy(td->columns);
463   td->columns = NULL;
464 } /* void snmp_agent_free_table_columns */
465
466 static void snmp_agent_free_table(table_definition_t **td) {
467
468   if (td == NULL || *td == NULL)
469     return;
470
471   if ((*td)->size_oid.oid_len)
472     unregister_mib((*td)->size_oid.oid, (*td)->size_oid.oid_len);
473
474   /* Unregister Index OIDs */
475   if ((*td)->index_oid.oid_len) {
476     int *index;
477     char *instance;
478
479     c_avl_iterator_t *iter = c_avl_get_iterator((*td)->index_instance);
480     while (c_avl_iterator_next(iter, (void *)&index, (void *)&instance) == 0)
481       snmp_agent_unregister_oid_index(&(*td)->index_oid, *index);
482
483     c_avl_iterator_destroy(iter);
484   }
485
486   /* Unregister all table columns and their registered OIDs */
487   snmp_agent_free_table_columns(*td);
488
489   void *key = NULL;
490   void *value = NULL;
491
492   /* index_instance and instance_index contain the same pointers */
493   c_avl_destroy((*td)->index_instance);
494   (*td)->index_instance = NULL;
495
496   if ((*td)->instance_index != NULL) {
497     while (c_avl_pick((*td)->instance_index, &key, &value) == 0) {
498       if (key != value)
499         sfree(key);
500       sfree(value);
501     }
502     c_avl_destroy((*td)->instance_index);
503     (*td)->instance_index = NULL;
504   }
505
506   sfree((*td)->name);
507   sfree(*td);
508
509   return;
510 }
511
512 static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests,
513                                  data_definition_t *dd, char *instance,
514                                  int oid_index) {
515   char name[DATA_MAX_NAME_LEN];
516   format_name(name, sizeof(name), hostname_g, dd->plugin,
517               instance ? instance : dd->plugin_instance, dd->type,
518               dd->type_instance);
519   DEBUG(PLUGIN_NAME ": Identifier '%s'", name);
520
521   value_t *values;
522   size_t values_num;
523   const data_set_t *ds = plugin_get_ds(dd->type);
524   if (ds == NULL) {
525     ERROR(PLUGIN_NAME ": Data set not found for '%s' type", dd->type);
526     return SNMP_NOSUCHINSTANCE;
527   }
528
529   int ret = uc_get_value_by_name(name, &values, &values_num);
530
531   if (ret != 0) {
532     ERROR(PLUGIN_NAME ": Failed to get value for '%s'", name);
533     return SNMP_NOSUCHINSTANCE;
534   }
535
536   assert(ds->ds_num == values_num);
537   assert(oid_index < (int)values_num);
538
539   char data[DATA_MAX_NAME_LEN];
540   size_t data_len = sizeof(data);
541   ret = snmp_agent_set_vardata(
542       data, &data_len, dd->oids[oid_index].type, dd->scale, dd->shift,
543       &values[oid_index], sizeof(values[oid_index]), ds->ds[oid_index].type);
544
545   sfree(values);
546
547   if (ret != 0) {
548     ERROR(PLUGIN_NAME ": Failed to convert '%s' value to snmp data", name);
549     return SNMP_NOSUCHINSTANCE;
550   }
551
552   requests->requestvb->type = dd->oids[oid_index].type;
553   snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
554                            (const u_char *)data, data_len);
555
556   return SNMP_ERR_NOERROR;
557 }
558
559 static int
560 snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
561                              struct netsnmp_handler_registration_s *reginfo,
562                              struct netsnmp_agent_request_info_s *reqinfo,
563                              struct netsnmp_request_info_s *requests) {
564
565   if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
566     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
567     return SNMP_ERR_NOERROR;
568   }
569
570   pthread_mutex_lock(&g_agent->lock);
571
572   oid_t oid;
573   memcpy(oid.oid, requests->requestvb->name,
574          sizeof(oid.oid[0]) * requests->requestvb->name_length);
575   oid.oid_len = requests->requestvb->name_length;
576
577 #if COLLECT_DEBUG
578   char oid_str[DATA_MAX_NAME_LEN];
579   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid);
580   DEBUG(PLUGIN_NAME ": Get request received for table OID '%s'", oid_str);
581 #endif
582
583   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
584     table_definition_t *td = te->value;
585
586     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
587       data_definition_t *dd = de->value;
588
589       for (size_t i = 0; i < dd->oids_len; i++) {
590         int ret = snmp_oid_ncompare(oid.oid, oid.oid_len, dd->oids[i].oid,
591                                     dd->oids[i].oid_len,
592                                     SNMP_MIN(oid.oid_len, dd->oids[i].oid_len));
593         if (ret != 0)
594           continue;
595
596         char *instance;
597
598         if (!td->index_oid.oid_len) {
599           char key[MAX_OID_LEN];
600
601           memset(key, 0, sizeof(key));
602           snmp_agent_generate_oid2string(
603               &oid, SNMP_MIN(oid.oid_len, dd->oids[i].oid_len), key);
604
605           ret = c_avl_get(td->instance_index, key, (void **)&instance);
606           if (ret != 0) {
607             DEBUG(PLUGIN_NAME ": Nonexisting index string '%s' requested", key);
608             pthread_mutex_unlock(&g_agent->lock);
609             return SNMP_NOSUCHINSTANCE;
610           }
611         } else {
612           int index = oid.oid[oid.oid_len - 1];
613
614           ret = c_avl_get(td->index_instance, &index, (void **)&instance);
615           if (ret != 0) {
616             DEBUG(PLUGIN_NAME ": Nonexisting index '%d' requested", index);
617             pthread_mutex_unlock(&g_agent->lock);
618             return SNMP_NOSUCHINSTANCE;
619           }
620         }
621
622         if (dd->is_instance) {
623           requests->requestvb->type = ASN_OCTET_STR;
624           snmp_set_var_typed_value(
625               requests->requestvb, requests->requestvb->type,
626               (const u_char *)instance, strlen((instance)));
627
628           pthread_mutex_unlock(&g_agent->lock);
629
630           return SNMP_ERR_NOERROR;
631         }
632
633         ret = snmp_agent_form_reply(requests, dd, instance, i);
634
635         pthread_mutex_unlock(&g_agent->lock);
636
637         return ret;
638       }
639     }
640   }
641
642   pthread_mutex_unlock(&g_agent->lock);
643
644   return SNMP_NOSUCHINSTANCE;
645 }
646
647 static int snmp_agent_table_index_oid_handler(
648     struct netsnmp_mib_handler_s *handler,
649     struct netsnmp_handler_registration_s *reginfo,
650     struct netsnmp_agent_request_info_s *reqinfo,
651     struct netsnmp_request_info_s *requests) {
652
653   if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
654     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
655     return SNMP_ERR_NOERROR;
656   }
657
658   pthread_mutex_lock(&g_agent->lock);
659
660   oid_t oid;
661   memcpy(oid.oid, requests->requestvb->name,
662          sizeof(oid.oid[0]) * requests->requestvb->name_length);
663   oid.oid_len = requests->requestvb->name_length;
664
665   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
666     table_definition_t *td = te->value;
667
668     if (td->index_oid.oid_len &&
669         (snmp_oid_ncompare(
670              oid.oid, oid.oid_len, td->index_oid.oid, td->index_oid.oid_len,
671              SNMP_MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) {
672
673       DEBUG(PLUGIN_NAME ": Handle '%s' table index OID", td->name);
674
675       int index = oid.oid[oid.oid_len - 1];
676
677       int ret = c_avl_get(td->index_instance, &index, &(void *){NULL});
678       if (ret != 0) {
679         /* nonexisting index requested */
680         pthread_mutex_unlock(&g_agent->lock);
681         return SNMP_NOSUCHINSTANCE;
682       }
683
684       requests->requestvb->type = ASN_INTEGER;
685       snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
686                                (const u_char *)&index, sizeof(index));
687
688       pthread_mutex_unlock(&g_agent->lock);
689
690       return SNMP_ERR_NOERROR;
691     }
692   }
693
694   pthread_mutex_unlock(&g_agent->lock);
695
696   return SNMP_NOSUCHINSTANCE;
697 }
698
699 static int snmp_agent_table_size_oid_handler(
700     struct netsnmp_mib_handler_s *handler,
701     struct netsnmp_handler_registration_s *reginfo,
702     struct netsnmp_agent_request_info_s *reqinfo,
703     struct netsnmp_request_info_s *requests) {
704
705   if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
706     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
707     return SNMP_ERR_NOERROR;
708   }
709
710   pthread_mutex_lock(&g_agent->lock);
711
712   oid_t oid;
713   memcpy(oid.oid, requests->requestvb->name,
714          sizeof(oid.oid[0]) * requests->requestvb->name_length);
715   oid.oid_len = requests->requestvb->name_length;
716
717   DEBUG(PLUGIN_NAME ": Get request received for table size OID");
718
719   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
720     table_definition_t *td = te->value;
721
722     if (td->size_oid.oid_len &&
723         (snmp_oid_ncompare(oid.oid, oid.oid_len, td->size_oid.oid,
724                            td->size_oid.oid_len,
725                            SNMP_MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) {
726       DEBUG(PLUGIN_NAME ": Handle '%s' table size OID", td->name);
727
728       long size = c_avl_size(td->index_instance);
729
730       requests->requestvb->type = ASN_INTEGER;
731       snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
732                                (const u_char *)&size, sizeof(size));
733
734       pthread_mutex_unlock(&g_agent->lock);
735
736       return SNMP_ERR_NOERROR;
737     }
738   }
739
740   pthread_mutex_unlock(&g_agent->lock);
741
742   return SNMP_NOSUCHINSTANCE;
743 }
744
745 static int
746 snmp_agent_scalar_oid_handler(struct netsnmp_mib_handler_s *handler,
747                               struct netsnmp_handler_registration_s *reginfo,
748                               struct netsnmp_agent_request_info_s *reqinfo,
749                               struct netsnmp_request_info_s *requests) {
750
751   if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
752     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
753     return SNMP_ERR_NOERROR;
754   }
755
756   pthread_mutex_lock(&g_agent->lock);
757
758   oid_t oid;
759   memcpy(oid.oid, requests->requestvb->name,
760          sizeof(oid.oid[0]) * requests->requestvb->name_length);
761   oid.oid_len = requests->requestvb->name_length;
762
763 #if COLLECT_DEBUG
764   char oid_str[DATA_MAX_NAME_LEN];
765   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid);
766   DEBUG(PLUGIN_NAME ": Get request received for scalar OID '%s'", oid_str);
767 #endif
768
769   for (llentry_t *de = llist_head(g_agent->scalars); de != NULL;
770        de = de->next) {
771     data_definition_t *dd = de->value;
772
773     for (size_t i = 0; i < dd->oids_len; i++) {
774
775       int ret = snmp_oid_compare(oid.oid, oid.oid_len, dd->oids[i].oid,
776                                  dd->oids[i].oid_len);
777       if (ret != 0)
778         continue;
779
780       ret = snmp_agent_form_reply(requests, dd, NULL, i);
781
782       pthread_mutex_unlock(&g_agent->lock);
783
784       return ret;
785     }
786   }
787
788   pthread_mutex_unlock(&g_agent->lock);
789
790   return SNMP_NOSUCHINSTANCE;
791 }
792
793 static int snmp_agent_register_table_oids(void) {
794
795   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
796     table_definition_t *td = te->value;
797
798     if (td->size_oid.oid_len != 0) {
799       td->size_oid.type =
800           snmp_agent_get_asn_type(td->size_oid.oid, td->size_oid.oid_len);
801       td->size_oid.oid_len++;
802       int ret = snmp_agent_register_oid(&td->size_oid,
803                                         snmp_agent_table_size_oid_handler);
804       if (ret != 0)
805         return ret;
806     }
807
808     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
809       data_definition_t *dd = de->value;
810
811       for (size_t i = 0; i < dd->oids_len; i++) {
812         dd->oids[i].type =
813             snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
814       }
815     }
816   }
817
818   return 0;
819 }
820
821 static int snmp_agent_register_scalar_oids(void) {
822
823   for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
824     data_definition_t *dd = e->value;
825
826     for (size_t i = 0; i < dd->oids_len; i++) {
827
828       dd->oids[i].type =
829           snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
830
831       int ret =
832           snmp_agent_register_oid(&dd->oids[i], snmp_agent_scalar_oid_handler);
833       if (ret != 0)
834         return ret;
835     }
836   }
837
838   return 0;
839 }
840
841 static int snmp_agent_config_data_oids(data_definition_t *dd,
842                                        oconfig_item_t *ci) {
843   if (ci->values_num < 1) {
844     WARNING(PLUGIN_NAME ": `OIDs' needs at least one argument");
845     return -EINVAL;
846   }
847
848   for (int i = 0; i < ci->values_num; i++)
849     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
850       WARNING(PLUGIN_NAME ": `OIDs' needs only string argument");
851       return -EINVAL;
852     }
853
854   if (dd->oids != NULL)
855     sfree(dd->oids);
856   dd->oids_len = 0;
857   dd->oids = calloc(ci->values_num, sizeof(*dd->oids));
858   if (dd->oids == NULL)
859     return -ENOMEM;
860   dd->oids_len = (size_t)ci->values_num;
861
862   for (int i = 0; i < ci->values_num; i++) {
863     dd->oids[i].oid_len = MAX_OID_LEN;
864
865     if (NULL == snmp_parse_oid(ci->values[i].value.string, dd->oids[i].oid,
866                                &dd->oids[i].oid_len)) {
867       ERROR(PLUGIN_NAME ": snmp_parse_oid (%s) failed",
868             ci->values[i].value.string);
869       sfree(dd->oids);
870       dd->oids_len = 0;
871       return -1;
872     }
873   }
874
875   return 0;
876 }
877
878 static int snmp_agent_config_table_size_oid(table_definition_t *td,
879                                             oconfig_item_t *ci) {
880   if (ci->values_num < 1) {
881     WARNING(PLUGIN_NAME ": `TableSizeOID' is empty");
882     return -EINVAL;
883   }
884
885   if (ci->values[0].type != OCONFIG_TYPE_STRING) {
886     WARNING(PLUGIN_NAME ": `TableSizeOID' needs only string argument");
887     return -EINVAL;
888   }
889
890   td->size_oid.oid_len = MAX_OID_LEN;
891
892   if (NULL == snmp_parse_oid(ci->values[0].value.string, td->size_oid.oid,
893                              &td->size_oid.oid_len)) {
894     ERROR(PLUGIN_NAME ": Failed to parse table size OID (%s)",
895           ci->values[0].value.string);
896     td->size_oid.oid_len = 0;
897     return -EINVAL;
898   }
899
900   return 0;
901 }
902
903 static int snmp_agent_config_table_index_oid(table_definition_t *td,
904                                              oconfig_item_t *ci) {
905
906   if (ci->values_num < 1) {
907     WARNING(PLUGIN_NAME ": `IndexOID' is empty");
908     return -EINVAL;
909   }
910
911   if (ci->values[0].type != OCONFIG_TYPE_STRING) {
912     WARNING(PLUGIN_NAME ": `IndexOID' needs only string argument");
913     return -EINVAL;
914   }
915
916   td->index_oid.oid_len = MAX_OID_LEN;
917
918   if (NULL == snmp_parse_oid(ci->values[0].value.string, td->index_oid.oid,
919                              &td->index_oid.oid_len)) {
920     ERROR(PLUGIN_NAME ": Failed to parse table index OID (%s)",
921           ci->values[0].value.string);
922     td->index_oid.oid_len = 0;
923     return -EINVAL;
924   }
925
926   return 0;
927 }
928
929 /* This function parses configuration of both scalar and table column
930  * because they have nearly the same structure */
931 static int snmp_agent_config_table_column(table_definition_t *td,
932                                           oconfig_item_t *ci) {
933   data_definition_t *dd;
934   int ret = 0;
935
936   assert(ci != NULL);
937
938   dd = calloc(1, sizeof(*dd));
939   if (dd == NULL) {
940     ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition");
941     return -ENOMEM;
942   }
943
944   ret = cf_util_get_string(ci, &dd->name);
945   if (ret != 0) {
946     sfree(dd);
947     return -1;
948   }
949
950   dd->scale = 1.0;
951   dd->shift = 0.0;
952   /* NULL if it's a scalar */
953   dd->table = td;
954
955   for (int i = 0; i < ci->children_num; i++) {
956     oconfig_item_t *option = ci->children + i;
957
958     /* Instance option is reserved for table entry only */
959     if (strcasecmp("Instance", option->key) == 0 && td != NULL)
960       ret = cf_util_get_boolean(option, &dd->is_instance);
961     else if (strcasecmp("Plugin", option->key) == 0)
962       ret = cf_util_get_string(option, &dd->plugin);
963     else if (strcasecmp("PluginInstance", option->key) == 0)
964       ret = cf_util_get_string(option, &dd->plugin_instance);
965     else if (strcasecmp("Type", option->key) == 0)
966       ret = cf_util_get_string(option, &dd->type);
967     else if (strcasecmp("TypeInstance", option->key) == 0)
968       ret = cf_util_get_string(option, &dd->type_instance);
969     else if (strcasecmp("Shift", option->key) == 0)
970       ret = cf_util_get_double(option, &dd->shift);
971     else if (strcasecmp("Scale", option->key) == 0)
972       ret = cf_util_get_double(option, &dd->scale);
973     else if (strcasecmp("OIDs", option->key) == 0)
974       ret = snmp_agent_config_data_oids(dd, option);
975     else {
976       WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
977       ret = -1;
978     }
979
980     if (ret != 0) {
981       snmp_agent_free_data(&dd);
982       return -1;
983     }
984   }
985
986   llentry_t *entry = llentry_create(dd->name, dd);
987   if (entry == NULL) {
988     snmp_agent_free_data(&dd);
989     return -ENOMEM;
990   }
991
992   /* Append to column list in parent table */
993   if (td != NULL)
994     llist_append(td->columns, entry);
995
996   return 0;
997 }
998
999 /* Parses scalar configuration entry */
1000 static int snmp_agent_config_scalar(oconfig_item_t *ci) {
1001   return snmp_agent_config_table_column(NULL, ci);
1002 }
1003
1004 static int num_compare(const int *a, const int *b) {
1005   assert((a != NULL) && (b != NULL));
1006   if (*a < *b)
1007     return -1;
1008   else if (*a > *b)
1009     return 1;
1010   else
1011     return 0;
1012 }
1013
1014 static int snmp_agent_config_table(oconfig_item_t *ci) {
1015   table_definition_t *td;
1016   int ret = 0;
1017
1018   assert(ci != NULL);
1019
1020   td = calloc(1, sizeof(*td));
1021   if (td == NULL) {
1022     ERROR(PLUGIN_NAME ": Failed to allocate memory for table definition");
1023     return -ENOMEM;
1024   }
1025
1026   ret = cf_util_get_string(ci, &td->name);
1027   if (ret != 0) {
1028     sfree(td);
1029     return -1;
1030   }
1031
1032   td->columns = llist_create();
1033   if (td->columns == NULL) {
1034     ERROR(PLUGIN_NAME ": Failed to allocate memory for columns list");
1035     snmp_agent_free_table(&td);
1036     return -ENOMEM;
1037   }
1038
1039   for (int i = 0; i < ci->children_num; i++) {
1040     oconfig_item_t *option = ci->children + i;
1041
1042     if (strcasecmp("IndexOID", option->key) == 0)
1043       ret = snmp_agent_config_table_index_oid(td, option);
1044     else if (strcasecmp("SizeOID", option->key) == 0)
1045       ret = snmp_agent_config_table_size_oid(td, option);
1046     else if (strcasecmp("Data", option->key) == 0)
1047       ret = snmp_agent_config_table_column(td, option);
1048     else {
1049       WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
1050       ret = -1;
1051     }
1052
1053     if (ret != 0) {
1054       snmp_agent_free_table(&td);
1055       return -ENOMEM;
1056     }
1057   }
1058
1059   td->instance_index =
1060       c_avl_create((int (*)(const void *, const void *))strcmp);
1061   if (td->instance_index == NULL) {
1062     snmp_agent_free_table(&td);
1063     return -ENOMEM;
1064   }
1065
1066   td->index_instance =
1067       c_avl_create((int (*)(const void *, const void *))num_compare);
1068   if (td->index_instance == NULL) {
1069     snmp_agent_free_table(&td);
1070     return -ENOMEM;
1071   }
1072
1073   llentry_t *entry = llentry_create(td->name, td);
1074   if (entry == NULL) {
1075     snmp_agent_free_table(&td);
1076     return -ENOMEM;
1077   }
1078   llist_append(g_agent->tables, entry);
1079
1080   return 0;
1081 }
1082
1083 static int snmp_agent_get_value_from_ds_type(const value_t *val, int type,
1084                                              double scale, double shift,
1085                                              long *value) {
1086   switch (type) {
1087   case DS_TYPE_COUNTER:
1088     *value = (long)((val->counter * scale) + shift);
1089     break;
1090   case DS_TYPE_ABSOLUTE:
1091     *value = (long)((val->absolute * scale) + shift);
1092     break;
1093   case DS_TYPE_DERIVE:
1094     *value = (long)((val->derive * scale) + shift);
1095     break;
1096   case DS_TYPE_GAUGE:
1097     *value = (long)((val->gauge * scale) + shift);
1098     break;
1099   case TYPE_STRING:
1100     break;
1101   default:
1102     ERROR(PLUGIN_NAME ": Unknown data source type: %i", type);
1103     return -EINVAL;
1104   }
1105
1106   return 0;
1107 }
1108
1109 static int snmp_agent_set_vardata(void *data, size_t *data_len, u_char asn_type,
1110                                   double scale, double shift, const void *value,
1111                                   size_t len, int type) {
1112
1113   int ret;
1114   netsnmp_vardata var;
1115   const value_t *val;
1116   long new_value = 0;
1117
1118   val = value;
1119   var.string = (u_char *)data;
1120
1121   ret = snmp_agent_get_value_from_ds_type(val, type, scale, shift, &new_value);
1122   if (ret != 0)
1123     return ret;
1124
1125   switch (asn_type) {
1126   case ASN_INTEGER:
1127   case ASN_UINTEGER:
1128   case ASN_COUNTER:
1129   case ASN_TIMETICKS:
1130   case ASN_GAUGE:
1131     if (*data_len < sizeof(*var.integer))
1132       return -EINVAL;
1133     *var.integer = new_value;
1134     *data_len = sizeof(*var.integer);
1135     break;
1136   case ASN_COUNTER64:
1137     if (*data_len < sizeof(*var.counter64))
1138       return -EINVAL;
1139     var.counter64->high = (u_long)((int64_t)new_value >> 32);
1140     var.counter64->low = (u_long)((int64_t)new_value & 0xFFFFFFFF);
1141     *data_len = sizeof(*var.counter64);
1142     break;
1143   case ASN_OCTET_STR:
1144     if (type == DS_TYPE_GAUGE) {
1145       char buf[DATA_MAX_NAME_LEN];
1146       snprintf(buf, sizeof(buf), "%.2f", val->gauge);
1147       if (*data_len < strlen(buf))
1148         return -EINVAL;
1149       *data_len = strlen(buf);
1150       memcpy(var.string, buf, *data_len);
1151     } else {
1152       ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type,
1153             asn_type);
1154       return -EINVAL;
1155     }
1156     break;
1157   default:
1158     ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type,
1159           asn_type);
1160     return -EINVAL;
1161   }
1162
1163   return 0;
1164 }
1165
1166 static int snmp_agent_register_oid_index(oid_t *oid, int index,
1167                                          Netsnmp_Node_Handler *handler) {
1168   oid_t new_oid;
1169   memcpy(&new_oid, oid, sizeof(*oid));
1170   new_oid.oid[new_oid.oid_len++] = index;
1171   return snmp_agent_register_oid(&new_oid, handler);
1172 }
1173
1174 static int snmp_agent_unregister_oid_index(oid_t *oid, int index) {
1175   oid_t new_oid;
1176   memcpy(&new_oid, oid, sizeof(*oid));
1177   new_oid.oid[new_oid.oid_len++] = index;
1178   return unregister_mib(new_oid.oid, new_oid.oid_len);
1179 }
1180
1181 static int snmp_agent_update_index(table_definition_t *td,
1182                                    const char *instance) {
1183
1184   if (c_avl_get(td->instance_index, instance, NULL) == 0)
1185     return 0;
1186
1187   int ret;
1188   int *index = NULL;
1189   char *ins;
1190
1191   ins = strdup(instance);
1192   if (ins == NULL)
1193     return -ENOMEM;
1194
1195   /* need to generate index for the table */
1196   if (td->index_oid.oid_len) {
1197     index = calloc(1, sizeof(*index));
1198     if (index == NULL) {
1199       sfree(ins);
1200       return -ENOMEM;
1201     }
1202
1203     *index = c_avl_size(td->instance_index) + 1;
1204
1205     ret = c_avl_insert(td->instance_index, ins, index);
1206     if (ret != 0) {
1207       sfree(ins);
1208       sfree(index);
1209       return ret;
1210     }
1211
1212     ret = c_avl_insert(td->index_instance, index, ins);
1213     if (ret < 0) {
1214       DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table",
1215             td->name);
1216       c_avl_remove(td->instance_index, ins, NULL, (void **)&index);
1217       sfree(ins);
1218       sfree(index);
1219       return ret;
1220     }
1221
1222     ret = snmp_agent_register_oid_index(&td->index_oid, *index,
1223                                         snmp_agent_table_index_oid_handler);
1224     if (ret != 0)
1225       return ret;
1226   } else {
1227     /* instance as a key is required for any table */
1228     ret = c_avl_insert(td->instance_index, ins, ins);
1229     if (ret != 0) {
1230       sfree(ins);
1231       return ret;
1232     }
1233   }
1234
1235   /* register new oids for all columns */
1236   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
1237     data_definition_t *dd = de->value;
1238
1239     for (size_t i = 0; i < dd->oids_len; i++) {
1240       if (td->index_oid.oid_len) {
1241         ret = snmp_agent_register_oid_index(&dd->oids[i], *index,
1242                                             snmp_agent_table_oid_handler);
1243       } else {
1244         ret = snmp_agent_register_oid_string(&dd->oids[i], ins,
1245                                              snmp_agent_table_oid_handler);
1246       }
1247
1248       if (ret != 0)
1249         return ret;
1250     }
1251   }
1252
1253   DEBUG(PLUGIN_NAME ": Updated index for '%s' table [%d, %s]", td->name,
1254         (index != NULL) ? *index : -1, ins);
1255
1256   notification_t n = {
1257       .severity = NOTIF_OKAY, .time = cdtime(), .plugin = PLUGIN_NAME};
1258   sstrncpy(n.host, hostname_g, sizeof(n.host));
1259   sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
1260   snprintf(n.message, sizeof(n.message),
1261            "Data row added to table %s instance %s index %d", td->name, ins,
1262            (index != NULL) ? *index : -1);
1263   plugin_dispatch_notification(&n);
1264
1265   return 0;
1266 }
1267
1268 static int snmp_agent_write(value_list_t const *vl) {
1269
1270   if (vl == NULL)
1271     return -EINVAL;
1272
1273   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
1274     table_definition_t *td = te->value;
1275
1276     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
1277       data_definition_t *dd = de->value;
1278
1279       if (!dd->is_instance) {
1280         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
1281                           vl->type_instance)) {
1282           snmp_agent_update_index(td, vl->plugin_instance);
1283           return 0;
1284         }
1285       }
1286     }
1287   }
1288
1289   return 0;
1290 }
1291
1292 static int snmp_agent_collect(const data_set_t *ds, const value_list_t *vl,
1293                               user_data_t __attribute__((unused)) * user_data) {
1294
1295   pthread_mutex_lock(&g_agent->lock);
1296
1297   snmp_agent_write(vl);
1298
1299   pthread_mutex_unlock(&g_agent->lock);
1300
1301   return 0;
1302 }
1303
1304 static int snmp_agent_preinit(void) {
1305
1306   g_agent = calloc(1, sizeof(*g_agent));
1307   if (g_agent == NULL) {
1308     ERROR(PLUGIN_NAME ": Failed to allocate memory for snmp agent context");
1309     return -ENOMEM;
1310   }
1311
1312   g_agent->tables = llist_create();
1313   g_agent->scalars = llist_create();
1314
1315   if (g_agent->tables == NULL || g_agent->scalars == NULL) {
1316     ERROR(PLUGIN_NAME ": llist_create() failed");
1317     llist_destroy(g_agent->scalars);
1318     llist_destroy(g_agent->tables);
1319     return -ENOMEM;
1320   }
1321
1322   int err;
1323   /* make us an agentx client. */
1324   err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE,
1325                                1);
1326   if (err != 0) {
1327     ERROR(PLUGIN_NAME ": Failed to set agent role (%d)", err);
1328     llist_destroy(g_agent->scalars);
1329     llist_destroy(g_agent->tables);
1330     return -1;
1331   }
1332
1333   /*
1334    *  For SNMP debug purposes uses snmp_set_do_debugging(1);
1335    */
1336
1337   /* initialize the agent library */
1338   err = init_agent(PLUGIN_NAME);
1339   if (err != 0) {
1340     ERROR(PLUGIN_NAME ": Failed to initialize the agent library (%d)", err);
1341     llist_destroy(g_agent->scalars);
1342     llist_destroy(g_agent->tables);
1343     return -1;
1344   }
1345
1346   init_snmp(PLUGIN_NAME);
1347
1348   g_agent->tp = read_all_mibs();
1349
1350   return 0;
1351 }
1352
1353 static int snmp_agent_init(void) {
1354   int ret;
1355
1356   if (g_agent == NULL || ((llist_head(g_agent->scalars) == NULL) &&
1357                           (llist_head(g_agent->tables) == NULL))) {
1358     ERROR(PLUGIN_NAME ": snmp_agent_init: plugin not configured");
1359     return -EINVAL;
1360   }
1361
1362   plugin_register_shutdown(PLUGIN_NAME, snmp_agent_shutdown);
1363
1364   ret = snmp_agent_register_scalar_oids();
1365   if (ret != 0)
1366     return ret;
1367
1368   ret = snmp_agent_register_table_oids();
1369   if (ret != 0)
1370     return ret;
1371
1372   ret = pthread_mutex_init(&g_agent->lock, NULL);
1373   if (ret != 0) {
1374     ERROR(PLUGIN_NAME ": Failed to initialize mutex, err %u", ret);
1375     return ret;
1376   }
1377
1378   ret = pthread_mutex_init(&g_agent->agentx_lock, NULL);
1379   if (ret != 0) {
1380     ERROR(PLUGIN_NAME ": Failed to initialize AgentX mutex, err %u", ret);
1381     return ret;
1382   }
1383
1384   /* create a second thread to listen for requests from AgentX*/
1385   ret = pthread_create(&g_agent->thread, NULL, &snmp_agent_thread_run, NULL);
1386   if (ret != 0) {
1387     ERROR(PLUGIN_NAME ": Failed to create a separate thread, err %u", ret);
1388     return ret;
1389   }
1390
1391   if (llist_head(g_agent->tables) != NULL) {
1392     plugin_register_write(PLUGIN_NAME, snmp_agent_collect, NULL);
1393     plugin_register_missing(PLUGIN_NAME, snmp_agent_clear_missing, NULL);
1394   }
1395
1396   return 0;
1397 }
1398
1399 static void *snmp_agent_thread_run(void __attribute__((unused)) * arg) {
1400   INFO(PLUGIN_NAME ": Thread is up and running");
1401
1402   for (;;) {
1403     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
1404
1405     pthread_mutex_lock(&g_agent->agentx_lock);
1406     agent_check_and_process(0); /* 0 == don't block */
1407     pthread_mutex_unlock(&g_agent->agentx_lock);
1408
1409     pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
1410     usleep(10);
1411   }
1412
1413   pthread_exit(0);
1414 }
1415
1416 static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler) {
1417   netsnmp_handler_registration *reg;
1418   char *oid_name = snmp_agent_get_oid_name(oid->oid, oid->oid_len - 1);
1419   char oid_str[DATA_MAX_NAME_LEN];
1420
1421   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), oid);
1422
1423   if (oid_name == NULL) {
1424     WARNING(PLUGIN_NAME
1425             ": Skipped registration: OID (%s) is not found in main tree",
1426             oid_str);
1427     return 0;
1428   }
1429
1430   reg = netsnmp_create_handler_registration(oid_name, handler, oid->oid,
1431                                             oid->oid_len, HANDLER_CAN_RONLY);
1432   if (reg == NULL) {
1433     ERROR(PLUGIN_NAME ": Failed to create handler registration for OID (%s)",
1434           oid_str);
1435     return -1;
1436   }
1437
1438   pthread_mutex_lock(&g_agent->agentx_lock);
1439
1440   if (netsnmp_register_instance(reg) != MIB_REGISTERED_OK) {
1441     ERROR(PLUGIN_NAME ": Failed to register handler for OID (%s)", oid_str);
1442     pthread_mutex_unlock(&g_agent->agentx_lock);
1443     return -1;
1444   }
1445
1446   pthread_mutex_unlock(&g_agent->agentx_lock);
1447
1448   DEBUG(PLUGIN_NAME ": Registered handler for OID (%s)", oid_str);
1449
1450   return 0;
1451 }
1452
1453 static int snmp_agent_free_config(void) {
1454
1455   if (g_agent == NULL)
1456     return -EINVAL;
1457
1458   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next)
1459     snmp_agent_free_table((table_definition_t **)&te->value);
1460   llist_destroy(g_agent->tables);
1461
1462   for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; de = de->next)
1463     snmp_agent_free_data((data_definition_t **)&de->value);
1464   llist_destroy(g_agent->scalars);
1465
1466   return 0;
1467 }
1468
1469 static int snmp_agent_shutdown(void) {
1470   int ret = 0;
1471
1472   DEBUG(PLUGIN_NAME ": snmp_agent_shutdown");
1473
1474   if (g_agent == NULL) {
1475     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: plugin not initialized");
1476     return -EINVAL;
1477   }
1478
1479   if (pthread_cancel(g_agent->thread) != 0)
1480     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to cancel the thread");
1481
1482   if (pthread_join(g_agent->thread, NULL) != 0)
1483     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to join the thread");
1484
1485   snmp_agent_free_config();
1486
1487   snmp_shutdown(PLUGIN_NAME);
1488
1489   pthread_mutex_destroy(&g_agent->lock);
1490   pthread_mutex_destroy(&g_agent->agentx_lock);
1491
1492   sfree(g_agent);
1493
1494   return ret;
1495 }
1496
1497 static int snmp_agent_config(oconfig_item_t *ci) {
1498
1499   int ret = snmp_agent_preinit();
1500
1501   if (ret != 0) {
1502     sfree(g_agent);
1503     return -EINVAL;
1504   }
1505
1506   for (int i = 0; i < ci->children_num; i++) {
1507     oconfig_item_t *child = ci->children + i;
1508     if (strcasecmp("Data", child->key) == 0) {
1509       ret = snmp_agent_config_scalar(child);
1510     } else if (strcasecmp("Table", child->key) == 0) {
1511       ret = snmp_agent_config_table(child);
1512     } else {
1513       ERROR(PLUGIN_NAME ": Unknown configuration option `%s'", child->key);
1514       ret = (-EINVAL);
1515     }
1516
1517     if (ret != 0) {
1518       ERROR(PLUGIN_NAME ": Failed to parse configuration");
1519       snmp_agent_free_config();
1520       snmp_shutdown(PLUGIN_NAME);
1521       sfree(g_agent);
1522       return -EINVAL;
1523     }
1524   }
1525
1526   ret = snmp_agent_validate_config();
1527   if (ret != 0) {
1528     ERROR(PLUGIN_NAME ": Invalid configuration provided");
1529     snmp_agent_free_config();
1530     snmp_shutdown(PLUGIN_NAME);
1531     sfree(g_agent);
1532     return -EINVAL;
1533   }
1534
1535   return 0;
1536 }
1537
1538 void module_register(void) {
1539   plugin_register_init(PLUGIN_NAME, snmp_agent_init);
1540   plugin_register_complex_config(PLUGIN_NAME, snmp_agent_config);
1541 }