1 /*----------------------------------------------------------------------------*/
2 /* Xymon monitor SNMP data collection tool */
3 /* */
4 /* Copyright (C) 2007-2011 Henrik Storner <henrik@hswn.dk> */
5 /* */
6 /* Inspired by the asyncapp.c file from the "NET-SNMP demo", available from */
7 /* the Net-SNMP website. This file carries the attribution */
8 /* "Niels Baggesen (Niels.Baggesen@uni-c.dk), 1999." */
9 /* */
10 /* This program is released under the GNU General Public License (GPL), */
11 /* version 2. See the file "COPYING" for details. */
12 /* */
13 /*----------------------------------------------------------------------------*/
14
15 static char rcsid[] = "$Id: xymon-snmpcollect.c 8069 2019-07-23 15:29:06Z jccleaver $";
16
17 #include <net-snmp/net-snmp-config.h>
18 #include <net-snmp/net-snmp-includes.h>
19
20 #include <limits.h>
21
22 #include "libxymon.h"
23
24 /* ----------------- struct's used for the host/requests we need to do ---------------- */
25 /* List of the OID's we will request */
26 typedef struct oid_t {
27 mibdef_t *mib; /* pointer to the mib definition for mibs */
28 oid Oid[MAX_OID_LEN]; /* the internal OID representation */
29 size_t OidLen; /* size of the oid */
30 char *devname; /* Users' chosen device name. May be a key (e.g "eth0") */
31 oidds_t *oiddef; /* Points to the oidds_t definition */
32 int setnumber; /* All vars fetched in one PDU's have the same setnumber */
33 char *result; /* the printable result data */
34 struct oid_t *next;
35 } oid_t;
36
37 /* Used for requests where we must determine the appropriate table index first */
38 typedef struct keyrecord_t {
39 mibdef_t *mib; /* Pointer to the mib definition */
40 mibidx_t *indexmethod; /* Pointer to the mib index definition */
41 char *key; /* The user-provided key we must find */
42 char *indexoid; /* Result: Index part of the OID */
43 struct keyrecord_t *next;
44 } keyrecord_t;
45
46 /* A host and the OID's we will be polling */
47 typedef struct req_t {
48 char *hostname; /* Hostname used for reporting to Xymon */
49 char *hostip[10]; /* Hostname(s) or IP(s) used for testing. Max 10 IP's */
50 int hostipidx; /* Index into hostip[] for the active IP we use */
51 long version; /* SNMP version to use */
52 unsigned char *community; /* Community name used to access the SNMP daemon (v1, v2c) */
53 unsigned char *username; /* Username used to access the SNMP daemon (v3) */
54 unsigned char *passphrase; /* Passphrase used to access the SNMP daemon (v3) */
55 enum {
56 SNMP_V3AUTH_MD5,
57 SNMP_V3AUTH_SHA1
58 } authmethod; /* Authentication method (v3) */
59 int setnumber; /* Per-host setnumber used while building requests */
60 struct snmp_session *sess; /* SNMP session data */
61
62 keyrecord_t *keyrecords, *currentkey; /* For keyed requests: Key records */
63
64 oid_t *oidhead, *oidtail; /* List of the OID's we will fetch */
65 oid_t *curr_oid, *next_oid; /* Current- and next-OID pointers while fetching data */
66 struct req_t *next;
67 } req_t;
68
69
70 /* Global variables */
71 req_t *reqhead = NULL; /* Holds the list of requests */
72 int active_requests = 0; /* Number of active SNMP requests in flight */
73
74 /* dataoperation tracks what we are currently doing */
75 enum {
76 GET_KEYS, /* Scan for the keys */
77 GET_DATA /* Fetch the actual data */
78 } dataoperation;
79
80 /* Tuneables */
81 int max_pending_requests = 30;
82 int retries = 0; /* Number of retries before timeout. 0 = Net-SNMP default (5). */
83 long timeout = 0; /* Number of uS until first timeout, then exponential backoff. 0 = Net-SNMP default (1 second). */
84
85 /* Statistics */
86 char *reportcolumn = NULL;
87 int varcount = 0;
88 int pducount = 0;
89 int okcount = 0;
90 int toobigcount = 0;
91 int timeoutcount = 0;
92 int errorcount = 0;
93 struct timeval starttv, endtv;
94
95
96
97 /* Must forward declare these */
98 void startonehost(struct req_t *r, int ipchange);
99 void starthosts(int resetstart);
100
101
generate_datarequest(req_t * item)102 struct snmp_pdu *generate_datarequest(req_t *item)
103 {
104 struct snmp_pdu *req;
105 int currentset;
106
107 if (!item->next_oid) return NULL;
108
109 req = snmp_pdu_create(SNMP_MSG_GET);
110 pducount++;
111 item->curr_oid = item->next_oid;
112 currentset = item->next_oid->setnumber;
113 while (item->next_oid && (currentset == item->next_oid->setnumber)) {
114 varcount++;
115 snmp_add_null_var(req, item->next_oid->Oid, item->next_oid->OidLen);
116 item->next_oid = item->next_oid->next;
117 }
118
119 return req;
120 }
121
122
123 /*
124 * Store data received in response PDU
125 */
print_result(int status,req_t * req,struct snmp_pdu * pdu)126 int print_result (int status, req_t *req, struct snmp_pdu *pdu)
127 {
128 struct variable_list *vp;
129 size_t len;
130 keyrecord_t *kwalk;
131 int keyoidlen;
132 oid_t *owalk;
133 int done;
134
135 switch (status) {
136 case STAT_SUCCESS:
137 if (pdu->errstat == SNMP_ERR_NOERROR) {
138 unsigned char *valstr = NULL, *oidstr = NULL;
139 size_t valsz = 0, oidsz = 0;
140
141 okcount++;
142
143 switch (dataoperation) {
144 case GET_KEYS:
145 /*
146 * Find the keyrecord currently processed for this request, and
147 * look through the unresolved keys to see if we have a match.
148 * If we do, determine the index for data retrieval.
149 */
150 vp = pdu->variables;
151 len = 0; sprint_realloc_value(&valstr, &valsz, &len, 1, vp->name, vp->name_length, vp);
152 len = 0; sprint_realloc_objid(&oidstr, &oidsz, &len, 1, vp->name, vp->name_length);
153 dbgprintf("Got key-oid '%s' = '%s'\n", oidstr, valstr);
154 for (kwalk = req->currentkey, done = 0; (kwalk && !done); kwalk = kwalk->next) {
155 /* Skip records where we have the result already, or that are not keyed */
156 if (kwalk->indexoid || (kwalk->indexmethod != req->currentkey->indexmethod)) {
157 continue;
158 }
159
160 keyoidlen = strlen(req->currentkey->indexmethod->keyoid);
161
162 switch (kwalk->indexmethod->idxtype) {
163 case MIB_INDEX_IN_OID:
164 /* Does the key match the value we just got? */
165 if (*kwalk->key == '*') {
166 /* Match all. Add an extra key-record at the end. */
167 keyrecord_t *newkey;
168
169 newkey = (keyrecord_t *)calloc(1, sizeof(keyrecord_t));
170 memcpy(newkey, kwalk, sizeof(keyrecord_t));
171 newkey->indexoid = strdup(oidstr + keyoidlen + 1);
172 newkey->key = valstr; valstr = NULL;
173 newkey->next = kwalk->next;
174 kwalk->next = newkey;
175 done = 1;
176 }
177 else if (strcmp(valstr, kwalk->key) == 0) {
178 /* Grab the index part of the OID */
179 kwalk->indexoid = strdup(oidstr + keyoidlen + 1);
180 done = 1;
181 }
182 break;
183
184 case MIB_INDEX_IN_VALUE:
185 /* Does the key match the index-part of the result OID? */
186 if (*kwalk->key == '*') {
187 /* Match all. Add an extra key-record at the end. */
188 keyrecord_t *newkey;
189
190 newkey = (keyrecord_t *)calloc(1, sizeof(keyrecord_t));
191 memcpy(newkey, kwalk, sizeof(keyrecord_t));
192 newkey->indexoid = valstr; valstr = NULL;
193 newkey->key = strdup(oidstr + keyoidlen + 1);
194 newkey->next = kwalk->next;
195 kwalk->next = newkey;
196 done = 1;
197 }
198 else if ((*(oidstr+keyoidlen) == '.') && (strcmp(oidstr+keyoidlen+1, kwalk->key)) == 0) {
199 /*
200 * Grab the index which is the value.
201 * Avoid a strdup by grabbing the valstr pointer.
202 */
203 kwalk->indexoid = valstr; valstr = NULL; valsz = 0;
204 done = 1;
205 }
206 break;
207 }
208 }
209 break;
210
211 case GET_DATA:
212 owalk = req->curr_oid;
213 vp = pdu->variables;
214 while (vp) {
215 valsz = len = 0;
216 sprint_realloc_value((unsigned char **)&owalk->result, &valsz, &len, 1,
217 vp->name, vp->name_length, vp);
218 owalk = owalk->next; vp = vp->next_variable;
219 }
220 break;
221 }
222
223 if (valstr) xfree(valstr);
224 if (oidstr) xfree(oidstr);
225 }
226 else {
227 errorcount++;
228 errprintf("ERROR %s: %s\n", req->hostip[req->hostipidx], snmp_errstring(pdu->errstat));
229 }
230 return 1;
231
232 case STAT_TIMEOUT:
233 timeoutcount++;
234 dbgprintf("%s: Timeout\n", req->hostip);
235 if (req->hostip[req->hostipidx+1]) {
236 req->hostipidx++;
237 startonehost(req, 1);
238 }
239 return 0;
240
241 case STAT_ERROR:
242 errorcount++;
243 snmp_sess_perror(req->hostip[req->hostipidx], req->sess);
244 return 0;
245 }
246
247 return 0;
248 }
249
250
251 /*
252 * response handler
253 */
asynch_response(int operation,struct snmp_session * sp,int reqid,struct snmp_pdu * pdu,void * magic)254 int asynch_response(int operation, struct snmp_session *sp, int reqid, struct snmp_pdu *pdu, void *magic)
255 {
256 struct req_t *req = (struct req_t *)magic;
257
258 if (operation == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) {
259 struct snmp_pdu *snmpreq = NULL;
260 int okoid = 1;
261
262 if (dataoperation == GET_KEYS) {
263 /*
264 * We're doing GETNEXT's when retrieving keys, so we will get a response
265 * which has nothing really to do with the data we're looking for. In that
266 * case, we should NOT process data from this response.
267 */
268 struct variable_list *vp = pdu->variables;
269
270 okoid = ((vp->name_length >= req->currentkey->indexmethod->rootoidlen) &&
271 (memcmp(req->currentkey->indexmethod->rootoid, vp->name, req->currentkey->indexmethod->rootoidlen * sizeof(oid)) == 0));
272 }
273
274 switch (pdu->errstat) {
275 case SNMP_ERR_NOERROR:
276 /* Pick up the results, but only if the OID is valid */
277 if (okoid) print_result(STAT_SUCCESS, req, pdu);
278 break;
279
280 case SNMP_ERR_NOSUCHNAME:
281 dbgprintf("Host %s item %s: No such name\n", req->hostname, req->curr_oid->devname);
282 if (req->hostip[req->hostipidx+1]) {
283 req->hostipidx++;
284 startonehost(req, 1);
285 }
286 break;
287
288 case SNMP_ERR_TOOBIG:
289 toobigcount++;
290 errprintf("Host %s item %s: Response too big\n", req->hostname, req->curr_oid->devname);
291 break;
292
293 default:
294 errorcount++;
295 errprintf("Host %s item %s: SNMP error %d\n", req->hostname, req->curr_oid->devname, pdu->errstat);
296 break;
297 }
298
299 /* Now see if we should send another request */
300 switch (dataoperation) {
301 case GET_KEYS:
302 /*
303 * While fetching keys, walk the current key-table until we reach the end of the table.
304 * When we reach the end of one key-table, start with the next.
305 * FIXME: Could optimize so we don't fetch the whole table, but only those rows we need.
306 */
307 if (pdu->errstat == SNMP_ERR_NOERROR) {
308 struct variable_list *vp = pdu->variables;
309
310 if ( (vp->name_length >= req->currentkey->indexmethod->rootoidlen) &&
311 (memcmp(req->currentkey->indexmethod->rootoid, vp->name, req->currentkey->indexmethod->rootoidlen * sizeof(oid)) == 0) ) {
312 /* Still more data in the current key table, get the next row */
313 snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT);
314 pducount++;
315 /* Probably only one variable to fetch, but never mind ... */
316 while (vp) {
317 varcount++;
318 snmp_add_null_var(snmpreq, vp->name, vp->name_length);
319 vp = vp->next_variable;
320 }
321 }
322 else {
323 /* End of current key table. If more keys to be found, start the next table. */
324 do {
325 req->currentkey = req->currentkey->next;
326 } while (req->currentkey && req->currentkey->indexoid);
327
328 if (req->currentkey) {
329 snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT);
330 pducount++;
331 snmp_add_null_var(snmpreq,
332 req->currentkey->indexmethod->rootoid,
333 req->currentkey->indexmethod->rootoidlen);
334 }
335 }
336 }
337 break;
338
339 case GET_DATA:
340 /* Generate a request for the next dataset, if any */
341 if (req->next_oid) {
342 snmpreq = generate_datarequest(req);
343 }
344 else {
345 dbgprintf("No more oids left\n");
346 }
347 break;
348 }
349
350 /* Send the request we just made */
351 if (snmpreq) {
352 if (snmp_send(req->sess, snmpreq))
353 goto finish;
354 else {
355 snmp_sess_perror("snmp_send", req->sess);
356 snmp_free_pdu(snmpreq);
357 }
358 }
359 }
360 else {
361 dbgprintf("operation not successful: %d\n", operation);
362 print_result(STAT_TIMEOUT, req, pdu);
363 }
364
365 /*
366 * Something went wrong (or end of variables).
367 * This host not active any more
368 */
369 dbgprintf("Finished host %s\n", req->hostname);
370 active_requests--;
371
372 finish:
373 /* Start some more hosts */
374 starthosts(0);
375
376 return 1;
377 }
378
379
startonehost(struct req_t * req,int ipchange)380 void startonehost(struct req_t *req, int ipchange)
381 {
382 struct snmp_session s;
383 struct snmp_pdu *snmpreq = NULL;
384
385 if ((dataoperation < GET_DATA) && !req->currentkey) return;
386
387 /* Are we retrying a cluster with a new IP? Then drop the current session */
388 if (req->sess && ipchange) {
389 /*
390 * Apparently, we cannot close a session while in a callback.
391 * So leave this for now - it will leak some memory, but
392 * this is not a problem as long as we only run once.
393 */
394 /* snmp_close(req->sess); */
395 req->sess = NULL;
396 }
397
398 /* Setup the SNMP session */
399 if (!req->sess) {
400 snmp_sess_init(&s);
401
402 /*
403 * snmp_session has a "remote_port" field, but it does not work.
404 * Instead, the peername should include a port number (IP:PORT)
405 * if (req->portnumber) s.remote_port = req->portnumber;
406 */
407 s.peername = req->hostip[req->hostipidx];
408
409 /* Set the SNMP version and authentication token(s) */
410 s.version = req->version;
411 switch (s.version) {
412 case SNMP_VERSION_1:
413 case SNMP_VERSION_2c:
414 s.community = req->community;
415 s.community_len = strlen((char *)req->community);
416 break;
417
418 case SNMP_VERSION_3:
419 /* set the SNMPv3 user name */
420 s.securityName = strdup(req->username);
421 s.securityNameLen = strlen(s.securityName);
422
423 /* set the security level to authenticated, but not encrypted */
424 s.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
425
426 /* set the authentication method */
427 switch (req->authmethod) {
428 case SNMP_V3AUTH_MD5:
429 s.securityAuthProto = usmHMACMD5AuthProtocol;
430 s.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
431 s.securityAuthKeyLen = USM_AUTH_KU_LEN;
432 break;
433
434 case SNMP_V3AUTH_SHA1:
435 s.securityAuthProto = usmHMACSHA1AuthProtocol;
436 s.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid);
437 s.securityAuthKeyLen = USM_AUTH_KU_LEN;
438 break;
439 }
440
441 /*
442 * set the authentication key to a hashed version of our
443 * passphrase (which must be at least 8 characters long).
444 */
445 if (generate_Ku(s.securityAuthProto, s.securityAuthProtoLen,
446 (u_char *)req->passphrase, strlen(req->passphrase),
447 s.securityAuthKey, &s.securityAuthKeyLen) != SNMPERR_SUCCESS) {
448 errprintf("Failed to generate Ku from authentication pass phrase for host %s\n",
449 req->hostname);
450 snmp_perror("generate_Ku");
451 return;
452 }
453 break;
454 }
455
456 /* Set timeouts and retries */
457 if (timeout > 0) s.timeout = timeout;
458 if (retries > 0) s.retries = retries;
459
460 /* Setup the callback */
461 s.callback = asynch_response;
462 s.callback_magic = req;
463
464 if (!(req->sess = snmp_open(&s))) {
465 snmp_sess_perror("snmp_open", &s);
466 return;
467 }
468 }
469
470 switch (dataoperation) {
471 case GET_KEYS:
472 snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT);
473 pducount++;
474 snmp_add_null_var(snmpreq, req->currentkey->indexmethod->rootoid, req->currentkey->indexmethod->rootoidlen);
475 break;
476
477 case GET_DATA:
478 /* Build the request PDU and send it */
479 snmpreq = generate_datarequest(req);
480 break;
481 }
482
483 if (!snmpreq) return;
484
485 if (snmp_send(req->sess, snmpreq))
486 active_requests++;
487 else {
488 errorcount++;
489 snmp_sess_perror("snmp_send", req->sess);
490 snmp_free_pdu(snmpreq);
491 }
492 }
493
494
starthosts(int resetstart)495 void starthosts(int resetstart)
496 {
497 static req_t *startpoint = NULL;
498 static int haverun = 0;
499
500 struct req_t *rwalk;
501
502 if (resetstart) {
503 startpoint = NULL;
504 haverun = 0;
505 }
506
507 if (startpoint == NULL) {
508 if (haverun) {
509 return;
510 }
511 else {
512 startpoint = reqhead;
513 haverun = 1;
514 }
515 }
516
517 /* startup as many hosts as we want to run in parallel */
518 for (rwalk = startpoint; (rwalk && (active_requests <= max_pending_requests)); rwalk = rwalk->next) {
519 startonehost(rwalk, 0);
520 }
521
522 startpoint = rwalk;
523 }
524
525
stophosts(void)526 void stophosts(void)
527 {
528 struct req_t *rwalk;
529
530 for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) {
531 if (rwalk->sess) {
532 snmp_close(rwalk->sess);
533 }
534 }
535 }
536
537
538 /*
539 * This routine loads MIB files, and computes the key-OID values.
540 * We defer this until the mib-definition is actually being referred to
541 * in snmphosts.cfg, because lots of MIB's are not being used
542 * (and probably do not exist on the host where we're running) and
543 * to avoid spending a lot of time to load MIB's that are not used.
544 */
setupmib(mibdef_t * mib,int verbose)545 void setupmib(mibdef_t *mib, int verbose)
546 {
547 mibidx_t *iwalk;
548 size_t sz, len;
549
550 if (mib->loadstatus != MIB_STATUS_NOTLOADED) return;
551
552 if (mib->mibfn && (read_mib(mib->mibfn) == NULL)) {
553 mib->loadstatus = MIB_STATUS_LOADFAILED;
554 if (verbose) {
555 errprintf("Failed to read MIB file %s\n", mib->mibfn);
556 snmp_perror("read_objid");
557 }
558 }
559
560 for (iwalk = mib->idxlist; (iwalk); iwalk = iwalk->next) {
561 iwalk->rootoid = calloc(MAX_OID_LEN, sizeof(oid));
562 iwalk->rootoidlen = MAX_OID_LEN;
563 if (read_objid(iwalk->keyoid, iwalk->rootoid, &iwalk->rootoidlen)) {
564 /* Re-use the iwalk->keyoid buffer */
565 sz = strlen(iwalk->keyoid) + 1; len = 0;
566 sprint_realloc_objid((unsigned char **)&iwalk->keyoid, &sz, &len, 1, iwalk->rootoid, iwalk->rootoidlen);
567 }
568 else {
569 mib->loadstatus = MIB_STATUS_LOADFAILED;
570 if (verbose) {
571 errprintf("Cannot determine OID for %s\n", iwalk->keyoid);
572 snmp_perror("read_objid");
573 }
574 }
575 }
576
577 mib->loadstatus = MIB_STATUS_LOADED;
578 }
579
580 /*
581 *
582 * Config file syntax
583 *
584 * [HOSTNAME]
585 * ip=ADDRESS[:PORT]
586 * version=VERSION
587 * community=COMMUNITY
588 * username=USERNAME
589 * passphrase=PASSPHRASE
590 * authmethod=[MD5|SHA1]
591 * mibname1[=index]
592 * mibname2[=index]
593 * mibname3[=index]
594 *
595 */
596
make_oitem(mibdef_t * mib,char * devname,oidds_t * oiddef,char * oidstr,struct req_t * reqitem)597 static oid_t *make_oitem(mibdef_t *mib, char *devname, oidds_t *oiddef, char *oidstr, struct req_t *reqitem)
598 {
599 oid_t *oitem = (oid_t *)calloc(1, sizeof(oid_t));
600
601 oitem->mib = mib;
602 oitem->devname = strdup(devname);
603 oitem->setnumber = reqitem->setnumber;
604 oitem->oiddef = oiddef;
605 oitem->OidLen = sizeof(oitem->Oid)/sizeof(oitem->Oid[0]);
606 if (read_objid(oidstr, oitem->Oid, &oitem->OidLen)) {
607 if (!reqitem->oidhead) reqitem->oidhead = oitem; else reqitem->oidtail->next = oitem;
608 reqitem->oidtail = oitem;
609 }
610 else {
611 /* Could not parse the OID definition */
612 errprintf("Cannot determine OID for %s\n", oidstr);
613 snmp_perror("read_objid");
614 xfree(oitem->devname);
615 xfree(oitem);
616 }
617
618 return oitem;
619 }
620
621
readconfig(char * cfgfn,int verbose)622 void readconfig(char *cfgfn, int verbose)
623 {
624 static void *cfgfiles = NULL;
625 FILE *cfgfd;
626 strbuffer_t *inbuf;
627
628 struct req_t *reqitem = NULL;
629 int tasksleep = atoi(xgetenv("TASKSLEEP"));
630
631 mibdef_t *mib;
632
633 /* Check if config was modified */
634 if (cfgfiles) {
635 if (!stackfmodified(cfgfiles)) {
636 dbgprintf("No files changed, skipping reload\n");
637 return;
638 }
639 else {
640 stackfclist(&cfgfiles);
641 cfgfiles = NULL;
642 }
643 }
644
645 cfgfd = stackfopen(cfgfn, "r", &cfgfiles);
646 if (cfgfd == NULL) {
647 errprintf("Cannot open configuration files %s\n", cfgfn);
648 return;
649 }
650
651 inbuf = newstrbuffer(0);
652 while (stackfgets(inbuf, NULL)) {
653 char *bot, *p, *mibidx;
654 char savech;
655
656 sanitize_input(inbuf, 0, 0);
657 bot = STRBUF(inbuf) + strspn(STRBUF(inbuf), " \t");
658 if ((*bot == '\0') || (*bot == '#')) continue;
659
660 if (*bot == '[') {
661 char *intvl = strchr(bot, '/');
662
663 /*
664 * See if we're running a non-standard interval.
665 * If yes, then process only the records that match
666 * this TASKSLEEP setting.
667 */
668 if (tasksleep != 300) {
669 /* Non-default interval. Skip the host if it HASN'T got an interval setting */
670 if (!intvl) continue;
671
672 /* Also skip the hosts that have an interval different from the current */
673 *intvl = '\0'; /* Clip the interval from the hostname */
674 if (atoi(intvl+1) != tasksleep) continue;
675 }
676 else {
677 /* Default interval. Skip the host if it HAS an interval setting */
678 if (intvl) continue;
679 }
680
681 reqitem = (req_t *)calloc(1, sizeof(req_t));
682
683 p = strchr(bot, ']'); if (p) *p = '\0';
684 reqitem->hostname = strdup(bot + 1);
685 if (p) *p = ']';
686
687 reqitem->hostip[0] = reqitem->hostname;
688 reqitem->version = SNMP_VERSION_1;
689 reqitem->authmethod = SNMP_V3AUTH_MD5;
690 reqitem->next = reqhead;
691 reqhead = reqitem;
692
693 continue;
694 }
695
696 /* If we have nowhere to put the data, then skip further processing */
697 if (!reqitem) continue;
698
699 if (strncmp(bot, "ip=", 3) == 0) {
700 char *nextip = strtok(strdup(bot+3), ",");
701 int i = 0;
702
703 do {
704 reqitem->hostip[i++] = nextip;
705 nextip = strtok(NULL, ",");
706 } while (nextip);
707 continue;
708 }
709
710 if (strncmp(bot, "version=", 8) == 0) {
711 switch (*(bot+8)) {
712 case '1': reqitem->version = SNMP_VERSION_1; break;
713 case '2': reqitem->version = SNMP_VERSION_2c; break;
714 case '3': reqitem->version = SNMP_VERSION_3; break;
715 }
716 continue;
717 }
718
719 if (strncmp(bot, "community=", 10) == 0) {
720 reqitem->community = strdup(bot+10);
721 continue;
722 }
723
724 if (strncmp(bot, "username=", 9) == 0) {
725 reqitem->username = strdup(bot+9);
726 continue;
727 }
728
729 if (strncmp(bot, "passphrase=", 11) == 0) {
730 reqitem->passphrase = strdup(bot+11);
731 continue;
732 }
733
734 if (strncmp(bot, "authmethod=", 11) == 0) {
735 if (strcasecmp(bot+11, "md5") == 0)
736 reqitem->authmethod = SNMP_V3AUTH_MD5;
737 else if (strcasecmp(bot+11, "sha1") == 0)
738 reqitem->authmethod = SNMP_V3AUTH_SHA1;
739 else
740 errprintf("Unknown SNMPv3 authentication method '%s'\n", bot+11);
741
742 continue;
743 }
744
745 /* Custom mibs */
746 p = bot + strcspn(bot, "= \t\r\n"); savech = *p; *p = '\0';
747 mib = find_mib(bot);
748 *p = savech;
749 p += strspn(p, "= \t");
750 mibidx = p;
751 if (mib) {
752 int i;
753 mibidx_t *iwalk = NULL;
754 char *oid;
755 SBUF_DEFINE(oidbuf);
756 char *devname;
757 oidset_t *swalk;
758
759 setupmib(mib, verbose);
760 if (mib->loadstatus != MIB_STATUS_LOADED) continue; /* Cannot use this MIB */
761
762 /* See if this is an entry where we must determine the index ourselves */
763 if (*mibidx) {
764 for (iwalk = mib->idxlist; (iwalk && (*mibidx != iwalk->marker)); iwalk = iwalk->next) ;
765 }
766
767 if ((*mibidx == '*') && !iwalk) {
768 errprintf("Cannot do wildcard matching without an index (host %s, mib %s)\n",
769 reqitem->hostname, mib->mibname);
770 continue;
771 }
772
773 if (!iwalk) {
774 /* No key lookup */
775 swalk = mib->oidlisthead;
776 while (swalk) {
777 reqitem->setnumber++;
778
779 for (i=0; (i <= swalk->oidcount); i++) {
780 if (*mibidx) {
781 SBUF_MALLOC(oidbuf, strlen(swalk->oids[i].oid) + strlen(mibidx) + 2);
782 oid = oidbuf;
783 snprintf(oidbuf, oidbuf_buflen, "%s.%s", swalk->oids[i].oid, mibidx);
784 devname = mibidx;
785 }
786 else {
787 oid = swalk->oids[i].oid;
788 oidbuf = NULL; oidbuf_buflen = 0;
789 devname = "-";
790 }
791
792 make_oitem(mib, devname, &swalk->oids[i], oid, reqitem);
793 if (oidbuf) xfree(oidbuf);
794 }
795
796 swalk = swalk->next;
797 }
798
799 reqitem->next_oid = reqitem->oidhead;
800 }
801 else {
802 /* Add a key-record so we can try to locate the index */
803 keyrecord_t *newitem = (keyrecord_t *)calloc(1, sizeof(keyrecord_t));
804 char endmarks[6];
805
806 mibidx++; /* Skip the key-marker */
807 snprintf(endmarks, sizeof(endmarks), "%s%c", ")]}>", iwalk->marker);
808 p = mibidx + strcspn(mibidx, endmarks); *p = '\0';
809 newitem->key = strdup(mibidx);
810 newitem->indexmethod = iwalk;
811 newitem->mib = mib;
812 newitem->next = reqitem->keyrecords;
813 reqitem->currentkey = reqitem->keyrecords = newitem;
814 }
815
816 continue;
817 }
818 else {
819 errprintf("Unknown MIB (not in snmpmibs.cfg): '%s'\n", bot);
820 }
821 }
822
823 stackfclose(cfgfd);
824 freestrbuffer(inbuf);
825 }
826
827
communicate(void)828 void communicate(void)
829 {
830 /* loop while any active requests */
831 while (active_requests) {
832 int fds = 0, block = 1;
833 fd_set fdset;
834 struct timeval timeout;
835
836 FD_ZERO(&fdset);
837 snmp_select_info(&fds, &fdset, &timeout, &block);
838 fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout);
839 if (fds < 0) {
840 perror("select failed");
841 exit(1);
842 }
843 if (fds)
844 snmp_read(&fdset);
845 else
846 snmp_timeout();
847 }
848 }
849
850
resolvekeys(void)851 void resolvekeys(void)
852 {
853 req_t *rwalk;
854 keyrecord_t *kwalk;
855 oidset_t *swalk;
856 int i;
857 SBUF_DEFINE(oid);
858
859 /* Fetch the key data, and determine the indices we want to use */
860 dataoperation = GET_KEYS;
861 starthosts(1);
862 communicate();
863
864 /* Generate new requests for the datasets we now know the indices of */
865 for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) {
866 if (!rwalk->keyrecords) continue;
867
868 for (kwalk = rwalk->keyrecords; (kwalk); kwalk = kwalk->next) {
869 if (!kwalk->indexoid) {
870 /* Don't report failed lookups for the pseudo match-all key record */
871 if (*kwalk->key != '*') {
872 /* We failed to determine the index */
873 errprintf("Could not determine index for host=%s mib=%s key=%s\n",
874 rwalk->hostname, kwalk->mib->mibname, kwalk->key);
875 }
876 continue;
877 }
878
879 swalk = kwalk->mib->oidlisthead;
880 while (swalk) {
881
882 rwalk->setnumber++;
883
884 for (i=0; (i <= swalk->oidcount); i++) {
885 SBUF_MALLOC(oid, strlen(swalk->oids[i].oid) + strlen(kwalk->indexoid) + 2);
886 snprintf(oid, oid_buflen, "%s.%s", swalk->oids[i].oid, kwalk->indexoid);
887 make_oitem(kwalk->mib, kwalk->key, &swalk->oids[i], oid, rwalk);
888 xfree(oid);
889 }
890
891 swalk = swalk->next;
892 }
893
894 rwalk->next_oid = rwalk->oidhead;
895 }
896 }
897 }
898
899
getdata(void)900 void getdata(void)
901 {
902 dataoperation = GET_DATA;
903 starthosts(1);
904 communicate();
905 }
906
907
sendresult(void)908 void sendresult(void)
909 {
910 struct req_t *rwalk;
911 struct oid_t *owalk;
912 char msgline[1024];
913 char *currdev, *currhost;
914 mibdef_t *mib;
915 strbuffer_t *clientmsg = newstrbuffer(0);
916 int havemsg = 0;
917 int itemcount = 0;
918
919 currhost = "";
920 for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) {
921 if (strcmp(rwalk->hostname, currhost) != 0) {
922 /* Flush buffer */
923 if (havemsg) {
924 snprintf(msgline, sizeof(msgline), "\n.<!-- linecount=%d -->\n", itemcount);
925 addtobuffer(clientmsg, msgline);
926 sendmessage(STRBUF(clientmsg), NULL, XYMON_TIMEOUT, NULL);
927 }
928 clearstrbuffer(clientmsg);
929 havemsg = 0;
930 itemcount = 0;
931
932 snprintf(msgline, sizeof(msgline), "client/snmpcollect %s.snmpcollect snmp\n\n", rwalk->hostname);
933 addtobuffer(clientmsg, msgline);
934 }
935
936 currdev = "";
937
938 for (mib = first_mib(); (mib); mib = next_mib()) {
939 clearstrbuffer(mib->resultbuf);
940 mib->haveresult = 0;
941
942 snprintf(msgline, sizeof(msgline), "\n[%s]\nInterval=%d\nActiveIP=%s\n\n",
943 mib->mibname,
944 atoi(xgetenv("TASKSLEEP")),
945 rwalk->hostip[rwalk->hostipidx]);
946 addtobuffer(mib->resultbuf, msgline);
947 }
948
949 for (owalk = rwalk->oidhead; (owalk); owalk = owalk->next) {
950 if (strcmp(currdev, owalk->devname)) {
951 currdev = owalk->devname; /* OK, because ->devname is permanent */
952
953 if (*owalk->devname && (*owalk->devname != '-') ) {
954 addtobuffer(owalk->mib->resultbuf, "\n<");
955 addtobuffer(owalk->mib->resultbuf, owalk->devname);
956 addtobuffer(owalk->mib->resultbuf, ">\n");
957 itemcount++;
958 }
959 }
960
961 addtobuffer(owalk->mib->resultbuf, "\t");
962 addtobuffer(owalk->mib->resultbuf, owalk->oiddef->dsname);
963 addtobuffer(owalk->mib->resultbuf, " = ");
964 if (owalk->result) {
965 int ival;
966 unsigned int uval;
967
968 switch (owalk->oiddef->conversion) {
969 case OID_CONVERT_U32:
970 ival = atoi(owalk->result);
971 memcpy(&uval, &ival, sizeof(uval));
972 snprintf(msgline, sizeof(msgline), "%u", uval);
973 addtobuffer(owalk->mib->resultbuf, msgline);
974 break;
975
976 default:
977 addtobuffer(owalk->mib->resultbuf, owalk->result);
978 break;
979 }
980 }
981 else
982 addtobuffer(owalk->mib->resultbuf, "NODATA");
983 addtobuffer(owalk->mib->resultbuf, "\n");
984 owalk->mib->haveresult = 1;
985 }
986
987 for (mib = first_mib(); (mib); mib = next_mib()) {
988 if (mib->haveresult) {
989 addtostrbuffer(clientmsg, mib->resultbuf);
990 havemsg = 1;
991 }
992 }
993 }
994
995 if (havemsg) {
996 sendmessage(STRBUF(clientmsg), NULL, XYMON_TIMEOUT, NULL);
997 }
998
999 freestrbuffer(clientmsg);
1000 }
1001
egoresult(int color,char * egocolumn)1002 void egoresult(int color, char *egocolumn)
1003 {
1004 char msgline[1024];
1005 char *timestamps = NULL;
1006
1007 init_timestamp();
1008
1009 combo_start();
1010 init_status(color);
1011 snprintf(msgline, sizeof(msgline), "status %s.%s %s snmpcollect %s\n\n",
1012 xgetenv("MACHINE"), egocolumn, colorname(color), timestamp);
1013 addtostatus(msgline);
1014
1015 snprintf(msgline, sizeof(msgline), "Variables : %d\n", varcount);
1016 addtostatus(msgline);
1017 snprintf(msgline, sizeof(msgline), "PDUs : %d\n", pducount);
1018 addtostatus(msgline);
1019 snprintf(msgline, sizeof(msgline), "Responses : %d\n", okcount);
1020 addtostatus(msgline);
1021 snprintf(msgline, sizeof(msgline), "Timeouts : %d\n", timeoutcount);
1022 addtostatus(msgline);
1023 snprintf(msgline, sizeof(msgline), "Too big : %d\n", toobigcount);
1024 addtostatus(msgline);
1025 snprintf(msgline, sizeof(msgline), "Errors : %d\n", errorcount);
1026 addtostatus(msgline);
1027
1028 show_timestamps(×tamps);
1029 if (timestamps) {
1030 addtostatus(timestamps);
1031 xfree(timestamps);
1032 }
1033
1034 finish_status();
1035 combo_end();
1036
1037 }
1038
1039
main(int argc,char ** argv)1040 int main (int argc, char **argv)
1041 {
1042 int argi;
1043 SBUF_DEFINE(configfn);
1044 int cfgcheck = 0;
1045 int mibcheck = 0;
1046
1047 for (argi = 1; (argi < argc); argi++) {
1048 if (strcmp(argv[argi], "--debug") == 0) {
1049 debug = 1;
1050 }
1051 else if (strcmp(argv[argi], "--no-update") == 0) {
1052 dontsendmessages = 1;
1053 }
1054 else if (strcmp(argv[argi], "--cfgcheck") == 0) {
1055 cfgcheck = 1;
1056 }
1057 else if (strcmp(argv[argi], "--mibcheck") == 0) {
1058 mibcheck = 1;
1059 }
1060 else if (argnmatch(argv[argi], "--timeout=")) {
1061 char *p = strchr(argv[argi], '=');
1062 timeout = 1000000*atoi(p+1);
1063 }
1064 else if (argnmatch(argv[argi], "--retries=")) {
1065 char *p = strchr(argv[argi], '=');
1066 retries = atoi(p+1);
1067 }
1068 else if (argnmatch(argv[argi], "--concurrency=")) {
1069 char *p = strchr(argv[argi], '=');
1070 max_pending_requests = atoi(p+1);
1071 }
1072 else if (argnmatch(argv[argi], "--report=")) {
1073 char *p = strchr(argv[argi], '=');
1074 reportcolumn = strdup(p+1);
1075 timing = 1;
1076 }
1077 else if (*argv[argi] != '-') {
1078 configfn = strdup(argv[argi]);
1079 configfn_buflen = strlen(configfn)+1;
1080 }
1081 }
1082
1083 add_timestamp("xymon-snmpcollect startup");
1084
1085 netsnmp_register_loghandler(NETSNMP_LOGHANDLER_STDERR, 7);
1086 init_snmp("xymon-snmpcollect");
1087 snmp_mib_toggle_options("e"); /* Like -Pe: Don't show MIB parsing errors */
1088 snmp_out_toggle_options("qn"); /* Like -Oqn: OID's printed as numbers, values printed without type */
1089
1090 readmibs(NULL, mibcheck);
1091
1092 if (configfn == NULL) {
1093 configfn = (char *)malloc(PATH_MAX);
1094 snprintf(configfn, configfn_buflen, "%s/etc/snmphosts.cfg", xgetenv("XYMONHOME"));
1095 }
1096 readconfig(configfn, mibcheck);
1097 if (cfgcheck) return 0;
1098 add_timestamp("Configuration loaded");
1099
1100 resolvekeys();
1101 add_timestamp("Keys lookup complete");
1102
1103 getdata();
1104 stophosts();
1105 add_timestamp("Data retrieved");
1106
1107 sendresult();
1108 add_timestamp("Results transmitted");
1109
1110 if (reportcolumn) egoresult(COL_GREEN, reportcolumn);
1111
1112 xfree(configfn);
1113
1114 return 0;
1115 }
1116
1117