root/src/modules/snmp.c

Revision 304ec80b8cf842fc0abe5f9029790908b6455957, 56.8 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 months ago)

Convert to libmtev.

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  * Copyright (c) 2010-2015, Circonus, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  *       notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  *       copyright notice, this list of conditions and the following
14  *       disclaimer in the documentation and/or other materials provided
15  *       with the distribution.
16  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
17  *       of its contributors may be used to endorse or promote products
18  *       derived from this software without specific prior written
19  *       permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include <mtev_defines.h>
35
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <sys/mman.h>
39 #include <errno.h>
40 #include <assert.h>
41 #include <math.h>
42 #include <ctype.h>
43 #include <arpa/inet.h>
44
45 #include <net-snmp/net-snmp-config.h>
46 #include <net-snmp/net-snmp-includes.h>
47
48 #include <mtev_hash.h>
49
50 #include "noit_module.h"
51 #include "noit_check.h"
52 #include "noit_check_tools.h"
53 #include "noit_mtev_bridge.h"
54
55 static mtev_log_stream_t nlerr = NULL;
56 static mtev_log_stream_t nldeb = NULL;
57 static int __snmp_initialize_once = 0;
58 static void ensure_usm_user(const char *username, u_char *engineID, size_t engineIDLen);
59
60 #define SNMPV2_TRAPS_PREFIX     SNMP_OID_SNMPMODULES,1,1,5
61 oid trap_prefix[]    = { SNMPV2_TRAPS_PREFIX };
62 oid cold_start_oid[] = { SNMPV2_TRAPS_PREFIX, 1 };  /* SNMPv2-MIB */
63 oid warm_start_oid[] = { SNMPV2_TRAPS_PREFIX, 2 };  /* SNMPv2-MIB */
64 oid link_down_oid[]  = { SNMPV2_TRAPS_PREFIX, 3 };  /* IF-MIB */
65 oid link_up_oid[]    = { SNMPV2_TRAPS_PREFIX, 4 };  /* IF-MIB */
66 oid auth_fail_oid[]  = { SNMPV2_TRAPS_PREFIX, 5 };  /* SNMPv2-MIB */
67 oid egp_xxx_oid[]    = { SNMPV2_TRAPS_PREFIX, 99 }; /* ??? */
68
69 #define SNMPV2_TRAP_OBJS_PREFIX SNMP_OID_SNMPMODULES,1,1,4
70 oid snmptrap_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 1, 0 };
71 size_t snmptrap_oid_len = OID_LENGTH(snmptrap_oid);
72 oid snmptrapenterprise_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 3, 0 };
73 size_t snmptrapenterprise_oid_len = OID_LENGTH(snmptrapenterprise_oid);
74 oid sysuptime_oid[] = { SNMP_OID_MIB2, 1, 3, 0 };
75 size_t sysuptime_oid_len = OID_LENGTH(sysuptime_oid);
76
77 #define SNMPV2_COMM_OBJS_PREFIX SNMP_OID_SNMPMODULES,18,1
78 oid agentaddr_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 3, 0 };
79 size_t agentaddr_oid_len = OID_LENGTH(agentaddr_oid);
80 oid community_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 4, 0 };
81 size_t community_oid_len = OID_LENGTH(community_oid);
82
83 #define RECONNOITER_PREFIX     SNMP_OID_ENTERPRISES,32863,1
84 oid reconnoiter_oid[] = { RECONNOITER_PREFIX };
85 size_t reconnoiter_oid_len = OID_LENGTH(reconnoiter_oid);
86 oid reconnoiter_check_prefix_oid[] = { RECONNOITER_PREFIX,1,1 };
87 size_t reconnoiter_check_prefix_oid_len =
88   OID_LENGTH(reconnoiter_check_prefix_oid);
89 size_t reconnoiter_check_oid_len = OID_LENGTH(reconnoiter_check_prefix_oid) + 8;
90 oid reconnoiter_metric_prefix_oid[] = { RECONNOITER_PREFIX,1,2 };
91 size_t reconnoiter_metric_prefix_oid_len =
92   OID_LENGTH(reconnoiter_metric_prefix_oid);
93
94 oid reconnoiter_check_status_oid[] = { RECONNOITER_PREFIX,1,3};
95 size_t reconnoiter_check_status_oid_len =
96   OID_LENGTH(reconnoiter_check_status_oid);
97 oid reconnoiter_check_state_oid[] = { RECONNOITER_PREFIX,1,3,1};
98 size_t reconnoiter_check_state_oid_len =
99   OID_LENGTH(reconnoiter_check_state_oid);
100 oid reconnoiter_check_state_unknown_oid[] = { RECONNOITER_PREFIX,1,3,1,0};
101 oid reconnoiter_check_state_good_oid[] = { RECONNOITER_PREFIX,1,3,1,1};
102 oid reconnoiter_check_state_bad_oid[] = { RECONNOITER_PREFIX,1,3,1,2};
103 size_t reconnoiter_check_state_val_len =
104   OID_LENGTH(reconnoiter_check_state_unknown_oid);
105 /* Boolean */
106 oid reconnoiter_check_available_oid[] = { RECONNOITER_PREFIX,1,3,2};
107 size_t reconnoiter_check_available_oid_len =
108   OID_LENGTH(reconnoiter_check_available_oid);
109 oid reconnoiter_check_available_unknown_oid[] = { RECONNOITER_PREFIX,1,3,2,0};
110 oid reconnoiter_check_available_yes_oid[] = { RECONNOITER_PREFIX,1,3,2,1};
111 oid reconnoiter_check_available_no_oid[] = { RECONNOITER_PREFIX,1,3,2,2};
112 size_t reconnoiter_check_available_val_len =
113   OID_LENGTH(reconnoiter_check_available_unknown_oid);
114 /* timeticks? gauge/unsigned? */
115 oid reconnoiter_check_duration_oid[] = { RECONNOITER_PREFIX,1,3,3};
116 size_t reconnoiter_check_duration_oid_len =
117   OID_LENGTH(reconnoiter_check_duration_oid);
118 /* string */
119 oid reconnoiter_check_status_msg_oid[] = { RECONNOITER_PREFIX,1,3,4};
120 size_t reconnoiter_check_status_msg_oid_len =
121   OID_LENGTH(reconnoiter_check_status_msg_oid);
122
123 typedef struct _mod_config {
124   mtev_hash_table *options;
125   mtev_hash_table target_sessions;
126 } snmp_mod_config_t;
127
128 struct target_session {
129   struct synch_state state;
130   struct session_list *slp;
131   noit_module_t *self;
132   char *key;
133   char *target;
134   eventer_t timeoutevent;
135   int version;
136   int fd;
137   int in_table;
138   int refcnt;
139   struct timeval last_open;
140 };
141
142 #define sess_handle slp->session
143
144 struct snmp_check_closure {
145   noit_module_t *self;
146   noit_check_t *check;
147 };
148
149 struct v3_probe_magic {
150   eventer_t timeoutevent;
151   netsnmp_callback cb;
152   noit_check_t *check;
153   struct target_session *ts; /* for timeout */
154   struct snmp_pdu *pdu;
155 };
156
157 struct check_info {
158   int timedout;
159   struct {
160      int reqid;
161      char *confname;
162      char *oidname;
163      oid oid[MAX_OID_LEN];
164      size_t oidlen;
165      metric_type_t type_override;
166      mtev_boolean type_should_override;
167      int seen;
168   } *oids;
169   int noids;
170   int noids_seen;
171   int nresults;
172   eventer_t timeoutevent;
173   noit_module_t *self;
174   noit_check_t *check;
175   struct target_session *ts;
176   int version;
177 };
178
179 /* We hold struct check_info's in there key's by their reqid.
180  *   If they timeout, we remove them.
181  *
182  *   When SNMP queries complete, we look them up, if we find them
183  *   then we know we can remove the timeout and  complete the check.
184  *   If we don't find them, the timeout fired and removed the check.
185  */
186 mtev_hash_table active_checks = MTEV_HASH_EMPTY;
187 static void add_check(struct check_info *c) {
188   int i;
189   for(i=0; i<c->noids; i++)
190     mtev_hash_store(&active_checks, (char *)&c->oids[i].reqid, sizeof(c->oids[i].reqid), c);
191 }
192 static struct check_info *get_check(int reqid) {
193   void *vc;
194   if(mtev_hash_retrieve(&active_checks, (char *)&reqid, sizeof(reqid), &vc))
195     return (struct check_info *)vc;
196   return NULL;
197 }
198 static void remove_check_req(struct check_info *c, int reqid) {
199   (void)c;
200   mtev_hash_delete(&active_checks, (char *)&reqid, sizeof(reqid),
201                    NULL, NULL);
202 }
203 static void remove_check(struct check_info *c) {
204   int i, lastreq = -1;
205   for(i=0; i<c->noids; i++) {
206     if(c->oids[i].reqid != lastreq) {
207       mtev_hash_delete(&active_checks, (char *)&c->oids[i].reqid, sizeof(c->oids[i].reqid),
208                        NULL, NULL);
209       lastreq = c->oids[i].reqid;
210     }
211   }
212 }
213
214 struct target_session *
215 _get_target_session(noit_module_t *self, char *target, int version) {
216   char key[128];
217   void *vts;
218   struct target_session *ts;
219   snmp_mod_config_t *conf;
220   conf = noit_module_get_userdata(self);
221   snprintf(key, sizeof(key), "%s:v%d", target, version);
222   if(!mtev_hash_retrieve(&conf->target_sessions,
223                          key, strlen(key), &vts)) {
224     ts = calloc(1, sizeof(*ts));
225     ts->self = self;
226     ts->version = version;
227     ts->fd = -1;
228     ts->refcnt = 0;
229     ts->target = strdup(target);
230     ts->key = strdup(key);
231     ts->in_table = 1;
232     mtev_hash_store(&conf->target_sessions,
233                     ts->key, strlen(ts->key), ts);
234     vts = ts;
235   }
236   return (struct target_session *)vts;
237 }
238
239 static int noit_snmp_accumulate_results(noit_check_t *check, struct snmp_pdu *pdu) {
240   struct check_info *info = check->closure;
241   struct variable_list *vars;
242
243   if(pdu)
244     for(vars = pdu->variables; vars; vars = vars->next_variable)
245       info->nresults++;
246
247   /* manipulate the information ourselves */
248   for(vars = pdu->variables; vars; vars = vars->next_variable) {
249     char *sp;
250     int nresults = 0;
251     int oid_idx;
252     double float_conv;
253     u_int64_t u64;
254     int64_t i64;
255     char *endptr;
256     char varbuff[256];
257
258     snprint_variable(varbuff, sizeof(varbuff),
259                      vars->name, vars->name_length, vars);
260
261     /* find the oid to which this is the response */
262     oid_idx = nresults; /* our check->stats.inprogress idx is the most likely */
263     if(info->oids[oid_idx].oidlen != vars->name_length ||
264        memcmp(info->oids[oid_idx].oid, vars->name,
265               vars->name_length * sizeof(oid))) {
266       /* Not the most obvious guess */
267       for(oid_idx = info->noids - 1; oid_idx >= 0; oid_idx--) {
268         if(info->oids[oid_idx].oidlen == vars->name_length &&
269            !memcmp(info->oids[oid_idx].oid, vars->name,
270                   vars->name_length * sizeof(oid))) break;
271       }
272     }
273     if(oid_idx < 0) {
274       mtevL(nlerr, "Unexpected oid results to %s`%s`%s: %s\n",
275             check->target, check->module, check->name, varbuff);
276       nresults++;
277       info->nresults++;
278       continue;
279     }
280     if(info->oids[oid_idx].seen == 0) {
281       info->oids[oid_idx].seen = 1;
282       info->noids_seen++;
283     }
284
285 #define SETM(a,b) noit_stats_set_metric(check, &check->stats.inprogress, \
286                                         info->oids[oid_idx].confname, a, b)
287     if(info->oids[oid_idx].type_should_override) {
288       sp = strchr(varbuff, ' ');
289       if(sp) sp++;
290       noit_stats_set_metric_coerce(check, &check->stats.inprogress, info->oids[oid_idx].confname,
291                                    info->oids[oid_idx].type_override,
292                                    sp);
293     }
294     else {
295       switch(vars->type) {
296         case ASN_OCTET_STR:
297           sp = malloc(1 + vars->val_len);
298           memcpy(sp, vars->val.string, vars->val_len);
299           sp[vars->val_len] = '\0';
300           SETM(METRIC_STRING, sp);
301           free(sp);
302           break;
303         case ASN_INTEGER:
304         case ASN_GAUGE:
305           SETM(METRIC_INT32, vars->val.integer);
306           break;
307         case ASN_TIMETICKS:
308         case ASN_COUNTER:
309           SETM(METRIC_UINT32, vars->val.integer);
310           break;
311 #ifdef ASN_OPAQUE_I64
312         case ASN_OPAQUE_I64:
313 #endif
314         case ASN_INTEGER64:
315           printI64(varbuff, vars->val.counter64);
316           i64 = strtoll(varbuff, &endptr, 10);
317           SETM(METRIC_INT64, (varbuff == endptr) ? NULL : &i64);
318           break;
319 #ifdef ASN_OPAQUE_U64
320         case ASN_OPAQUE_U64:
321 #endif
322 #ifdef ASN_OPAQUE_COUNTER64
323         case ASN_OPAQUE_COUNTER64:
324 #endif
325         case ASN_COUNTER64:
326           printU64(varbuff, vars->val.counter64);
327           u64 = strtoull(varbuff, &endptr, 10);
328           SETM(METRIC_UINT64, (varbuff == endptr) ? NULL : &u64);
329           break;
330 #ifdef ASN_OPAQUE_FLOAT
331         case ASN_OPAQUE_FLOAT:
332 #endif
333         case ASN_FLOAT:
334           if(vars->val.floatVal) float_conv = *(vars->val.floatVal);
335           SETM(METRIC_DOUBLE, vars->val.floatVal ? &float_conv : NULL);
336           break;
337 #ifdef ASN_OPAQUE_DOUBLE
338         case ASN_OPAQUE_DOUBLE:
339 #endif
340         case ASN_DOUBLE:
341           SETM(METRIC_DOUBLE, vars->val.doubleVal);
342           break;
343         case ASN_NULL:
344           mtevL(nldeb, "snmp[null]: %s\n", varbuff);
345         case SNMP_NOSUCHOBJECT:
346         case SNMP_NOSUCHINSTANCE:
347           SETM(METRIC_STRING, NULL);
348           break;
349         default:
350           /* Advance passed the first space and use that unless there
351            * is no space or we have no more string left.
352            */
353           sp = strchr(varbuff, ' ');
354           if(sp) sp++;
355           SETM(METRIC_STRING, (sp && *sp) ? sp : NULL);
356           mtevL(nlerr, "snmp: unknown type[%d] %s\n", vars->type, varbuff);
357       }
358     }
359     nresults++;
360     info->nresults++;
361   }
362   return (info->noids_seen == info->noids) ? 1 : 0;
363 }
364
365 /* Handling of results */
366 static void noit_snmp_log_results(noit_module_t *self, noit_check_t *check, const char *err) {
367   struct check_info *info = check->closure;
368   struct timeval duration;
369   char buff[128];
370
371   gettimeofday(&check->stats.inprogress.whence, NULL);
372   sub_timeval(check->stats.inprogress.whence, check->last_fire_time, &duration);
373   check->stats.inprogress.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
374   check->stats.inprogress.available = (info->nresults > 0) ? NP_AVAILABLE : NP_UNAVAILABLE;
375   check->stats.inprogress.state = (info->noids_seen == info->noids) ? NP_GOOD : NP_BAD;
376   if(err) snprintf(buff, sizeof(buff), "%s", err);
377   else snprintf(buff, sizeof(buff), "%d/%d gets", info->noids_seen, info->noids);
378   check->stats.inprogress.status = buff;
379
380   noit_check_set_stats(check, &check->stats.inprogress);
381   noit_check_stats_clear(check, &check->stats.inprogress);
382   return;
383 }
384
385 static int noit_snmp_session_cleanse(struct target_session *ts,
386                                      int needs_free) {
387   if(ts->refcnt == 0 && ts->slp) {
388     eventer_t e = eventer_remove_fd(ts->fd);
389     if(needs_free) eventer_free(e);
390     ts->fd = -1;
391     if(ts->timeoutevent) {
392       eventer_remove(ts->timeoutevent);
393       ts->timeoutevent = NULL;
394     }
395     snmp_sess_close(ts->slp);
396     ts->slp = NULL;
397     if(!ts->in_table) {
398       free(ts);
399     }
400     return 1;
401   }
402   return 0;
403 }
404
405 static int noit_snmp_session_timeout(eventer_t e, int mask, void *closure,
406                                      struct timeval *now) {
407   struct target_session *ts = closure;
408   if(ts->slp) snmp_sess_timeout(ts->slp);
409   noit_snmp_session_cleanse(ts, 1);
410   if(ts->timeoutevent == e)
411     ts->timeoutevent = NULL; /* this will be freed on return */
412   return 0;
413 }
414
415 static int noit_snmp_check_timeout(eventer_t e, int mask, void *closure,
416                                    struct timeval *now) {
417   struct check_info *info = closure;
418   info->timeoutevent = NULL;
419   info->timedout = 1;
420   if(info->ts) {
421     info->ts->refcnt--;
422     noit_snmp_session_cleanse(info->ts, 1);
423     info->ts = NULL;
424   }
425   remove_check(info);
426   /* Log our findings */
427   noit_snmp_log_results(info->self, info->check, NULL);
428   info->check->flags &= ~NP_RUNNING;
429   return 0;
430 }
431
432 static void _set_ts_timeout(struct target_session *ts, struct timeval *t) {
433   struct timeval now;
434   eventer_t e = NULL;
435   if(ts->timeoutevent) {
436     e = eventer_remove(ts->timeoutevent);
437     ts->timeoutevent = NULL;
438   }
439   if(!t) return;
440
441   gettimeofday(&now, NULL);
442   if(!e) e = eventer_alloc();
443   e->callback = noit_snmp_session_timeout;
444   e->closure = ts;
445   e->mask = EVENTER_TIMER;
446   add_timeval(now, *t, &e->whence);
447   ts->timeoutevent = e;
448   eventer_add(e);
449 }
450
451 static int noit_snmp_handler(eventer_t e, int mask, void *closure,
452                              struct timeval *now) {
453   int block = 0, rv, liberr, snmperr;
454   struct timeval timeout = { 0, 0 };
455   struct target_session *ts = closure;
456   char *errmsg;
457
458   rv = snmp_sess_read_C1(ts->slp, e->fd);
459   if(noit_snmp_session_cleanse(ts, 0))
460     return 0;
461   snmp_sess_select_info2_flags(ts->slp, NULL, NULL,
462                                &timeout, &block, NETSNMP_SELECT_NOFLAGS);
463   _set_ts_timeout(ts, block ? &timeout : NULL);
464
465   return EVENTER_READ | EVENTER_EXCEPTION;
466 }
467
468 /* This 'convert_v1pdu_to_v2' was cribbed directly from netsnmp */
469 static netsnmp_pdu *
470 convert_v1pdu_to_v2( netsnmp_pdu* template_v1pdu ) {
471   netsnmp_pdu *template_v2pdu;
472   netsnmp_variable_list *var;
473   oid enterprise[MAX_OID_LEN];
474   size_t enterprise_len;
475
476   /*
477    * Make a copy of the v1 Trap PDU
478    *   before starting to convert this
479    *   into a v2 Trap PDU.
480    */
481   template_v2pdu = snmp_clone_pdu( template_v1pdu);
482   if(!template_v2pdu) {
483     snmp_log(LOG_WARNING,
484              "send_trap: failed to copy v2 template PDU\n");
485     return NULL;
486   }
487   template_v2pdu->command = SNMP_MSG_TRAP2;
488
489   /*
490    * Insert an snmpTrapOID varbind before the original v1 varbind list
491    *   either using one of the standard defined trap OIDs,
492    *   or constructing this from the PDU enterprise & specific trap fields
493    */
494   if(template_v1pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
495     if(template_v1pdu->enterprise_length + 2 > MAX_OID_LEN) {
496       mtevL(nlerr, "send_trap: enterprise_length too large\n");
497       snmp_free_pdu(template_v2pdu);
498       return NULL;
499     }
500     memcpy(enterprise, template_v1pdu->enterprise,
501            template_v1pdu->enterprise_length*sizeof(oid));
502     enterprise_len = template_v1pdu->enterprise_length;
503     enterprise[enterprise_len++] = 0;
504     enterprise[enterprise_len++] = template_v1pdu->specific_type;
505   } else {
506     memcpy(enterprise, cold_start_oid, sizeof(cold_start_oid));
507     enterprise[9]  = template_v1pdu->trap_type+1;
508     enterprise_len = sizeof(cold_start_oid)/sizeof(oid);
509   }
510
511   var = NULL;
512   if(!snmp_varlist_add_variable(&var,
513                                 snmptrap_oid, snmptrap_oid_len,
514                                 ASN_OBJECT_ID,
515                                 (u_char*)enterprise,
516                                 enterprise_len*sizeof(oid))) {
517     mtevL(nlerr, "send_trap: failed to insert copied snmpTrapOID varbind\n");
518     snmp_free_pdu(template_v2pdu);
519     return NULL;
520   }
521   var->next_variable        = template_v2pdu->variables;
522   template_v2pdu->variables = var;
523
524   /*
525    * Insert a sysUptime varbind at the head of the v2 varbind list
526    */
527   var = NULL;
528   if(!snmp_varlist_add_variable(&var,
529                                 sysuptime_oid, sysuptime_oid_len,
530                                 ASN_TIMETICKS,
531                                 (u_char*)&(template_v1pdu->time),
532                                 sizeof(template_v1pdu->time))) {
533     mtevL(nlerr, "send_trap: failed to insert copied sysUptime varbind\n");
534     snmp_free_pdu(template_v2pdu);
535     return NULL;
536   }
537   var->next_variable = template_v2pdu->variables;
538   template_v2pdu->variables = var;
539
540   /*
541    * Append the other three conversion varbinds,
542    *  (snmpTrapAgentAddr, snmpTrapCommunity & snmpTrapEnterprise)
543    *  if they're not already present.
544    *  But don't bomb out completely if there are problems.
545    */
546   var = find_varbind_in_list(template_v2pdu->variables,
547                              agentaddr_oid, agentaddr_oid_len);
548   if(!var && (template_v1pdu->agent_addr[0]
549               || template_v1pdu->agent_addr[1]
550               || template_v1pdu->agent_addr[2]
551               || template_v1pdu->agent_addr[3])) {
552     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
553                                   agentaddr_oid, agentaddr_oid_len,
554                                   ASN_IPADDRESS,
555                                   (u_char*)&(template_v1pdu->agent_addr),
556                                   sizeof(template_v1pdu->agent_addr)))
557       mtevL(nlerr, "send_trap: failed to append snmpTrapAddr varbind\n");
558   }
559   var = find_varbind_in_list(template_v2pdu->variables,
560                              community_oid, community_oid_len);
561   if(!var && template_v1pdu->community) {
562     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
563                                   community_oid, community_oid_len,
564                                   ASN_OCTET_STR,
565                                   template_v1pdu->community,
566                                   template_v1pdu->community_len))
567       mtevL(nlerr, "send_trap: failed to append snmpTrapCommunity varbind\n");
568   }
569   var = find_varbind_in_list(template_v2pdu->variables,
570                              snmptrapenterprise_oid,
571                              snmptrapenterprise_oid_len);
572   if(!var &&
573      template_v1pdu->trap_type != SNMP_TRAP_ENTERPRISESPECIFIC) {
574     if(!snmp_varlist_add_variable(&(template_v2pdu->variables),
575                                   snmptrapenterprise_oid,
576                                   snmptrapenterprise_oid_len,
577                                   ASN_OBJECT_ID,
578                                   (u_char*)template_v1pdu->enterprise,
579                                   template_v1pdu->enterprise_length*sizeof(oid)))
580       mtevL(nlerr, "send_trap: failed to append snmpEnterprise varbind\n");
581   }
582   return template_v2pdu;
583 }
584
585 static int noit_snmp_oid_to_checkid(oid *o, int l, uuid_t checkid, char *out) {
586   int i;
587   char _uuid_str[UUID_STR_LEN+1], *cp, *uuid_str;
588
589   uuid_str = out ? out : _uuid_str;
590   if(l != reconnoiter_check_oid_len) {
591     mtevL(nlerr, "unsupported (length) trap recieved\n");
592     return -1;
593   }
594   if(netsnmp_oid_equals(o,
595                         reconnoiter_check_prefix_oid_len,
596                         reconnoiter_check_prefix_oid,
597                         reconnoiter_check_prefix_oid_len) != 0) {
598     mtevL(nlerr, "unsupported (wrong namespace) trap recieved\n");
599     return -1;
600   }
601   /* encode this as a uuid */
602   cp = uuid_str;
603   for(i=0;
604       i < reconnoiter_check_oid_len - reconnoiter_check_prefix_oid_len;
605       i++) {
606     oid v = o[i + reconnoiter_check_prefix_oid_len];
607     if(v > 0xffff) {
608       mtevL(nlerr, "trap target oid [%ld] out of range\n", (long int)v);
609       return -1;
610     }
611     snprintf(cp, 5, "%04x", (unsigned short)(v & 0xffff));
612     cp += 4;
613     /* hyphens after index 1,2,3,4 */
614     if(i > 0 && i < 5) *cp++ = '-';
615   }
616   if(uuid_parse(uuid_str, checkid) != 0) {
617     mtevL(nlerr, "unexpected error decoding trap uuid '%s'\n", uuid_str);
618     return -1;
619   }
620   return 0;
621 }
622
623 #define isoid(a,b,c,d) (netsnmp_oid_equals(a,b,c,d) == 0)
624 #define isoidprefix(a,b,c,d) (netsnmp_oid_equals(a,MIN(b,d),c,d) == 0)
625 #define setstatus(st,soid,sv) \
626   if(isoid(o,l,soid,reconnoiter_check_state_val_len)) current->st = sv
627
628 static int
629 noit_snmp_trapvars_to_stats(noit_check_t *check, netsnmp_variable_list *var) {
630   stats_t *current = &check->stats.inprogress;
631   if(isoidprefix(var->name, var->name_length, reconnoiter_check_status_oid,
632                  reconnoiter_check_status_oid_len)) {
633     if(var->type == ASN_OBJECT_ID) {
634       if(isoid(var->name, var->name_length,
635                reconnoiter_check_state_oid, reconnoiter_check_state_oid_len)) {
636         oid *o = var->val.objid;
637         size_t l = var->val_len / sizeof(*o);
638         setstatus(state, reconnoiter_check_state_unknown_oid, NP_UNKNOWN);
639         else setstatus(state, reconnoiter_check_state_good_oid, NP_GOOD);
640         else setstatus(state, reconnoiter_check_state_bad_oid, NP_BAD);
641         else return -1;
642       }
643       else if(isoid(var->name, var->name_length,
644                     reconnoiter_check_available_oid,
645                     reconnoiter_check_available_oid_len)) {
646         oid *o = var->val.objid;
647         size_t l = var->val_len / sizeof(*o);
648         setstatus(available, reconnoiter_check_available_unknown_oid, NP_UNKNOWN);
649         else setstatus(available, reconnoiter_check_available_yes_oid, NP_AVAILABLE);
650         else setstatus(available, reconnoiter_check_available_no_oid, NP_UNAVAILABLE);
651         else return -1;
652       }
653       else {
654         /* We don't unerstand any other OBJECT_ID types */
655         return -1;
656       }
657     }
658     else if(var->type == ASN_UNSIGNED) {
659       /* This is only for the duration (in ms) */
660       if(isoid(var->name, var->name_length,
661                reconnoiter_check_duration_oid,
662                reconnoiter_check_duration_oid_len)) {
663         current->duration = *(var->val.integer);
664       }
665       else
666         return -1;
667     }
668     else if(var->type == ASN_OCTET_STR) {
669       /* This is only for the status message */
670       if(isoid(var->name, var->name_length,
671                reconnoiter_check_status_msg_oid,
672                reconnoiter_check_status_msg_oid_len)) {
673         current->status = malloc(var->val_len + 1);
674         memcpy(current->status, var->val.string, var->val_len);
675         current->status[var->val_len] = '\0';
676       }
677       else
678         return -1;
679     }
680     else {
681       /* I don't understand any other type of status message */
682       return -1;
683     }
684   }
685   else if(isoidprefix(var->name, var->name_length,
686                       reconnoiter_metric_prefix_oid,
687                       reconnoiter_metric_prefix_oid_len)) {
688     /* decode the metric and store the value */
689     int i, len;
690     u_int64_t u64;
691     double doubleVal;
692     char metric_name[128], buff[128], *cp;
693     if(var->name_length <= reconnoiter_metric_prefix_oid_len) return -1;
694     len = var->name[reconnoiter_metric_prefix_oid_len];
695     if(var->name_length != (reconnoiter_metric_prefix_oid_len + 1 + len) ||
696        len > sizeof(metric_name) - 1) {
697       mtevL(nlerr, "snmp trap, malformed metric name\n");
698       return -1;
699     }
700     for(i=0;i<len;i++) {
701       ((unsigned char *)metric_name)[i] =
702         (unsigned char)var->name[reconnoiter_metric_prefix_oid_len + 1 + i];
703       if(!isprint(metric_name[i])) {
704         mtevL(nlerr, "metric_name contains unprintable characters\n");
705         return -1;
706       }
707     }
708     metric_name[i] = '\0';
709     switch(var->type) {
710       case ASN_INTEGER:
711       case ASN_UINTEGER:
712       case ASN_TIMETICKS:
713       case ASN_INTEGER64:
714         noit_stats_set_metric(check, current, metric_name,
715                               METRIC_INT64, var->val.integer);
716         break;
717       case ASN_COUNTER64:
718         u64 = ((u_int64_t)var->val.counter64->high) << 32;
719         u64 |= var->val.counter64->low;
720         noit_stats_set_metric(check, current, metric_name,
721                               METRIC_UINT64, &u64);
722         break;
723       case ASN_OPAQUE_FLOAT:
724         doubleVal = (double)*var->val.floatVal;
725         noit_stats_set_metric(check, current, metric_name,
726                               METRIC_DOUBLE, &doubleVal);
727         break;
728       case ASN_OPAQUE_DOUBLE:
729         noit_stats_set_metric(check, current, metric_name,
730                               METRIC_DOUBLE, var->val.doubleVal);
731         break;
732       case ASN_OCTET_STR:
733         snprint_value(buff, sizeof(buff), var->name, var->name_length, var);
734         /* Advance passed the first space and use that unless there
735          * is no space or we have no more string left.
736          */
737         cp = strchr(buff, ' ');
738         if(cp) {
739           char *ecp;
740           cp++;
741           if(*cp == '"') {
742             ecp = cp + strlen(cp) - 1;
743             if(*ecp == '"') {
744               cp++; *ecp = '\0';
745             }
746           }
747         }
748         noit_stats_set_metric(check, current, metric_name,
749                               METRIC_STRING, (cp && *cp) ? cp : NULL);
750         break;
751       default:
752         mtevL(nlerr, "snmp trap unsupport data type %d\n", var->type);
753     }
754     mtevL(nldeb, "metric_name -> '%s'\n", metric_name);
755   }
756   else {
757     /* No idea what this is */
758     return -1;
759   }
760   return 0;
761 }
762 static int noit_snmp_trapd_response(int operation, struct snmp_session *sp,
763                                     int reqid, struct snmp_pdu *pdu,
764                                     void *magic) {
765   /* the noit pieces */
766   noit_check_t *check;
767   struct target_session *ts = magic;
768   snmp_mod_config_t *conf;
769   const char *community = NULL;
770   int success = 0;
771
772   /* parsing destination */
773   char uuid_str[UUID_STR_LEN + 1];
774   uuid_t checkid;
775
776   /* snmp oid parsing helper vars */
777   netsnmp_pdu *newpdu = pdu;
778   netsnmp_variable_list *var;
779
780   conf = noit_module_get_userdata(ts->self);
781
782   if(pdu->version == SNMP_VERSION_1)
783     newpdu = convert_v1pdu_to_v2(pdu);
784   if(!newpdu || newpdu->version != SNMP_VERSION_2c) goto cleanup;
785
786   for(var = newpdu->variables; var != NULL; var = var->next_variable) {
787     if(netsnmp_oid_equals(var->name, var->name_length,
788                           snmptrap_oid, snmptrap_oid_len) == 0)
789       break;
790   }
791
792   if (!var || var->type != ASN_OBJECT_ID) {
793     mtevL(nlerr, "unsupport trap (not a trap?) received\n");
794     goto cleanup;
795   }
796
797   /* var is the oid on which we are trapping.
798    * It should be in the reconnoiter check prefix.
799    */
800   if(noit_snmp_oid_to_checkid(var->val.objid, var->val_len/sizeof(oid),
801                               checkid, uuid_str)) {
802     goto cleanup;
803   }
804   mtevL(nldeb, "recieved trap for %s\n", uuid_str);
805   check = noit_poller_lookup(checkid);
806   if(!check) {
807     mtevL(nlerr, "trap received for non-existent check '%s'\n", uuid_str);
808     goto cleanup;
809   }
810   if(!mtev_hash_retr_str(check->config, "community", strlen("community"),
811                          &community) &&
812      !mtev_hash_retr_str(conf->options, "community", strlen("community"),
813                          &community)) {
814     mtevL(nlerr, "No community defined for check, dropping trap\n");
815     goto cleanup;
816   }
817
818   if(strlen(community) != newpdu->community_len ||
819      memcmp(community, newpdu->community, newpdu->community_len)) {
820     mtevL(nlerr, "trap attempt with wrong community string\n");
821     goto cleanup;
822   }
823
824   /* We have a check. The trap is authorized. Now, extract everything. */
825   memset(&check->stats.inprogress, 0, sizeof(check->stats.inprogress));
826   gettimeofday(&check->stats.inprogress.whence, NULL);
827   check->stats.inprogress.available = NP_AVAILABLE;
828
829   /* Rate limit */
830   if(((check->stats.inprogress.whence.tv_sec * 1000 +
831        check->stats.inprogress.whence.tv_usec / 1000) -
832       (check->last_fire_time.tv_sec * 1000 +
833        check->last_fire_time.tv_usec / 1000)) < check->period) goto cleanup;
834
835   /* update the last fire time... */
836   gettimeofday(&check->last_fire_time, NULL);
837
838   for(; var != NULL; var = var->next_variable)
839     if(noit_snmp_trapvars_to_stats(check, var) == 0) success++;
840   if(success) {
841     char buff[24];
842     snprintf(buff, sizeof(buff), "%d datum", success);
843     check->stats.inprogress.state = NP_GOOD;
844     check->stats.inprogress.status = strdup(buff);
845   }
846   else {
847     check->stats.inprogress.state = NP_BAD;
848     check->stats.inprogress.status = strdup("no data");
849   }
850   noit_check_set_stats(check, &check->stats.inprogress);
851
852  cleanup:
853   if(newpdu != pdu)
854     snmp_free_pdu(newpdu);
855   return 0;
856 }
857 static int noit_snmp_asynch_response(int operation, struct snmp_session *sp,
858                                      int reqid, struct snmp_pdu *pdu,
859                                      void *magic) {
860   struct check_info *info;
861   /* We don't deal with refcnt hitting zero here.  We could only be hit from
862    * the snmp read/timeout stuff.  Handle it there.
863    */
864
865   info = get_check(reqid);
866   if(!info) return 1;
867   remove_check_req(info, reqid);
868
869
870   if(noit_snmp_accumulate_results(info->check, pdu)) {
871     mtevL(nldeb, "snmp %s pdu completed check requirements\n", info->check->name);
872     if(info->timeoutevent) {
873       eventer_remove(info->timeoutevent);
874       eventer_free(info->timeoutevent);
875       info->timeoutevent = NULL;
876     }
877     if(info->ts) {
878       info->ts->refcnt--;
879       info->ts = NULL;
880     }
881     noit_snmp_log_results(info->self, info->check, NULL);
882     info->check->flags &= ~NP_RUNNING;
883   }
884   return 1;
885 }
886
887 static void noit_snmp_sess_open(struct target_session *ts,
888                                 noit_check_t *check) {
889   size_t bsize, offset;
890   u_char _buf[512];
891   u_char *buf = _buf;
892   const char *cval;
893   struct snmp_session sess;
894   netsnmp_transport *transport;
895   struct check_info *info = check->closure;
896   memset(&sess, 0, sizeof(sess));
897   snmp_sess_init(&sess);
898   sess.version = info->version;
899   sess.peername = ts->target;
900   u_char contextEngineID_buf[256];
901   u_char securityEngineID_buf[256];
902
903 /*
904 final String walk_base             = config.remove("walk");
905 */
906
907 #define CONF_GET(name, tgt) \
908   mtev_hash_retr_str(check->config, name, strlen(name), tgt)
909 #define SESS_SET_STRING(value, len, key, default) do {    \
910   const char *_cval = NULL;                               \
911   if(!CONF_GET(key, &_cval)) {                            \
912     _cval = default;                                      \
913   }                                                       \
914   value = ( __typeof__ (value) )(_cval);                  \
915   len = strlen(_cval);                                    \
916 } while(0)
917
918   SESS_SET_STRING(sess.community, sess.community_len, "community", "public");
919   /* TCP/UDP -> session_flags |= SNMP_FLAGS_STREAM_SOCKET */
920   /* securityEngineID? */
921   /* contextEngineID? */
922   SESS_SET_STRING(sess.contextName, sess.contextNameLen, "context_name", "");
923   if(CONF_GET("context_engine", &cval)) {
924     bsize = sizeof(buf);
925     offset = 0;
926     if (netsnmp_hex_to_binary(&buf,&bsize,&offset,0,cval,".")) {
927             sess.contextEngineID = contextEngineID_buf;
928             memcpy(sess.contextEngineID, buf, bsize);
929             sess.contextEngineIDLen = bsize;
930     }
931         }
932   SESS_SET_STRING(sess.securityName, sess.securityNameLen, "security_name", "");
933   if(sess.securityName) sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
934   if(CONF_GET("security_engine", &cval)) {
935     bsize = sizeof(buf);
936     offset = 0;
937     if (netsnmp_hex_to_binary(&buf,&bsize,&offset,0,cval,".")) {
938             sess.securityEngineID = securityEngineID_buf;
939             memcpy(sess.securityEngineID, buf, bsize);
940             sess.securityEngineIDLen = bsize;
941     }
942         }
943   if(CONF_GET("security_level", &cval)) {
944     if(!strcasecmp(cval, "nanp")) sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
945     else if(!strcasecmp(cval, "anp")) sess.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
946     else if(!strcasecmp(cval, "ap")) sess.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
947   }
948   if(CONF_GET("auth_protocol", &cval)) {
949     if (!strcasecmp(cval,"MD5")) {
950       sess.securityAuthProto = usmHMACMD5AuthProtocol;
951       sess.securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN;
952     }
953     else if (!strcasecmp(cval,"SHA")) {
954       sess.securityAuthProto = usmHMACSHA1AuthProtocol;
955       sess.securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN;
956     }
957   }
958   if(CONF_GET("auth_passphrase", &cval)) {
959     if(sess.securityAuthProto == NULL) {
960       sess.securityAuthProto = usmHMACMD5AuthProtocol;
961       sess.securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN;
962     }
963     sess.securityAuthKeyLen = USM_AUTH_KU_LEN;
964     if(generate_Ku(sess.securityAuthProto, sess.securityAuthProtoLen,
965                    (u_char *)cval, strlen(cval),
966                    sess.securityAuthKey, &sess.securityAuthKeyLen) != SNMPERR_SUCCESS) {
967       /* What do we do? */
968       mtevL(nlerr, "auth_passphrase failed to gen master key\n");
969     }
970   }
971   if(CONF_GET("privacy_protocol", &cval)) {
972     if (!strcasecmp(cval,"DES")) {
973       sess.securityPrivProto = usmDESPrivProtocol;
974       sess.securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN;
975     }
976     else if (!strcasecmp(cval,"AES")) {
977       sess.securityPrivProto = usmAESPrivProtocol;
978       sess.securityPrivProtoLen = USM_PRIV_PROTO_AES_LEN;
979     }
980   }
981   if(CONF_GET("privacy_passphrase", &cval)) {
982     if(sess.securityPrivProto == NULL) {
983       sess.securityPrivProto = usmDESPrivProtocol;
984       sess.securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN;
985     }
986     sess.securityPrivKeyLen = USM_PRIV_KU_LEN;
987     if(generate_Ku(sess.securityPrivProto, sess.securityPrivProtoLen,
988                    (u_char *)cval, strlen(cval),
989                    sess.securityPrivKey, &sess.securityPrivKeyLen) != SNMPERR_SUCCESS) {
990       /* What do we do? */
991     }
992   }
993   sess.callback = noit_snmp_asynch_response;
994   sess.callback_magic = ts;
995   sess.flags |= SNMP_FLAGS_DONT_PROBE;
996   ts->slp = snmp_sess_open_C1(&sess, &transport);
997   ts->sess_handle->flags &= ~SNMP_FLAGS_DONT_PROBE;
998   ts->fd = transport->sock;
999   gettimeofday(&ts->last_open, NULL);
1000 }
1001
1002 static int noit_snmp_fill_oidinfo(noit_check_t *check) {
1003   int i, klen;
1004   mtev_hash_iter iter = MTEV_HASH_ITER_ZERO;
1005   const char *name, *value;
1006   struct check_info *info = check->closure;
1007   mtev_hash_table check_attrs_hash = MTEV_HASH_EMPTY;
1008
1009   /* Toss the old set and bail if we have zero */
1010   if(info->oids) {
1011     for(i=0; i<info->noids;i++) {
1012       if(info->oids[i].confname) free(info->oids[i].confname);
1013       if(info->oids[i].oidname) free(info->oids[i].oidname);
1014     }
1015     free(info->oids);
1016   }
1017   info->noids = 0;
1018   info->nresults = 0;
1019   info->noids_seen = 0;
1020   info->oids = NULL;
1021
1022   /* Figure our how many. */
1023   while(mtev_hash_next_str(check->config, &iter, &name, &klen, &value)) {
1024     if(!strncasecmp(name, "oid_", 4)) {
1025       info->noids++;
1026     }
1027   }
1028
1029   if(info->noids == 0) return 0;
1030
1031   /* Create a hash of important check attributes */
1032   noit_check_make_attrs(check, &check_attrs_hash);
1033
1034   /* Fill out the new set of required oids */
1035   info->oids = calloc(info->noids, sizeof(*info->oids));
1036   memset(&iter, 0, sizeof(iter));
1037   i = 0;
1038   while(mtev_hash_next_str(check->config, &iter, &name, &klen, &value)) {
1039     if(!strncasecmp(name, "oid_", 4)) {
1040       const char *type_override;
1041       char oidbuff[2048], typestr[256];
1042       name += 4;
1043       info->oids[i].confname = strdup(name);
1044       noit_check_interpolate(oidbuff, sizeof(oidbuff), value,
1045                              &check_attrs_hash, check->config);
1046       info->oids[i].oidname = strdup(oidbuff);
1047       info->oids[i].oidlen = MAX_OID_LEN;
1048       if(oidbuff[0] == '.') {
1049         if(!read_objid(oidbuff, info->oids[i].oid, &info->oids[i].oidlen)) {
1050           mtevL(nlerr, "Failed to translate oid: %s\n", oidbuff);
1051           info->noids--;
1052           continue;
1053         }
1054       }
1055       else {
1056         if(!get_node(oidbuff, info->oids[i].oid, &info->oids[i].oidlen)) {
1057           mtevL(nlerr, "Failed to translate oid: %s\n", oidbuff);
1058           info->noids--;
1059           continue;
1060         }
1061       }
1062       snprintf(typestr, sizeof(typestr), "type_%s", name);
1063       if(mtev_hash_retr_str(check->config, typestr, strlen(typestr),
1064                             &type_override)) {
1065         int type_enum_fake = *type_override;
1066
1067         if(!strcasecmp(type_override, "guess"))
1068           type_enum_fake = METRIC_GUESS;
1069         else if(!strcasecmp(type_override, "int32"))
1070           type_enum_fake = METRIC_INT32;
1071         else if(!strcasecmp(type_override, "uint32"))
1072           type_enum_fake = METRIC_UINT32;
1073         else if(!strcasecmp(type_override, "int64"))
1074           type_enum_fake = METRIC_INT64;
1075         else if(!strcasecmp(type_override, "uint64"))
1076           type_enum_fake = METRIC_UINT64;
1077         else if(!strcasecmp(type_override, "double"))
1078           type_enum_fake = METRIC_DOUBLE;
1079         else if(!strcasecmp(type_override, "string"))
1080           type_enum_fake = METRIC_STRING;
1081
1082         switch(type_enum_fake) {
1083           case METRIC_GUESS:
1084           case METRIC_INT32: case METRIC_UINT32:
1085           case METRIC_INT64: case METRIC_UINT64:
1086           case METRIC_DOUBLE: case METRIC_STRING:
1087             info->oids[i].type_override = *type_override;
1088             info->oids[i].type_should_override = mtev_true;
1089           default: break;
1090         }
1091       }
1092       i++;
1093     }
1094   }
1095   assert(info->noids == i);
1096   mtev_hash_destroy(&check_attrs_hash, NULL, NULL);
1097   return info->noids;
1098 }
1099 static int noit_snmp_fill_req(struct snmp_pdu *req, noit_check_t *check, int idx) {
1100   int i;
1101   struct check_info *info = check->closure;
1102
1103   if(idx == -1) {
1104     for(i=0; i<info->noids; i++)
1105       snmp_add_null_var(req, info->oids[i].oid, info->oids[i].oidlen);
1106     return info->noids;
1107   }
1108
1109   assert(idx >= 0 && idx <info->noids);
1110   snmp_add_null_var(req, info->oids[idx].oid, info->oids[idx].oidlen);
1111   return 1;
1112 }
1113
1114 static void
1115 ensure_usm_user(const char *username, u_char *engineID, size_t engineIDLen) {
1116   struct usmUser *user;
1117   user = usm_get_user(NULL, 0, (char *)username);
1118   if (user == NULL) {
1119     user = (struct usmUser *) calloc(1, sizeof(struct usmUser));
1120     user->name = strdup(username);
1121     user->secName = strdup(username);
1122     user->authProtocolLen = sizeof(usmNoAuthProtocol) / sizeof(oid);
1123     user->authProtocol =
1124         snmp_duplicate_objid(usmNoAuthProtocol, user->authProtocolLen);
1125     user->privProtocolLen = sizeof(usmNoPrivProtocol) / sizeof(oid);
1126     user->privProtocol =
1127         snmp_duplicate_objid(usmNoPrivProtocol, user->privProtocolLen);
1128     if(engineIDLen) {
1129       user->engineID = malloc(engineIDLen);
1130       memcpy(user->engineID, engineID, engineIDLen);
1131       user->engineIDLen = engineIDLen;
1132     }
1133     usm_add_user(user);
1134     mtevL(nldeb, "usm adding user: %s\n", username);
1135   }
1136 }
1137
1138 /* Shenanigans to work around snmp v3 probes blocking */
1139 static int
1140 snmpv3_build_probe_pdu(netsnmp_pdu **pdu) {
1141     struct usmUser *user;
1142
1143     /*
1144      * create the pdu
1145      */
1146     if (!pdu)
1147         return -1;
1148     *pdu = snmp_pdu_create(SNMP_MSG_GET);
1149     if (!(*pdu))
1150         return -1;
1151     (*pdu)->version = SNMP_VERSION_3;
1152     (*pdu)->securityName = strdup("");
1153     (*pdu)->securityNameLen = strlen((*pdu)->securityName);
1154     (*pdu)->securityLevel = SNMP_SEC_LEVEL_NOAUTH;
1155     (*pdu)->securityModel = SNMP_SEC_MODEL_USM;
1156
1157     /*
1158      * create the empty user
1159      */
1160     ensure_usm_user((*pdu)->securityName, NULL, 0);
1161     return 0;
1162 }
1163
1164 static int
1165 noit_snmpv3_probe_timeout(eventer_t e, int mask, void *closure,
1166                           struct timeval *now) {
1167   struct v3_probe_magic *magic = closure;
1168   struct check_info *info = magic->check->closure;
1169   struct target_session *ts = magic->ts;
1170   if(ts && ts->slp) {
1171     ts->sess_handle->flags &= ~SNMP_FLAGS_DONT_PROBE;
1172   }
1173   if(ts) ts->refcnt--;
1174   magic->timeoutevent = NULL;
1175   return 0;
1176 }
1177
1178 static void
1179 copy_auth_to_pdu(struct snmp_session *sp, struct snmp_pdu *pdu) {
1180 #define pdu_copystring(name) do { \
1181   if(pdu->name) free(pdu->name); \
1182   pdu->name = NULL; \
1183   if(sp->name) pdu->name = strdup(sp->name); \
1184   pdu->name##Len = strlen(pdu->name); \
1185 } while(0)
1186 #define pdu_copyoid(oid) do { \
1187   if(pdu->oid) free(pdu->oid); \
1188   pdu->oid = NULL; \
1189   pdu->oid = snmp_duplicate_objid(sp->oid, sp->oid##Len); \
1190   pdu->oid##Len = sp->oid##Len; \
1191 } while(0)
1192
1193   pdu_copystring(securityName);
1194   pdu->securityModel = sp->securityModel;
1195   pdu->securityLevel = sp->securityLevel;
1196 }
1197
1198 static int
1199 probe_engine_step1_cb(int operation,
1200                       struct snmp_session *sp,
1201                       int reqid,
1202                       struct snmp_pdu *pdu,
1203                       void *arg) {
1204   struct v3_probe_magic *magic = arg;
1205   struct check_info *info = magic->check->closure;
1206   struct target_session *ts = info->ts;
1207   int ret = 1;
1208
1209   if(magic->timeoutevent) {
1210     eventer_remove(magic->timeoutevent);
1211     eventer_free(magic->timeoutevent);
1212     magic->timeoutevent = NULL;
1213   }
1214   /* Did we receive the appropriate Report message? */
1215   if (operation == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE &&
1216       pdu && pdu->command == SNMP_MSG_REPORT) {
1217     int reqid, i;
1218     if(pdu->securityEngineIDLen) {
1219       if(sp->securityEngineID) free(sp->securityEngineID);
1220       sp->securityEngineID = malloc(sizeof(*sp->securityEngineID)*pdu->securityEngineIDLen);
1221       memcpy(sp->securityEngineID, pdu->securityEngineID, sizeof(*sp->securityEngineID)*pdu->securityEngineIDLen);
1222       sp->securityEngineIDLen = pdu->securityEngineIDLen;
1223     }
1224     if(pdu->contextEngineIDLen) {
1225       if(sp->contextEngineID) free(sp->contextEngineID);
1226       sp->contextEngineID = malloc(sizeof(*sp->contextEngineID)*pdu->contextEngineIDLen);
1227       memcpy(sp->contextEngineID, pdu->contextEngineID, sizeof(*sp->contextEngineID)*pdu->contextEngineIDLen);
1228       sp->contextEngineIDLen = pdu->contextEngineIDLen;
1229     }
1230     i = usm_create_user_from_session(sp);
1231     mtevL(nldeb, "usm_create_user_from_session(...) -> %d\n", i);
1232     copy_auth_to_pdu(sp, magic->pdu);
1233     reqid = snmp_sess_send(ts->slp, magic->pdu);
1234     if(reqid == 0) {
1235       int liberr, snmperr;
1236       char *errmsg;
1237       snmp_sess_error(ts->slp, &liberr, &snmperr, &errmsg);
1238       mtevL(nlerr, "Error sending snmp get request: %s\n", errmsg);
1239       snmp_free_pdu(magic->pdu);
1240       magic->pdu = NULL;
1241       goto probe_failed;
1242     }
1243     for(i=0; i<info->noids; i++) info->oids[i].reqid = reqid;
1244     mtevL(nldeb, "Probe followup sent snmp get[all/%d] -> reqid:%d\n", info->noids, reqid);
1245     add_check(info);
1246     ts->refcnt--;
1247     goto out;
1248   }
1249
1250  probe_failed:
1251   if(magic->pdu) snmp_free_pdu(magic->pdu);
1252   sp->flags &= ~SNMP_FLAGS_DONT_PROBE;
1253   ret = magic->cb(NETSNMP_CALLBACK_OP_SEND_FAILED,
1254                   sp, reqid, pdu, info);
1255
1256  out:
1257   return ret;
1258 }
1259
1260 static int noit_snmp_send(noit_module_t *self, noit_check_t *check,
1261                           noit_check_t *cause) {
1262   struct timeval when, to;
1263   struct snmp_pdu *req = NULL;
1264   struct target_session *ts;
1265   struct check_info *info = check->closure;
1266   int port = 161, i;
1267   mtev_boolean separate_queries = mtev_false;
1268   const char *portstr, *versstr, *sepstr;
1269   const char *err = "unknown err";
1270   char target_port[64];
1271
1272   info->version = SNMP_VERSION_2c;
1273   info->self = self;
1274   info->check = check;
1275   info->timedout = 0;
1276
1277   BAIL_ON_RUNNING_CHECK(check);
1278   check->flags |= NP_RUNNING;
1279
1280   gettimeofday(&check->last_fire_time, NULL);
1281   if(mtev_hash_retr_str(check->config, "separate_queries",
1282                         strlen("separate_queries"), &sepstr)) {
1283     if(!strcasecmp(sepstr, "on") || !strcasecmp(sepstr, "true"))
1284       separate_queries = mtev_true;
1285   }
1286   if(mtev_hash_retr_str(check->config, "port", strlen("port"),
1287                         &portstr)) {
1288     port = atoi(portstr);
1289   }
1290   if(mtev_hash_retr_str(check->config, "version", strlen("version"),
1291                         &versstr)) {
1292     /* We don't care about 2c or others... as they all default to 2c */
1293     if(!strcmp(versstr, "1")) info->version = SNMP_VERSION_1;
1294     if(!strcmp(versstr, "3")) info->version = SNMP_VERSION_3;
1295   }
1296   snprintf(target_port, sizeof(target_port), "%s:%d", check->target_ip, port);
1297   ts = _get_target_session(self, target_port, info->version);
1298   gettimeofday(&check->last_fire_time, NULL);
1299   if(!ts->refcnt) {
1300     eventer_t newe;
1301     struct timeval timeout;
1302     netsnmp_session *rsess;
1303     noit_snmp_sess_open(ts, check);
1304     newe = eventer_alloc();
1305     newe->fd = ts->fd;
1306     newe->callback = noit_snmp_handler;
1307     newe->closure = ts;
1308     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
1309     eventer_add(newe);
1310   }
1311   if(!ts->slp) goto bail;
1312   ts->refcnt++; /* Increment here, decrement when this check completes */
1313
1314   noit_snmp_fill_oidinfo(check);
1315   /* Do we need probing? */
1316   if (info->version == SNMP_VERSION_3 &&
1317       ts->sess_handle->securityEngineIDLen == 0 &&
1318       (0 == (ts->sess_handle->flags & SNMP_FLAGS_DONT_PROBE))) {
1319     /* Allocate some "magic" structure to remember PDU, callback and argument*/
1320     struct v3_probe_magic *magic = calloc(1, sizeof(struct v3_probe_magic));
1321     netsnmp_pdu *probe = NULL;
1322
1323     mtevL(nldeb, "Probing for v3\n");
1324     if (!magic) goto bail;
1325     magic->cb = noit_snmp_asynch_response;
1326     magic->check = check;
1327     magic->ts = ts;
1328  
1329     if (snmpv3_build_probe_pdu(&probe) != 0) {
1330       free(magic);
1331       goto bail;
1332     }
1333
1334     /* Send it. */
1335     ts->sess_handle->flags |= SNMP_FLAGS_DONT_PROBE; /* prevent recursion */
1336     ts->refcnt++;
1337
1338     magic->pdu = snmp_pdu_create(SNMP_MSG_GET);
1339     noit_snmp_fill_req(magic->pdu, check, -1);
1340     magic->pdu->version = info->version;
1341
1342     if (!snmp_sess_async_send(ts->slp, probe, probe_engine_step1_cb, magic)) {
1343       ts->refcnt--;
1344       snmp_free_pdu(probe);
1345       free(magic);
1346       ts->sess_handle->flags &= ~SNMP_FLAGS_DONT_PROBE;
1347       goto bail;
1348     }
1349
1350     magic->timeoutevent = eventer_alloc();
1351     magic->timeoutevent->callback = noit_snmpv3_probe_timeout;
1352     magic->timeoutevent->closure = magic;
1353     magic->timeoutevent->mask = EVENTER_TIMER;
1354  
1355     gettimeofday(&when, NULL);
1356     to.tv_sec = 5;
1357     to.tv_usec = 0;
1358     add_timeval(when, to, &magic->timeoutevent->whence);
1359     eventer_add(magic->timeoutevent);
1360   }
1361   else {
1362     /* Separate queries is not supported on v3... it makes no sense */
1363     if(separate_queries && info->version != SNMP_VERSION_3) {
1364       int reqid, i;
1365       mtevL(nldeb, "Regular old get...\n");
1366       for(i=0;i<info->noids;i++) {
1367         req = snmp_pdu_create(SNMP_MSG_GET);
1368         if(!req) continue;
1369         noit_snmp_fill_req(req, check, i);
1370         req->version = info->version;
1371         reqid = snmp_sess_send(ts->slp, req);
1372         if(reqid == 0) {
1373           int liberr, snmperr;
1374           char *errmsg;
1375           snmp_sess_error(ts->slp, &liberr, &snmperr, &errmsg);
1376           mtevL(nlerr, "Error sending snmp get request: %s\n", errmsg);
1377           snmp_free_pdu(req);
1378           continue;
1379         }
1380         info->oids[i].reqid = reqid;
1381         mtevL(nldeb, "Sent snmp get[%d/%d] -> reqid:%d\n", i, info->noids, reqid);
1382       }
1383     }
1384     else {
1385       int reqid, i;
1386       mtevL(nldeb, "Regular old get...\n");
1387       req = snmp_pdu_create(SNMP_MSG_GET);
1388       if(!req) goto bail;
1389       noit_snmp_fill_req(req, check, -1);
1390       if(info->version == SNMP_VERSION_3 && ts->sess_handle->securityName) {
1391         i = usm_create_user_from_session(ts->sess_handle);
1392         mtevL(nldeb, "usm_create_user_from_session(...) -> %d\n", i);
1393       }
1394       req->version = info->version;
1395       reqid = snmp_sess_send(ts->slp, req);
1396       if(reqid == 0) {
1397         int liberr, snmperr;
1398         char *errmsg;
1399         snmp_sess_error(ts->slp, &liberr, &snmperr, &errmsg);
1400         mtevL(nlerr, "Error sending snmp get request: %s\n", errmsg);
1401         err = errmsg;
1402         if(reqid == 0) goto bail;
1403       }
1404       for(i=0; i<info->noids; i++) info->oids[i].reqid = reqid;
1405       mtevL(nldeb, "Sent snmp get[all/%d] -> reqid:%d\n", info->noids, reqid);
1406     }
1407   }
1408   info->ts = ts;
1409   info->timeoutevent = eventer_alloc();
1410   info->timeoutevent->callback = noit_snmp_check_timeout;
1411   info->timeoutevent->closure = info;
1412   info->timeoutevent->mask = EVENTER_TIMER;
1413
1414   gettimeofday(&when, NULL);
1415   to.tv_sec = check->timeout / 1000;
1416   to.tv_usec = (check->timeout % 1000) * 1000;
1417   add_timeval(when, to, &info->timeoutevent->whence);
1418   eventer_add(info->timeoutevent);
1419   add_check(info);
1420   return 0;
1421
1422  bail:
1423   ts->refcnt--;
1424   noit_snmp_log_results(self, check, err);
1425   noit_snmp_session_cleanse(ts, 1);
1426   if(req) snmp_free_pdu(req);
1427   check->flags &= ~NP_RUNNING;
1428   return 0;
1429 }
1430
1431 static int noit_snmp_initiate_check(noit_module_t *self, noit_check_t *check,
1432                                     int once, noit_check_t *cause) {
1433   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
1434   INITIATE_CHECK(noit_snmp_send, self, check, cause);
1435   return 0;
1436 }
1437
1438 static int noit_snmptrap_initiate_check(noit_module_t *self,
1439                                         noit_check_t *check,
1440                                         int once, noit_check_t *cause) {
1441   /* We don't do anything for snmptrap checks.  Not intuitive... but they
1442    * never "run."  We accept input out-of-band via snmp traps.
1443    */
1444   check->flags |= NP_PASSIVE_COLLECTION;
1445   return 0;
1446 }
1447
1448 static int noit_snmp_config(noit_module_t *self, mtev_hash_table *options) {
1449   snmp_mod_config_t *conf;
1450   conf = noit_module_get_userdata(self);
1451   if(conf) {
1452     if(conf->options) {
1453       mtev_hash_destroy(conf->options, free, free);
1454       free(conf->options);
1455     }
1456   }
1457   else
1458     conf = calloc(1, sizeof(*conf));
1459   conf->options = options;
1460   noit_module_set_userdata(self, conf);
1461   return 1;
1462 }
1463 static int noit_snmp_onload(mtev_image_t *self) {
1464   if(!nlerr) nlerr = mtev_log_stream_find("error/snmp");
1465   if(!nldeb) nldeb = mtev_log_stream_find("debug/snmp");
1466   if(!nlerr) nlerr = noit_stderr;
1467   if(!nldeb) nldeb = noit_debug;
1468   eventer_name_callback("noit_snmp/check_timeout", noit_snmp_check_timeout);
1469   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
1470   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
1471   return 0;
1472 }
1473
1474 static int noit_snmptrap_onload(mtev_image_t *self) {
1475   if(!nlerr) nlerr = mtev_log_stream_find("error/snmp");
1476   if(!nldeb) nldeb = mtev_log_stream_find("debug/snmp");
1477   if(!nlerr) nlerr = noit_stderr;
1478   if(!nldeb) nldeb = noit_debug;
1479   eventer_name_callback("noit_snmp/session_timeout", noit_snmp_session_timeout);
1480   eventer_name_callback("noit_snmp/handler", noit_snmp_handler);
1481   return 0;
1482 }
1483
1484 static void
1485 nc_printf_snmpts_brief(mtev_console_closure_t ncct,
1486                        struct target_session *ts) {
1487   char fd[32];
1488   struct timeval now, diff;
1489   const char *snmpvers = "v(unknown)";
1490   gettimeofday(&now, NULL);
1491   sub_timeval(now, ts->last_open, &diff);
1492   if(ts->fd < 0)
1493     snprintf(fd, sizeof(fd), "%s", "(closed)");
1494   else
1495     snprintf(fd, sizeof(fd), "%d", ts->fd);
1496   switch(ts->version) {
1497     case SNMP_VERSION_1: snmpvers = "v1"; break;
1498     case SNMP_VERSION_2c: snmpvers = "v2c"; break;
1499     case SNMP_VERSION_3: snmpvers = "v3"; break;
1500   }
1501   nc_printf(ncct, "[%s %s]\n\topened: %0.3fs ago\n\tFD: %s\n\trefcnt: %d\n",
1502             ts->target, snmpvers, diff.tv_sec + (float)diff.tv_usec/1000000,
1503             fd, ts->refcnt);
1504 }
1505
1506 static int
1507 noit_console_show_snmp(mtev_console_closure_t ncct,
1508                        int argc, char **argv,
1509                        mtev_console_state_t *dstate,
1510                        void *closure) {
1511   mtev_hash_iter iter = MTEV_HASH_ITER_ZERO;
1512   uuid_t key_id;
1513   int klen;
1514   void *vts;
1515   snmp_mod_config_t *conf = closure;
1516
1517   while(mtev_hash_next(&conf->target_sessions, &iter,
1518                        (const char **)key_id, &klen,
1519                        &vts)) {
1520     struct target_session *ts = vts;
1521     nc_printf_snmpts_brief(ncct, ts);
1522   }
1523   return 0;
1524 }
1525
1526 static void
1527 register_console_snmp_commands(snmp_mod_config_t *conf) {
1528   mtev_console_state_t *tl;
1529   cmd_info_t *showcmd;
1530
1531   tl = mtev_console_state_initial();
1532   showcmd = mtev_console_state_get_cmd(tl, "show");
1533   assert(showcmd && showcmd->dstate);
1534   mtev_console_state_add_cmd(showcmd->dstate,
1535     NCSCMD("snmp", noit_console_show_snmp, NULL, NULL, conf));
1536 }
1537
1538 static __thread char linebuf[1024] = "\0";
1539 static int
1540 _private_snmp_log(int majorID, int minorID, void *serverarg, void *clientarg) {
1541   struct snmp_log_message *slm;
1542   snmp_mod_config_t *conf;
1543   size_t len;
1544   conf = clientarg;
1545   slm = serverarg;
1546   len = strlcat(linebuf, slm->msg, sizeof(linebuf));
1547   if(len > sizeof(linebuf)-1) {
1548     linebuf[sizeof(linebuf)-2] = '\n';
1549     linebuf[sizeof(linebuf)-1] = '\0';
1550   }
1551   else if(linebuf[len-1] == '\n') {
1552   }
1553   else {
1554     return 1;
1555   }
1556   mtevL(((slm->priority < LOG_NOTICE) ? nlerr : nldeb), "[pri:%d] %s",
1557         slm->priority, linebuf);
1558   linebuf[0] = '\0';
1559   return 1;
1560 }
1561
1562 static int noit_snmp_init(noit_module_t *self) {
1563   const char *opt;
1564   snmp_mod_config_t *conf;
1565
1566   conf = noit_module_get_userdata(self);
1567   if(mtev_hash_retr_str(conf->options, "debugging", strlen("debugging"), &opt)) {
1568     snmp_set_do_debugging(atoi(opt));
1569   }
1570
1571   if(!__snmp_initialize_once) {
1572     register_mib_handlers();
1573     read_premib_configs();
1574     read_configs();
1575     init_snmp("noitd");
1576     snmp_disable_stderrlog();
1577     snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING,
1578                            _private_snmp_log, conf);
1579     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_EMERG);
1580     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_ALERT);
1581     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_CRIT);
1582     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_ERR);
1583     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_WARNING);
1584     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_NOTICE);
1585     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_INFO);
1586     netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK, LOG_DEBUG);
1587     __snmp_initialize_once = 1;
1588   }
1589   if(strcmp(self->hdr.name, "snmp") == 0) {
1590     register_console_snmp_commands(conf);
1591   }
1592
1593   if(strcmp(self->hdr.name, "snmptrap") == 0) {
1594     eventer_t newe;
1595     int i, block = 0, fds = 0;
1596     fd_set fdset;
1597     struct timeval timeout = { 0, 0 };
1598     struct target_session *ts;
1599     netsnmp_transport *transport;
1600     netsnmp_session sess, *session = &sess;
1601
1602     if(!mtev_hash_retrieve(conf->options,
1603                            "snmptrapd_port", strlen("snmptrapd_port"),
1604                            (void **)&opt))
1605       opt = "162";
1606
1607     transport = netsnmp_transport_open_server("snmptrap", opt);
1608     if(!transport) {
1609       mtevL(nlerr, "cannot open netsnmp transport for trap daemon\n");
1610       return -1;
1611     }
1612     ts = _get_target_session(self, "snmptrapd", SNMP_DEFAULT_VERSION);
1613     snmp_sess_init(session);
1614     session->peername = SNMP_DEFAULT_PEERNAME;
1615     session->version = SNMP_DEFAULT_VERSION;
1616     session->community_len = SNMP_DEFAULT_COMMUNITY_LEN;
1617     session->retries = SNMP_DEFAULT_RETRIES;
1618     session->timeout = SNMP_DEFAULT_TIMEOUT;
1619     session->callback = noit_snmp_trapd_response;
1620     session->callback_magic = (void *) ts;
1621     session->authenticator = NULL;
1622     session->isAuthoritative = SNMP_SESS_UNKNOWNAUTH;
1623     ts->slp = snmp_sess_add(session, transport, NULL, NULL);
1624
1625     FD_ZERO(&fdset);
1626     snmp_sess_select_info(ts->slp, &fds, &fdset, &timeout, &block);
1627     assert(fds > 0);
1628     for(i=0; i<fds; i++) {
1629       if(FD_ISSET(i, &fdset)) {
1630         ts->refcnt++;
1631         ts->fd = i;
1632         newe = eventer_alloc();
1633         newe->fd = ts->fd;
1634         newe->callback = noit_snmp_handler;
1635         newe->closure = ts;
1636         newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
1637         eventer_add(newe);
1638       }
1639     }
1640   }
1641   return 0;
1642 }
1643
1644 #include "snmp.xmlh"
1645 noit_module_t snmp = {
1646   {
1647     .magic = NOIT_MODULE_MAGIC,
1648     .version = NOIT_MODULE_ABI_VERSION,
1649     .name = "snmp",
1650     .description = "SNMP collection",
1651     .xml_description = snmp_xml_description,
1652     .onload = noit_snmp_onload
1653   },
1654   noit_snmp_config,
1655   noit_snmp_init,
1656   noit_snmp_initiate_check,
1657   NULL, /* noit_snmp_cleanup */
1658   .thread_unsafe = 1
1659 };
1660
1661 #include "snmptrap.xmlh"
1662 noit_module_t snmptrap = {
1663   {
1664     .magic = NOIT_MODULE_MAGIC,
1665     .version = NOIT_MODULE_ABI_VERSION,
1666     .name = "snmptrap",
1667     .description = "SNMP trap collection",
1668     .xml_description = snmptrap_xml_description,
1669     .onload = noit_snmptrap_onload
1670   },
1671   noit_snmp_config,
1672   noit_snmp_init,
1673   noit_snmptrap_initiate_check,
1674   NULL, /* noit_snmp_cleanup */
1675   .thread_unsafe = 1
1676 };
Note: See TracBrowser for help on using the browser.