Annotation of src/usr.bin/snmp/snmpc.c, Revision 1.15
1.15 ! martijn 1: /* $OpenBSD: snmpc.c,v 1.14 2019/10/07 07:39:50 bluhm Exp $ */
1.1 martijn 2:
3: /*
4: * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
5: * Copyright (c) 2013 Reyk Floeter <reyk@openbsd.org>
6: *
7: * Permission to use, copy, modify, and distribute this software for any
8: * purpose with or without fee is hereby granted, provided that the above
9: * copyright notice and this permission notice appear in all copies.
10: *
11: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18: */
19:
20: #include <sys/limits.h>
21: #include <sys/types.h>
22: #include <sys/socket.h>
23: #include <sys/un.h>
24:
25: #include <arpa/inet.h>
1.10 martijn 26: #include <openssl/evp.h>
1.1 martijn 27:
28: #include <ber.h>
1.9 martijn 29: #include <ctype.h>
1.1 martijn 30: #include <err.h>
31: #include <errno.h>
32: #include <netdb.h>
33: #include <poll.h>
34: #include <stdio.h>
35: #include <stdlib.h>
36: #include <stdint.h>
37: #include <string.h>
38: #include <time.h>
39: #include <unistd.h>
40:
41: #include "smi.h"
42: #include "snmp.h"
1.9 martijn 43: #include "usm.h"
1.1 martijn 44:
1.11 martijn 45: #define GETOPT_COMMON "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
1.1 martijn 46:
47: int snmpc_get(int, char *[]);
48: int snmpc_walk(int, char *[]);
1.13 martijn 49: int snmpc_set(int, char *[]);
1.1 martijn 50: int snmpc_trap(int, char *[]);
51: int snmpc_mibtree(int, char *[]);
1.8 martijn 52: struct snmp_agent *snmpc_connect(char *, char *);
1.1 martijn 53: int snmpc_parseagent(char *, char *);
54: int snmpc_print(struct ber_element *);
1.15 ! martijn 55: __dead void snmpc_printerror(enum snmp_error, struct ber_element *, int,
! 56: const char *);
1.9 martijn 57: char *snmpc_hex2bin(char *, size_t *);
1.13 martijn 58: struct ber_element *snmpc_varbindparse(int, char *[]);
1.1 martijn 59: void usage(void);
60:
61: struct snmp_app {
62: const char *name;
63: const int usecommonopt;
64: const char *optstring;
65: const char *usage;
1.2 deraadt 66: int (*exec)(int, char *[]);
1.1 martijn 67: };
68:
69: struct snmp_app snmp_apps[] = {
1.2 deraadt 70: { "get", 1, NULL, "agent oid ...", snmpc_get },
71: { "getnext", 1, NULL, "agent oid ...", snmpc_get },
1.7 deraadt 72: { "walk", 1, "C:", "[-C cIipt] [-C E endoid] agent [oid]", snmpc_walk },
1.2 deraadt 73: { "bulkget", 1, "C:", "[-C n<nonrep>r<maxrep>] agent oid ...", snmpc_get },
74: { "bulkwalk", 1, "C:", "[-C cipn<nonrep>r<maxrep>] agent [oid]", snmpc_walk },
1.13 martijn 75: { "set", 1, NULL, "agent oid type value [oid type value] ...", snmpc_set },
1.2 deraadt 76: { "trap", 1, NULL, "agent uptime oid [oid type value] ...", snmpc_trap },
77: { "mibtree", 0, "O:", "[-O fnS]", snmpc_mibtree }
1.1 martijn 78: };
79: struct snmp_app *snmp_app = NULL;
80:
81: char *community = "public";
1.9 martijn 82: struct snmp_v3 *v3;
1.1 martijn 83: char *mib = "mib_2";
84: int retries = 5;
85: int timeout = 1;
1.9 martijn 86: enum snmp_version version = SNMP_V2C;
1.1 martijn 87: int print_equals = 1;
88: int print_varbind_only = 0;
89: int print_summary = 0;
90: int print_time = 0;
91: int walk_check_increase = 1;
92: int walk_fallback_oid = 1;
93: int walk_include_oid = 0;
94: int smi_print_hint = 1;
95: int non_repeaters = 0;
96: int max_repetitions = 10;
97: struct ber_oid walk_end = {{0}, 0};
98: enum smi_oid_lookup oid_lookup = smi_oidl_short;
99: enum smi_output_string output_string = smi_os_default;
100:
101: int
102: main(int argc, char *argv[])
103: {
1.10 martijn 104: const EVP_MD *md = NULL;
1.11 martijn 105: const EVP_CIPHER *cipher = NULL;
1.9 martijn 106: struct snmp_sec *sec;
107: char *user = NULL;
1.10 martijn 108: enum usm_key_level authkeylevel;
109: char *authkey = NULL;
110: size_t authkeylen = 0;
1.11 martijn 111: enum usm_key_level privkeylevel;
112: char *privkey = NULL;
113: size_t privkeylen = 0;
1.9 martijn 114: int seclevel = SNMP_MSGFLAG_REPORT;
115: char *ctxname = NULL;
116: char *ctxengineid = NULL, *secengineid = NULL;
117: size_t ctxengineidlen, secengineidlen;
118: int zflag = 0;
119: long long boots, time;
1.1 martijn 120: char optstr[BUFSIZ];
121: const char *errstr;
122: char *strtolp;
123: int ch;
124: size_t i;
125:
126: if (pledge("stdio inet dns", NULL) == -1)
127: err(1, "pledge");
1.2 deraadt 128:
1.1 martijn 129: if (argc <= 1)
130: usage();
131:
1.14 bluhm 132: optstr[0] = '\0';
1.1 martijn 133: for (i = 0; i < sizeof(snmp_apps)/sizeof(*snmp_apps); i++) {
134: if (strcmp(snmp_apps[i].name, argv[1]) == 0) {
135: snmp_app = &snmp_apps[i];
136: if (snmp_app->optstring != NULL) {
137: if (strlcpy(optstr, snmp_app->optstring,
138: sizeof(optstr)) > sizeof(optstr))
139: errx(1, "strlcat");
140: }
141: break;
142: }
143: }
144: if (snmp_app == NULL)
145: usage();
146:
147: if (snmp_app->usecommonopt) {
148: if (strlcat(optstr, GETOPT_COMMON, sizeof(optstr)) >
149: sizeof(optstr))
150: errx(1, "strlcpy");
151: }
152:
153: argc--;
154: argv++;
155:
156: smi_init();
157:
158: while ((ch = getopt(argc, argv, optstr)) != -1) {
159: switch (ch) {
1.10 martijn 160: case 'A':
161: authkey = optarg;
162: authkeylen = strlen(authkey);
163: authkeylevel = USM_KEY_PASSWORD;
164: break;
165: case 'a':
166: if (strcasecmp(optarg, "MD5") == 0)
167: md = EVP_md5();
168: else if (strcasecmp(optarg, "SHA") == 0)
169: md = EVP_sha1();
170: else if (strcasecmp(optarg, "SHA-224") == 0)
171: md = EVP_sha224();
172: else if (strcasecmp(optarg, "SHA-256") == 0)
173: md = EVP_sha256();
174: else if (strcasecmp(optarg, "SHA-384") == 0)
175: md = EVP_sha384();
176: else if (strcasecmp(optarg, "SHA-512") == 0)
177: md = EVP_sha512();
178: else
179: errx(1, "Invalid authentication protocol "
180: "specified after -a flag: %s", optarg);
181: break;
1.1 martijn 182: case 'c':
183: community = optarg;
184: break;
1.9 martijn 185: case 'E':
186: ctxengineid = snmpc_hex2bin(optarg,
187: &ctxengineidlen);
188: if (ctxengineid == NULL) {
189: if (errno == EINVAL)
190: errx(1, "Bad engine ID value "
191: "after -3E flag.");
192: err(1, "-3E");
193: }
194: break;
195: case 'e':
196: secengineid = snmpc_hex2bin(optarg,
197: &secengineidlen);
198: if (secengineid == NULL) {
199: if (errno == EINVAL)
200: errx(1, "Bad engine ID value "
201: "after -3e flag.");
202: err(1, "-3e");
203: }
204: break;
1.11 martijn 205: case 'K':
206: privkey = snmpc_hex2bin(optarg, &privkeylen);
207: if (privkey == NULL) {
208: if (errno == EINVAL)
209: errx(1, "Bad key value after "
210: "-3K flag.");
211: errx(1, "-3K");
212: }
213: privkeylevel = USM_KEY_LOCALIZED;
214: break;
1.10 martijn 215: case 'k':
216: authkey = snmpc_hex2bin(optarg, &authkeylen);
217: if (authkey == NULL) {
218: if (errno == EINVAL)
219: errx(1, "Bad key value after -k flag.");
220: err(1, "-k");
221: }
222: authkeylevel = USM_KEY_LOCALIZED;
223: break;
224: case 'l':
225: if (strcasecmp(optarg, "noAuthNoPriv") == 0)
226: seclevel = SNMP_MSGFLAG_REPORT;
227: else if (strcasecmp(optarg, "authNoPriv") == 0)
228: seclevel = SNMP_MSGFLAG_AUTH |
229: SNMP_MSGFLAG_REPORT;
1.11 martijn 230: else if (strcasecmp(optarg, "authPriv") == 0)
231: seclevel = SNMP_MSGFLAG_AUTH |
232: SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
1.10 martijn 233: else
234: errx(1, "Invalid security level specified "
235: "after -l flag: %s", optarg);
236: break;
1.9 martijn 237: case 'n':
238: ctxname = optarg;
239: break;
1.1 martijn 240: case 'r':
241: if ((retries = strtonum(optarg, 0, INT_MAX,
242: &errstr)) == 0) {
243: if (errstr != NULL)
244: errx(1, "-r: %s argument", errstr);
245: }
246: break;
247: case 't':
248: if ((timeout = strtonum(optarg, 1, INT_MAX,
249: &errstr)) == 0) {
250: if (errstr != NULL)
251: errx(1, "-t: %s argument", errstr);
252: }
253: break;
1.9 martijn 254: case 'u':
255: user = optarg;
256: break;
1.1 martijn 257: case 'v':
258: if (strcmp(optarg, "1") == 0)
259: version = SNMP_V1;
260: else if (strcmp(optarg, "2c") == 0)
261: version = SNMP_V2C;
1.9 martijn 262: else if (strcmp(optarg, "3") == 0)
263: version = SNMP_V3;
1.1 martijn 264: else
265: errc(1, EINVAL, "-v");
266: break;
267: case 'C':
268: for (i = 0; i < strlen(optarg); i++) {
269: switch (optarg[i]) {
270: case 'c':
271: if (strcmp(snmp_app->name, "walk") &&
272: strcmp(snmp_app->name, "bulkwalk"))
273: usage();
274: walk_check_increase = 0;
275: break;
276: case 'i':
277: if (strcmp(snmp_app->name, "walk") &&
278: strcmp(snmp_app->name, "bulkwalk"))
279: usage();
280: walk_include_oid = 1;
281: break;
282: case 'n':
283: if (strcmp(snmp_app->name, "bulkget") &&
284: strcmp(snmp_app->name, "bulkwalk"))
285: usage();
286: errno = 0;
287: non_repeaters = strtol(&optarg[i + 1],
288: &strtolp, 10);
289: if (non_repeaters < 0 ||
290: errno == ERANGE) {
291: if (non_repeaters < 0)
292: errx(1, "%s%s",
293: "-Cn: too small ",
294: "argument");
295: else
296: errx(1, "%s%s",
297: "-Cn: too large",
298: "argument");
299: } else if (&optarg[i + 1] == strtolp)
300: errx(1, "-Cn invalid argument");
301: i = strtolp - optarg - 1;
302: break;
303: case 'p':
304: if (strcmp(snmp_app->name, "walk") &&
305: strcmp(snmp_app->name, "bulkwalk"))
306: usage();
307: print_summary = 1;
308: break;
309: case 'r':
310: if (strcmp(snmp_app->name, "bulkget") &&
311: strcmp(snmp_app->name, "bulkwalk"))
312: usage();
313: errno = 0;
314: max_repetitions = strtol(&optarg[i + 1],
315: &strtolp, 10);
316: if (max_repetitions < 0 ||
317: errno == ERANGE) {
318: if (max_repetitions < 0)
319: errx(1, "%s%s",
320: "-Cr: too small ",
321: "argument");
322: else
323: errx(1, "%s%s",
324: "-Cr: too large",
325: "argument");
326: } else if (&optarg[i + 1] == strtolp)
327: errx(1, "-Cr invalid argument");
328: i = strtolp - optarg - 1;
329: break;
330: case 't':
331: if (strcmp(snmp_app->name, "walk"))
332: usage();
333: print_time = 1;
334: break;
335: case 'E':
336: if (strcmp(snmp_app->name, "walk"))
337: usage();
338: if (smi_string2oid(argv[optind],
339: &walk_end) != 0)
340: errx(1, "%s: %s",
341: "Unknown Object Identifier",
342: argv[optind]);
343: optind++;
344: continue;
345: case 'I':
346: if (strcmp(snmp_app->name, "walk"))
347: usage();
348: walk_fallback_oid = 0;
349: break;
350: default:
351: usage();
352: }
353: if (optarg[i] == 'E')
354: break;
355: }
356: break;
357: case 'O':
358: for (i = 0; i < strlen(optarg); i++) {
359: if (strcmp(snmp_app->name, "mibtree") == 0 &&
360: optarg[i] != 'f' && optarg[i] != 'n' &&
361: optarg[i] != 'S')
362: usage();
363: switch (optarg[i]) {
364: case 'a':
365: output_string = smi_os_ascii;
366: break;
367: case 'f':
368: oid_lookup = smi_oidl_full;
369: break;
370: case 'n':
371: oid_lookup = smi_oidl_numeric;
372: break;
373: case 'q':
374: print_equals = 0;
375: smi_print_hint = 0;
376: break;
377: case 'v':
1.2 deraadt 378: print_varbind_only = 1;
1.1 martijn 379: break;
380: case 'x':
381: output_string = smi_os_hex;
382: break;
383: case 'S':
384: oid_lookup = smi_oidl_short;
385: break;
386: case 'Q':
387: smi_print_hint = 0;
388: break;
389: default:
390: usage();
391: }
392: }
393: break;
1.11 martijn 394: case 'X':
395: privkey = optarg;
396: privkeylen = strlen(privkey);
397: privkeylevel = USM_KEY_PASSWORD;
398: break;
399: case 'x':
400: if (strcasecmp(optarg, "DES") == 0)
401: cipher = EVP_des_cbc();
402: else if (strcasecmp(optarg, "AES") == 0)
403: cipher = EVP_aes_128_cfb128();
404: else
405: errx(1, "Invalid privacy protocol "
406: "specified after -3x flag: %s",
407: optarg);
408: break;
1.9 martijn 409: case 'Z':
410: boots = strtoll(optarg, &strtolp, 10);
411: if (boots < 0 || strtolp == optarg || strtolp[0] != ',')
412: usage();
413: strtolp++;
414: while (strtolp[0] == ' ' && strtolp[0] == '\t')
415: strtolp++;
416: time = strtoll(strtolp, &strtolp, 10);
417: if (boots < 0 || strtolp == optarg)
418: usage();
419: zflag = 1;
420: break;
1.1 martijn 421: default:
422: usage();
423: }
424: }
425: argc -= optind;
426: argv += optind;
427:
1.9 martijn 428: if (version == SNMP_V3) {
429: /* Setup USM */
430: if (user == NULL || user[0] == '\0')
431: errx(1, "No securityName specified");
432: if ((sec = usm_init(user, strlen(user))) == NULL)
433: err(1, "usm_init");
1.10 martijn 434: if (seclevel & SNMP_MSGFLAG_AUTH) {
435: if (md == NULL)
436: md = EVP_md5();
437: if (authkey == NULL)
438: errx(1, "No authKey or authPassword specified");
439: if (usm_setauth(sec, md, authkey, authkeylen,
440: authkeylevel) == -1)
441: err(1, "Can't set authkey");
442: }
1.11 martijn 443: if (seclevel & SNMP_MSGFLAG_PRIV) {
444: if (cipher == NULL)
445: cipher = EVP_des_cbc();
446: if (privkey == NULL)
447: errx(1, "No privKey or privPassword specified");
448: if (usm_setpriv(sec, cipher, privkey, privkeylen,
449: privkeylevel) == -1)
450: err(1, "Can't set authkey");
451: }
1.9 martijn 452: if (secengineid != NULL) {
453: if (usm_setengineid(sec, secengineid,
454: secengineidlen) == -1)
455: err(1, "Can't set secengineid");
456: }
457: if (zflag)
458: if (usm_setbootstime(sec, boots, time) == -1)
459: err(1, "Can't set boots/time");
460: v3 = snmp_v3_init(seclevel, ctxname, ctxname == NULL ? 0 :
461: strlen(ctxname), sec);
462: if (v3 == NULL)
463: err(1, "snmp_v3_init");
464: if (ctxengineid != NULL) {
465: if (snmp_v3_setengineid(v3, ctxengineid,
466: ctxengineidlen) == -1)
467: err(1, "Can't set ctxengineid");
468: }
469: }
470:
471:
1.1 martijn 472: return snmp_app->exec(argc, argv);
473: }
474:
475: int
476: snmpc_get(int argc, char *argv[])
477: {
478: struct ber_oid *oid;
479: struct ber_element *pdu, *varbind;
480: struct snmp_agent *agent;
481: int errorstatus, errorindex;
482: int i;
1.9 martijn 483: int class;
484: unsigned type;
1.15 ! martijn 485: char *hint = NULL;
1.1 martijn 486:
487: if (argc < 2)
488: usage();
489:
1.8 martijn 490: if ((agent = snmpc_connect(argv[0], "161")) == NULL)
1.1 martijn 491: err(1, "%s", snmp_app->name);
492: agent->timeout = timeout;
493: agent->retries = retries;
494:
495: if (pledge("stdio", NULL) == -1)
496: err(1, "pledge");
497: argc--;
498: argv++;
499:
500: oid = reallocarray(NULL, argc, sizeof(*oid));
1.3 deraadt 501: if (oid == NULL)
502: err(1, "malloc");
1.1 martijn 503: for (i = 0; i < argc; i++) {
504: if (smi_string2oid(argv[i], &oid[i]) == -1)
1.12 semarie 505: errx(1, "%s: Unknown object identifier", argv[i]);
1.1 martijn 506: }
507: if (strcmp(snmp_app->name, "getnext") == 0) {
508: if ((pdu = snmp_getnext(agent, oid, argc)) == NULL)
509: err(1, "getnext");
510: } else if (strcmp(snmp_app->name, "bulkget") == 0) {
511: if (version < SNMP_V2C)
512: errx(1, "Cannot send V2 PDU on V1 session");
513: if (non_repeaters > argc)
514: errx(1, "need more objects than -Cn<num>");
515: if ((pdu = snmp_getbulk(agent, oid, argc, non_repeaters,
516: max_repetitions)) == NULL)
517: err(1, "bulkget");
518: } else {
519: if ((pdu = snmp_get(agent, oid, argc)) == NULL)
520: err(1, "get");
521: }
522:
1.9 martijn 523: (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, &errorstatus,
524: &errorindex, &varbind);
1.15 ! martijn 525: if (errorstatus != 0) {
! 526: if (errorindex >= 1 && errorindex <= argc)
! 527: hint = argv[errorindex - 1];
! 528: snmpc_printerror((enum snmp_error) errorstatus, varbind,
! 529: errorindex, hint);
! 530: }
1.1 martijn 531:
1.9 martijn 532: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
533: printf("Received report:\n");
1.1 martijn 534: for (; varbind != NULL; varbind = varbind->be_next) {
535: if (!snmpc_print(varbind))
536: err(1, "Can't print response");
537: }
538: ber_free_elements(pdu);
539: snmp_free_agent(agent);
540: return 0;
541: }
542:
543: int
544: snmpc_walk(int argc, char *argv[])
545: {
546: struct ber_oid oid, loid, noid;
547: struct ber_element *pdu, *varbind, *value;
548: struct timespec start, finish;
549: struct snmp_agent *agent;
550: const char *oids;
551: int n = 0, prev_cmp;
552: int errorstatus, errorindex;
1.9 martijn 553: int class;
554: unsigned type;
1.1 martijn 555:
556: if (strcmp(snmp_app->name, "bulkwalk") == 0 && version < SNMP_V2C)
557: errx(1, "Cannot send V2 PDU on V1 session");
558: if (argc < 1 || argc > 2)
559: usage();
560: oids = argc == 1 ? mib : argv[1];
561:
1.8 martijn 562: if ((agent = snmpc_connect(argv[0], "161"))== NULL)
1.1 martijn 563: err(1, "%s", snmp_app->name);
564: agent->timeout = timeout;
565: agent->retries = retries;
566: if (pledge("stdio", NULL) == -1)
567: err(1, "pledge");
568:
569: if (smi_string2oid(oids, &oid) == -1)
570: errx(1, "%s: Unknown object identifier", oids);
571: bcopy(&oid, &noid, sizeof(noid));
572: if (print_time)
573: clock_gettime(CLOCK_MONOTONIC, &start);
574:
575: if (walk_include_oid) {
576: if ((pdu = snmp_get(agent, &oid, 1)) == NULL)
577: err(1, "%s", snmp_app->name);
578:
1.9 martijn 579: (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type,
580: &errorstatus, &errorindex, &varbind);
1.1 martijn 581: if (errorstatus != 0)
1.15 ! martijn 582: snmpc_printerror((enum snmp_error) errorstatus, varbind,
! 583: errorindex, oids);
1.1 martijn 584:
1.9 martijn 585: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
586: printf("Received report:\n");
1.1 martijn 587: if (!snmpc_print(varbind))
588: err(1, "Can't print response");
589: ber_free_element(pdu);
1.9 martijn 590: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
591: return 1;
1.1 martijn 592: n++;
593: }
594: while (1) {
595: bcopy(&noid, &loid, sizeof(loid));
596: if (strcmp(snmp_app->name, "bulkwalk") == 0) {
597: if ((pdu = snmp_getbulk(agent, &noid, 1,
598: non_repeaters, max_repetitions)) == NULL)
599: err(1, "bulkwalk");
600: } else {
601: if ((pdu = snmp_getnext(agent, &noid, 1)) == NULL)
602: err(1, "walk");
603: }
604:
1.9 martijn 605: (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type,
606: &errorstatus, &errorindex, &varbind);
1.1 martijn 607: if (errorstatus != 0) {
1.15 ! martijn 608: snmpc_printerror((enum snmp_error) errorstatus, varbind,
! 609: errorindex, NULL);
1.1 martijn 610: }
611:
1.9 martijn 612: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
613: printf("Received report:\n");
1.2 deraadt 614: for (; varbind != NULL; varbind = varbind->be_next) {
1.1 martijn 615: (void) ber_scanf_elements(varbind, "{oe}", &noid,
616: &value);
617: if (value->be_class == BER_CLASS_CONTEXT &&
618: value->be_type == BER_TYPE_EOC)
619: break;
620: prev_cmp = ber_oid_cmp(&loid, &noid);
621: if (walk_check_increase && prev_cmp == -1)
622: errx(1, "OID not increasing");
623: if (prev_cmp == 0 || ber_oid_cmp(&oid, &noid) != 2)
624: break;
625: if (walk_end.bo_n != 0 &&
626: ber_oid_cmp(&walk_end, &noid) != -1)
627: break;
628:
629: if (!snmpc_print(varbind))
630: err(1, "Can't print response");
631: n++;
632: }
633: ber_free_elements(pdu);
1.9 martijn 634: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
635: return 1;
1.1 martijn 636: if (varbind != NULL)
637: break;
638: }
639: if (walk_fallback_oid && n == 0) {
640: if ((pdu = snmp_get(agent, &oid, 1)) == NULL)
641: err(1, "%s", snmp_app->name);
642:
1.9 martijn 643: (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type,
644: &errorstatus, &errorindex, &varbind);
1.1 martijn 645: if (errorstatus != 0)
1.15 ! martijn 646: snmpc_printerror((enum snmp_error) errorstatus, varbind,
! 647: errorindex, oids);
1.1 martijn 648:
1.9 martijn 649: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
650: printf("Received report:\n");
1.1 martijn 651: if (!snmpc_print(varbind))
652: err(1, "Can't print response");
653: ber_free_element(pdu);
1.9 martijn 654: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
655: return 1;
1.1 martijn 656: n++;
657: }
658: if (print_time)
659: clock_gettime(CLOCK_MONOTONIC, &finish);
660: if (print_summary)
661: printf("Variables found: %d\n", n);
662: if (print_time) {
663: if ((finish.tv_nsec -= start.tv_nsec) < 0) {
664: finish.tv_sec -= 1;
665: finish.tv_nsec += 1000000000;
666: }
667: finish.tv_sec -= start.tv_sec;
668: fprintf(stderr, "Total traversal time: %lld.%09ld seconds\n",
669: finish.tv_sec, finish.tv_nsec);
670: }
671: snmp_free_agent(agent);
672: return 0;
673: }
674:
675: int
1.13 martijn 676: snmpc_set(int argc, char *argv[])
677: {
678: struct snmp_agent *agent;
679: struct ber_element *pdu, *varbind;
680: int errorstatus, errorindex;
681: int class;
682: unsigned type;
1.15 ! martijn 683: char *hint = NULL;
1.13 martijn 684:
685: if (argc < 4)
686: usage();
687: if ((agent = snmpc_connect(argv[0], "161")) == NULL)
688: err(1, "%s", snmp_app->name);
689: argc--;
690: argv++;
691:
692: if (pledge("stdio", NULL) == -1)
693: err(1, "pledge");
694:
1.15 ! martijn 695: if ((pdu = snmp_set(agent, snmpc_varbindparse(argc, argv))) == NULL)
! 696: err(1, "set");
1.13 martijn 697:
698: (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, &errorstatus,
699: &errorindex, &varbind);
1.15 ! martijn 700: if (errorstatus != 0) {
! 701: if (errorindex >= 1 && errorindex <= argc / 3)
! 702: hint = argv[(errorindex - 1) * 3];
! 703: snmpc_printerror((enum snmp_error) errorstatus, varbind,
! 704: errorindex, hint);
! 705: }
1.13 martijn 706:
707: if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT)
708: printf("Received report:\n");
709: for (; varbind != NULL; varbind = varbind->be_next) {
710: if (!snmpc_print(varbind))
711: err(1, "Can't print response");
712: }
713: ber_free_elements(pdu);
714: snmp_free_agent(agent);
715: return 0;
716: }
717:
718: int
1.1 martijn 719: snmpc_trap(int argc, char *argv[])
720: {
721: struct snmp_agent *agent;
722: struct timespec ts;
1.13 martijn 723: struct ber_oid trapoid;
1.1 martijn 724: const char *errstr = NULL;
725: long long lval;
726:
727: if (version == SNMP_V1)
728: errx(1, "trap is not supported for snmp v1");
729:
1.8 martijn 730: if ((agent = snmpc_connect(argv[0], "162")) == NULL)
1.1 martijn 731: err(1, "%s", snmp_app->name);
732:
733: if (pledge("stdio", NULL) == -1)
734: err(1, "pledge");
735:
736: if (argv[1][0] == '\0') {
737: if (clock_gettime(CLOCK_UPTIME, &ts) == -1)
738: err(1, "clock_gettime");
739: } else {
740: lval = strtonum(argv[1], 0, LLONG_MAX, &errstr);
741: if (errstr != NULL)
742: errx(1, "Bad value notation (%s)", argv[1]);
743: ts.tv_sec = lval / 100;
744: ts.tv_nsec = (lval % 100) * 10000000;
745: }
746: if (smi_string2oid(argv[2], &trapoid) == -1)
747: errx(1, "Invalid oid: %s\n", argv[2]);
748:
749: argc -= 3;
750: argv += 3;
751:
1.13 martijn 752: snmp_trap(agent, &ts, &trapoid, snmpc_varbindparse(argc, argv));
1.1 martijn 753:
754: return 0;
755: }
756:
757: int
758: snmpc_mibtree(int argc, char *argv[])
759: {
760: struct oid *oid;
761: char buf[BUFSIZ];
762:
763: for (oid = NULL; (oid = smi_foreach(oid, 0)) != NULL;) {
764: smi_oid2string(&oid->o_id, buf, sizeof(buf), oid_lookup);
765: printf("%s\n", buf);
766: }
767: return 0;
1.8 martijn 768: }
769:
770: struct snmp_agent *
771: snmpc_connect(char *host, char *port)
772: {
773: switch (version) {
774: case SNMP_V1:
775: case SNMP_V2C:
776: return snmp_connect_v12(snmpc_parseagent(host, port), version,
777: community);
1.9 martijn 778: case SNMP_V3:
779: return snmp_connect_v3(snmpc_parseagent(host, port), v3);
1.8 martijn 780: }
781: return NULL;
1.1 martijn 782: }
783:
784: int
785: snmpc_print(struct ber_element *elm)
786: {
787: struct ber_oid oid;
788: char oids[SNMP_MAX_OID_STRLEN];
789: char *value;
790:
791: elm = elm->be_sub;
792: if (ber_get_oid(elm, &oid) != 0) {
793: errno = EINVAL;
794: return 0;
795: }
796:
797: elm = elm->be_next;
798: value = smi_print_element(elm, smi_print_hint, output_string, oid_lookup);
799: if (value == NULL)
800: return 0;
801:
802: if (print_varbind_only)
803: printf("%s\n", value);
804: else if (print_equals) {
805: smi_oid2string(&oid, oids, sizeof(oids), oid_lookup);
806: printf("%s = %s\n", oids, value);
807: } else {
808: smi_oid2string(&oid, oids, sizeof(oids), oid_lookup);
809: printf("%s %s\n", oids, value);
810: }
811: free(value);
1.2 deraadt 812:
1.1 martijn 813: return 1;
814: }
815:
816: __dead void
1.15 ! martijn 817: snmpc_printerror(enum snmp_error error, struct ber_element *varbind,
! 818: int index, const char *hint)
1.1 martijn 819: {
1.15 ! martijn 820: struct ber_oid hoid, vboid;
! 821: char oids[SNMP_MAX_OID_STRLEN];
! 822: const char *oid = NULL;
! 823: int i;
! 824:
! 825: if (index >= 1) {
! 826: /* Only print if the index is in the reply */
! 827: for (i = 1; varbind != NULL && i <= index;
! 828: varbind = varbind->be_next)
! 829: i++;
! 830: if (varbind != NULL &&
! 831: ber_get_oid(varbind->be_sub, &vboid) == 0) {
! 832: /* If user and reply conform print user input */
! 833: if (hint != NULL &&
! 834: smi_string2oid(hint, &hoid) == 0 &&
! 835: ber_oid_cmp(&hoid, &vboid) == 0)
! 836: oid = hint;
! 837: else
! 838: oid = smi_oid2string(&vboid, oids,
! 839: sizeof(oids), oid_lookup);
! 840: }
! 841: }
! 842: if (oid == NULL)
! 843: oid = "?";
! 844:
1.2 deraadt 845: switch (error) {
1.1 martijn 846: case SNMP_ERROR_NONE:
847: errx(1, "No error, how did I get here?");
848: case SNMP_ERROR_TOOBIG:
849: errx(1, "Can't parse oid %s: Response too big", oid);
850: case SNMP_ERROR_NOSUCHNAME:
851: errx(1, "Can't parse oid %s: No such object", oid);
852: case SNMP_ERROR_BADVALUE:
853: errx(1, "Can't parse oid %s: Bad value", oid);
854: case SNMP_ERROR_READONLY:
855: errx(1, "Can't parse oid %s: Read only", oid);
856: case SNMP_ERROR_GENERR:
857: errx(1, "Can't parse oid %s: Generic error", oid);
858: case SNMP_ERROR_NOACCESS:
859: errx(1, "Can't parse oid %s: Access denied", oid);
860: case SNMP_ERROR_WRONGTYPE:
861: errx(1, "Can't parse oid %s: Wrong type", oid);
862: case SNMP_ERROR_WRONGLENGTH:
863: errx(1, "Can't parse oid %s: Wrong length", oid);
864: case SNMP_ERROR_WRONGENC:
865: errx(1, "Can't parse oid %s: Wrong encoding", oid);
866: case SNMP_ERROR_WRONGVALUE:
867: errx(1, "Can't parse oid %s: Wrong value", oid);
868: case SNMP_ERROR_NOCREATION:
869: errx(1, "Can't parse oid %s: Can't be created", oid);
870: case SNMP_ERROR_INCONVALUE:
871: errx(1, "Can't parse oid %s: Inconsistent value", oid);
872: case SNMP_ERROR_RESUNAVAIL:
873: errx(1, "Can't parse oid %s: Resource unavailable", oid);
874: case SNMP_ERROR_COMMITFAILED:
875: errx(1, "Can't parse oid %s: Commit failed", oid);
876: case SNMP_ERROR_UNDOFAILED:
877: errx(1, "Can't parse oid %s: Undo faild", oid);
878: case SNMP_ERROR_AUTHERROR:
879: errx(1, "Can't parse oid %s: Authorization error", oid);
880: case SNMP_ERROR_NOTWRITABLE:
881: errx(1, "Can't parse oid %s: Not writable", oid);
882: case SNMP_ERROR_INCONNAME:
883: errx(1, "Can't parse oid %s: Inconsistent name", oid);
884: }
885: errx(1, "Can't parse oid %s: Unknown error (%d)", oid, error);
886: }
887:
888: int
889: snmpc_parseagent(char *agent, char *defaultport)
890: {
891: struct addrinfo hints, *ai, *ai0 = NULL;
892: struct sockaddr_un saddr;
893: char *agentdup, *specifier, *hostname, *port = NULL;
894: int error;
895: int s;
896:
897: if ((agentdup = specifier = strdup(agent)) == NULL)
898: err(1, NULL);
899:
900: bzero(&hints, sizeof(hints));
901: if ((hostname = strchr(specifier, ':')) != NULL) {
902: *hostname++ = '\0';
903: if (strcasecmp(specifier, "udp") == 0) {
904: hints.ai_family = AF_INET;
905: hints.ai_socktype = SOCK_DGRAM;
906: } else if (strcasecmp(specifier, "tcp") == 0) {
907: hints.ai_family = AF_INET;
908: hints.ai_socktype = SOCK_STREAM;
909: } else if (strcasecmp(specifier, "udp6") == 0 ||
910: strcasecmp(specifier, "udpv6") == 0 ||
911: strcasecmp(specifier, "udpipv6") == 0) {
912: hints.ai_family = AF_INET6;
913: hints.ai_socktype = SOCK_DGRAM;
914: } else if (strcasecmp(specifier, "tcp6") == 0 ||
915: strcasecmp(specifier, "tcpv6") == 0 ||
916: strcasecmp(specifier, "tcpipv6") == 0) {
917: hints.ai_family = AF_INET6;
918: hints.ai_socktype = SOCK_STREAM;
919: } else if (strcasecmp(specifier, "unix") == 0) {
920: hints.ai_family = AF_UNIX;
921: hints.ai_socktype = SOCK_STREAM;
922: hints.ai_addr = (struct sockaddr *)&saddr;
923: hints.ai_addrlen = sizeof(saddr);
924: saddr.sun_len = sizeof(saddr);
925: saddr.sun_family = AF_UNIX;
926: if (strlcpy(saddr.sun_path, hostname,
927: sizeof(saddr.sun_path)) > sizeof(saddr.sun_path))
928: errx(1, "Hostname path too long");
929: ai = &hints;
930: } else {
931: port = hostname;
932: hostname = specifier;
933: specifier = NULL;
934: hints.ai_family = AF_INET;
935: hints.ai_socktype = SOCK_DGRAM;
936: }
937: if (port == NULL) {
938: if (hints.ai_family == AF_INET) {
939: if ((port = strchr(hostname, ':')) != NULL)
940: *port++ = '\0';
941: } else if (hints.ai_family == AF_INET6) {
942: if (hostname[0] == '[') {
943: hostname++;
944: if ((port = strchr(hostname, ']')) == NULL)
945: errx(1, "invalid agent");
946: *port++ = '\0';
947: if (port[0] == ':')
948: *port++ = '\0';
949: else
950: port = NULL;
951: } else {
952: if ((port = strrchr(hostname, ':')) == NULL)
953: errx(1, "invalid agent");
954: *port++ = '\0';
955: }
956: }
957: }
958: } else {
959: hostname = specifier;
960: hints.ai_family = AF_INET;
961: hints.ai_socktype = SOCK_DGRAM;
962: }
963:
964: if (hints.ai_family != AF_UNIX) {
965: if (port == NULL)
966: port = defaultport;
967: error = getaddrinfo(hostname, port, &hints, &ai0);
968: if (error)
969: errx(1, "%s", gai_strerror(error));
970: s = -1;
971: for (ai = ai0; ai != NULL; ai = ai->ai_next) {
972: if ((s = socket(ai->ai_family, ai->ai_socktype,
973: ai->ai_protocol)) == -1)
974: continue;
975: break;
976: }
977: } else
978: s = socket(hints.ai_family, hints.ai_socktype,
979: hints.ai_protocol);
980: if (s == -1)
981: err(1, "socket");
982:
983: if (connect(s, (struct sockaddr *)ai->ai_addr, ai->ai_addrlen) == -1)
984: err(1, "Can't connect to %s", agent);
985:
986: if (ai0 != NULL)
987: freeaddrinfo(ai0);
988: free(agentdup);
989: return s;
990: }
991:
1.9 martijn 992: char *
993: snmpc_hex2bin(char *hexstr, size_t *binlen)
994: {
995: char *decstr;
996:
997: if (hexstr[0] == '0' && hexstr[1] == 'x')
998: hexstr += 2;
999: while (hexstr[0] == ' ' || hexstr[0] == '\t')
1000: hexstr++;
1001:
1002: if ((decstr = malloc((strlen(hexstr) / 2) + 1)) == NULL)
1003: return NULL;
1004:
1005: for (*binlen = 0; hexstr[0] != '\0'; (*binlen)++) {
1006: hexstr[0] = toupper(hexstr[0]);
1007: hexstr[1] = toupper(hexstr[1]);
1008: if (hexstr[0] >= '0' && hexstr[0] <= '9')
1009: decstr[*binlen] = (hexstr[0] - '0') << 4;
1010: else if (hexstr[0] >= 'A' && hexstr[0] <= 'F')
1011: decstr[*binlen] = ((hexstr[0] - 'A') + 10) << 4;
1012: else
1013: goto fail;
1014: if (hexstr[1] >= '0' && hexstr[1] <= '9')
1015: decstr[*binlen] |= (hexstr[1] - '0');
1016: else if (hexstr[1] >= 'A' && hexstr[1] <= 'F')
1017: decstr[*binlen] |= (hexstr[1] - 'A') + 10;
1018: else
1019: goto fail;
1020:
1021: hexstr += 2;
1022: while (hexstr[0] == ' ' || hexstr[0] == '\t')
1023: hexstr++;
1024: }
1025:
1026: return decstr;
1027: fail:
1028: errno = EINVAL;
1029: free(decstr);
1030: return NULL;
1.13 martijn 1031: }
1032:
1033: struct ber_element *
1034: snmpc_varbindparse(int argc, char *argv[])
1035: {
1036: struct ber_oid oid, oidval;
1037: struct in_addr addr4;
1038: char *addr = (char *)&addr4;
1039: char *str = NULL, *tmpstr, *endstr;
1040: const char *errstr = NULL;
1041: struct ber_element *varbind = NULL, *vblist = NULL;
1042: int i, ret;
1043: size_t strl, byte;
1044: long long lval;
1045:
1046: if (argc % 3 != 0)
1047: usage();
1048: for (i = 0; i < argc; i += 3) {
1049: if (smi_string2oid(argv[i], &oid) == -1)
1050: errx(1, "Invalid oid: %s\n", argv[i]);
1051: switch (argv[i + 1][0]) {
1052: case 'a':
1053: ret = inet_pton(AF_INET, argv[i + 2], &addr4);
1054: if (ret == -1)
1055: err(1, "inet_pton");
1056: if (ret == 0)
1057: errx(1, "%s: Bad value notation (%s)", argv[i],
1058: argv[i + 2]);
1059: if ((varbind = ber_printf_elements(varbind, "{Oxt}",
1060: &oid, addr, sizeof(addr4), BER_CLASS_APPLICATION,
1061: SNMP_T_IPADDR)) == NULL)
1062: err(1, "ber_printf_elements");
1063: break;
1064: case 'b':
1065: tmpstr = argv[i + 2];
1066: strl = 0;
1067: do {
1068: lval = strtoll(tmpstr, &endstr, 10);
1069: if (endstr[0] != ' ' && endstr[0] != '\t' &&
1070: endstr[0] != ',' && endstr[0] != '\0')
1071: errx(1, "%s: Bad value notation (%s)",
1072: argv[i], argv[i + 2]);
1073: if (tmpstr == endstr) {
1074: tmpstr++;
1075: continue;
1076: }
1077: if (lval < 0)
1078: errx(1, "%s: Bad value notation (%s)",
1079: argv[i], argv[i + 2]);
1080: byte = lval / 8;
1081: if (byte >= strl) {
1082: if ((str = recallocarray(str, strl,
1083: byte + 1, 1)) == NULL)
1084: err(1, "malloc");
1085: strl = byte + 1;
1086: }
1087: str[byte] |= 0x80 >> (lval % 8);
1088: tmpstr = endstr + 1;
1089: } while (endstr[0] != '\0');
1090: /*
1091: * RFC3416 Section 2.5
1092: * A BITS value is encoded as an OCTET STRING
1093: */
1094: goto pastestring;
1095: case 'c':
1096: lval = strtonum(argv[i + 2], INT32_MIN, INT32_MAX,
1097: &errstr);
1098: if (errstr != NULL)
1099: errx(1, "%s: Bad value notation (%s)", argv[i],
1100: argv[i + 2]);
1101: if ((varbind = ber_printf_elements(varbind, "{Oit}",
1102: &oid, lval, BER_CLASS_APPLICATION,
1103: SNMP_T_COUNTER32)) == NULL)
1104: err(1, "ber_printf_elements");
1105: break;
1106: case 'd':
1107: /* String always shrinks */
1108: if ((str = malloc(strlen(argv[i + 2]))) == NULL)
1109: err(1, "malloc");
1110: tmpstr = argv[i + 2];
1111: strl = 0;
1112: do {
1113: lval = strtoll(tmpstr, &endstr, 10);
1114: if (endstr[0] != ' ' && endstr[0] != '\t' &&
1115: endstr[0] != '\0')
1116: errx(1, "%s: Bad value notation (%s)",
1117: argv[i], argv[i + 2]);
1118: if (tmpstr == endstr) {
1119: tmpstr++;
1120: continue;
1121: }
1122: if (lval < 0 || lval > 0xff)
1123: errx(1, "%s: Bad value notation (%s)",
1124: argv[i], argv[i + 2]);
1125: str[strl++] = (unsigned char) lval;
1126: tmpstr = endstr + 1;
1127: } while (endstr[0] != '\0');
1128: goto pastestring;
1129: case 'u':
1130: case 'i':
1131: lval = strtonum(argv[i + 2], LLONG_MIN, LLONG_MAX,
1132: &errstr);
1133: if (errstr != NULL)
1134: errx(1, "%s: Bad value notation (%s)", argv[i],
1135: argv[i + 2]);
1136: if ((varbind = ber_printf_elements(varbind, "{Oi}",
1137: &oid, lval)) == NULL)
1138: err(1, "ber_printf_elements");
1139: break;
1140: case 'n':
1141: if ((varbind = ber_printf_elements(varbind, "{O0}",
1142: &oid)) == NULL)
1143: err(1, "ber_printf_elements");
1144: break;
1145: case 'o':
1146: if (smi_string2oid(argv[i + 2], &oidval) == -1)
1147: errx(1, "%s: Unknown Object Identifier (Sub-id "
1148: "not found: (top) -> %s)", argv[i],
1149: argv[i + 2]);
1150: if ((varbind = ber_printf_elements(varbind, "{OO}",
1151: &oid, &oidval)) == NULL)
1152: err(1, "ber_printf_elements");
1153: break;
1154: case 's':
1155: if ((str = strdup(argv[i + 2])) == NULL)
1156: err(1, NULL);
1157: strl = strlen(argv[i + 2]);
1158: pastestring:
1159: if ((varbind = ber_printf_elements(varbind, "{Ox}",
1160: &oid, str, strl)) == NULL)
1161: err(1, "ber_printf_elements");
1162: free(str);
1163: break;
1164: case 't':
1165: lval = strtonum(argv[i + 2], LLONG_MIN, LLONG_MAX,
1166: &errstr);
1167: if (errstr != NULL)
1168: errx(1, "%s: Bad value notation (%s)", argv[i],
1169: argv[i + 2]);
1170: if ((varbind = ber_printf_elements(varbind, "{Oit}",
1171: &oid, lval, BER_CLASS_APPLICATION,
1172: SNMP_T_TIMETICKS)) == NULL)
1173: err(1, "ber_printf_elements");
1174: break;
1175: case 'x':
1176: /* String always shrinks */
1177: if ((str = malloc(strlen(argv[i + 2]))) == NULL)
1178: err(1, "malloc");
1179: tmpstr = argv[i + 2];
1180: strl = 0;
1181: do {
1182: lval = strtoll(tmpstr, &endstr, 16);
1183: if (endstr[0] != ' ' && endstr[0] != '\t' &&
1184: endstr[0] != '\0')
1185: errx(1, "%s: Bad value notation (%s)",
1186: argv[i], argv[i + 2]);
1187: if (tmpstr == endstr) {
1188: tmpstr++;
1189: continue;
1190: }
1191: if (lval < 0 || lval > 0xff)
1192: errx(1, "%s: Bad value notation (%s)",
1193: argv[i], argv[i + 2]);
1194: str[strl++] = (unsigned char) lval;
1195: tmpstr = endstr + 1;
1196: } while (endstr[0] != '\0');
1197: goto pastestring;
1198: default:
1199: usage();
1200: }
1201: if (vblist == NULL)
1202: vblist = varbind;
1203: }
1204:
1205: return vblist;
1.9 martijn 1206: }
1207:
1.1 martijn 1208: __dead void
1209: usage(void)
1210: {
1211: size_t i;
1212:
1213: if (snmp_app != NULL) {
1.9 martijn 1214: fprintf(stderr, "usage: snmp %s%s%s\n",
1.4 martijn 1215: snmp_app->name,
1.1 martijn 1216: snmp_app->usecommonopt ?
1.10 martijn 1217: " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
1.11 martijn 1218: " [-E ctxengineid] [-K localpriv] [-k localauth] [-l seclevel]\n"
1219: " [-n ctxname] [-O afnqvxSQ] [-r retries] [-t timeout] [-u user]\n"
1220: " [-v version] [-X privpass] [-x cipher] [-Z boots,time]\n"
1.9 martijn 1221: " " : "",
1.1 martijn 1222: snmp_app->usage == NULL ? "" : snmp_app->usage);
1223: exit(1);
1224: }
1225: for (i = 0; i < (sizeof(snmp_apps)/sizeof(*snmp_apps)); i++) {
1.4 martijn 1226: if (i == 0)
1227: fprintf(stderr, "usage: ");
1228: else
1229: fprintf(stderr, " ");
1230: fprintf(stderr, "snmp %s%s %s\n",
1231: snmp_apps[i].name,
1.1 martijn 1232: snmp_apps[i].usecommonopt ?
1.9 martijn 1233: " [common options]" : "",
1.4 martijn 1234: snmp_apps[i].usage ? snmp_apps[i].usage : "");
1.1 martijn 1235: }
1236: exit(1);
1237: }