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(&timestamps);
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