xref: /openbsd-src/usr.bin/snmp/smi.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /*	$OpenBSD: smi.c,v 1.7 2020/01/17 09:52:44 martijn Exp $	*/
2 
3 /*
4  * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
5  * Copyright (c) 2007, 2008 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/tree.h>
22 #include <sys/queue.h>
23 
24 #include <arpa/inet.h>
25 
26 #include <ctype.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <strings.h>
31 
32 #include "ber.h"
33 #include "mib.h"
34 #include "snmp.h"
35 #include "smi.h"
36 
37 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
38 
39 int smi_oid_cmp(struct oid *, struct oid *);
40 int smi_key_cmp(struct oid *, struct oid *);
41 struct oid * smi_findkey(char *);
42 
43 RB_HEAD(oidtree, oid);
44 RB_PROTOTYPE(oidtree, oid, o_element, smi_oid_cmp)
45 struct oidtree smi_oidtree;
46 
47 RB_HEAD(keytree, oid);
48 RB_PROTOTYPE(keytree, oid, o_keyword, smi_key_cmp)
49 struct keytree smi_keytree;
50 
51 int
52 smi_init(void)
53 {
54 	/* Initialize the Structure of Managed Information (SMI) */
55 	RB_INIT(&smi_oidtree);
56 	mib_init();
57 	return (0);
58 }
59 
60 void
61 smi_debug_elements(struct ber_element *root)
62 {
63 	static int	 indent = 0;
64 	char		*value;
65 	int		 constructed;
66 
67 	/* calculate lengths */
68 	ober_calc_len(root);
69 
70 	switch (root->be_encoding) {
71 	case BER_TYPE_SEQUENCE:
72 	case BER_TYPE_SET:
73 		constructed = root->be_encoding;
74 		break;
75 	default:
76 		constructed = 0;
77 		break;
78 	}
79 
80 	fprintf(stderr, "%*slen %lu ", indent, "", root->be_len);
81 	switch (root->be_class) {
82 	case BER_CLASS_UNIVERSAL:
83 		fprintf(stderr, "class: universal(%u) type: ", root->be_class);
84 		switch (root->be_type) {
85 		case BER_TYPE_EOC:
86 			fprintf(stderr, "end-of-content");
87 			break;
88 		case BER_TYPE_BOOLEAN:
89 			fprintf(stderr, "boolean");
90 			break;
91 		case BER_TYPE_INTEGER:
92 			fprintf(stderr, "integer");
93 			break;
94 		case BER_TYPE_BITSTRING:
95 			fprintf(stderr, "bit-string");
96 			break;
97 		case BER_TYPE_OCTETSTRING:
98 			fprintf(stderr, "octet-string");
99 			break;
100 		case BER_TYPE_NULL:
101 			fprintf(stderr, "null");
102 			break;
103 		case BER_TYPE_OBJECT:
104 			fprintf(stderr, "object");
105 			break;
106 		case BER_TYPE_ENUMERATED:
107 			fprintf(stderr, "enumerated");
108 			break;
109 		case BER_TYPE_SEQUENCE:
110 			fprintf(stderr, "sequence");
111 			break;
112 		case BER_TYPE_SET:
113 			fprintf(stderr, "set");
114 			break;
115 		}
116 		break;
117 	case BER_CLASS_APPLICATION:
118 		fprintf(stderr, "class: application(%u) type: ",
119 		    root->be_class);
120 		switch (root->be_type) {
121 		case SNMP_T_IPADDR:
122 			fprintf(stderr, "ipaddr");
123 			break;
124 		case SNMP_T_COUNTER32:
125 			fprintf(stderr, "counter32");
126 			break;
127 		case SNMP_T_GAUGE32:
128 			fprintf(stderr, "gauge32");
129 			break;
130 		case SNMP_T_TIMETICKS:
131 			fprintf(stderr, "timeticks");
132 			break;
133 		case SNMP_T_OPAQUE:
134 			fprintf(stderr, "opaque");
135 			break;
136 		case SNMP_T_COUNTER64:
137 			fprintf(stderr, "counter64");
138 			break;
139 		}
140 		break;
141 	case BER_CLASS_CONTEXT:
142 		fprintf(stderr, "class: context(%u) type: ",
143 		    root->be_class);
144 		switch (root->be_type) {
145 		case SNMP_C_GETREQ:
146 			fprintf(stderr, "getreq");
147 			break;
148 		case SNMP_C_GETNEXTREQ:
149 			fprintf(stderr, "nextreq");
150 			break;
151 		case SNMP_C_GETRESP:
152 			fprintf(stderr, "getresp");
153 			break;
154 		case SNMP_C_SETREQ:
155 			fprintf(stderr, "setreq");
156 			break;
157 		case SNMP_C_TRAP:
158 			fprintf(stderr, "trap");
159 			break;
160 		case SNMP_C_GETBULKREQ:
161 			fprintf(stderr, "getbulkreq");
162 			break;
163 		case SNMP_C_INFORMREQ:
164 			fprintf(stderr, "informreq");
165 			break;
166 		case SNMP_C_TRAPV2:
167 			fprintf(stderr, "trapv2");
168 			break;
169 		case SNMP_C_REPORT:
170 			fprintf(stderr, "report");
171 			break;
172 		}
173 		break;
174 	case BER_CLASS_PRIVATE:
175 		fprintf(stderr, "class: private(%u) type: ", root->be_class);
176 		break;
177 	default:
178 		fprintf(stderr, "class: <INVALID>(%u) type: ", root->be_class);
179 		break;
180 	}
181 	fprintf(stderr, "(%u) encoding %u ",
182 	    root->be_type, root->be_encoding);
183 
184 	if ((value = smi_print_element(root, 1, smi_os_default,
185 	    smi_oidl_numeric)) == NULL)
186 		goto invalid;
187 
188 	switch (root->be_encoding) {
189 	case BER_TYPE_BOOLEAN:
190 		fprintf(stderr, "%s", value);
191 		break;
192 	case BER_TYPE_INTEGER:
193 	case BER_TYPE_ENUMERATED:
194 		fprintf(stderr, "value %s", value);
195 		break;
196 	case BER_TYPE_BITSTRING:
197 		fprintf(stderr, "hexdump %s", value);
198 		break;
199 	case BER_TYPE_OBJECT:
200 		fprintf(stderr, "oid %s", value);
201 		break;
202 	case BER_TYPE_OCTETSTRING:
203 		if (root->be_class == BER_CLASS_APPLICATION &&
204 		    root->be_type == SNMP_T_IPADDR) {
205 			fprintf(stderr, "addr %s", value);
206 		} else {
207 			fprintf(stderr, "string %s", value);
208 		}
209 		break;
210 	case BER_TYPE_NULL:	/* no payload */
211 	case BER_TYPE_EOC:
212 	case BER_TYPE_SEQUENCE:
213 	case BER_TYPE_SET:
214 	default:
215 		fprintf(stderr, "%s", value);
216 		break;
217 	}
218 
219  invalid:
220 	if (value == NULL)
221 		fprintf(stderr, "<INVALID>");
222 	else
223 		free(value);
224 	fprintf(stderr, "\n");
225 
226 	if (constructed)
227 		root->be_encoding = constructed;
228 
229 	if (constructed && root->be_sub) {
230 		indent += 2;
231 		smi_debug_elements(root->be_sub);
232 		indent -= 2;
233 	}
234 	if (root->be_next)
235 		smi_debug_elements(root->be_next);
236 }
237 
238 char *
239 smi_print_element(struct ber_element *root, int print_hint,
240     enum smi_output_string output_string, enum smi_oid_lookup lookup)
241 {
242 	char		*str = NULL, *buf, *p;
243 	size_t		 len, i, slen;
244 	long long	 v, ticks;
245 	int		 d;
246 	int		 is_hex = 0, ret;
247 	struct ber_oid	 o;
248 	char		 strbuf[BUFSIZ];
249 	char		*hint;
250 	int		 days, hours, min, sec, csec;
251 
252 	switch (root->be_encoding) {
253 	case BER_TYPE_BOOLEAN:
254 		if (ober_get_boolean(root, &d) == -1)
255 			goto fail;
256 		if (print_hint) {
257 			if (asprintf(&str, "INTEGER: %s(%d)",
258 			    d ? "true" : "false", d) == -1)
259 				goto fail;
260 		} else
261 			if (asprintf(&str, "%s", d ? "true" : "false") == -1)
262 				goto fail;
263 		break;
264 	case BER_TYPE_INTEGER:
265 	case BER_TYPE_ENUMERATED:
266 		if (ober_get_integer(root, &v) == -1)
267 			goto fail;
268 		if (root->be_class == BER_CLASS_APPLICATION &&
269 		    root->be_type == SNMP_T_TIMETICKS) {
270 			ticks = v;
271 			days = ticks / (60 * 60 * 24 * 100);
272 			ticks %= (60 * 60 * 24 * 100);
273 			hours = ticks / (60 * 60 * 100);
274 			ticks %= (60 * 60 * 100);
275 			min = ticks / (60 * 100);
276 			ticks %= (60 * 100);
277 			sec = ticks / 100;
278 			ticks %= 100;
279 			csec = ticks;
280 
281 			if (print_hint) {
282 				if (days == 0) {
283 					if (asprintf(&str,
284 					    "Timeticks: (%lld) "
285 					    "%d:%02d:%02d.%02d",
286 					    v, hours, min, sec, csec) == -1)
287 						goto fail;
288 				} else if (days == 1) {
289 					if (asprintf(&str,
290 					    "Timeticks: (%lld) "
291 					    "1 day %d:%02d:%02d.%02d",
292 					    v, hours, min, sec, csec) == -1)
293 						goto fail;
294 				} else {
295 					if (asprintf(&str,
296 					    "Timeticks: (%lld) "
297 					    "%d day %d:%02d:%02d.%02d",
298 					    v, days, hours, min, sec, csec) ==
299 					    -1)
300 						goto fail;
301 				}
302 			} else {
303 				if (days == 0) {
304 					if (asprintf(&str, "%d:%02d:%02d.%02d",
305 					    hours, min, sec, csec) == -1)
306 						goto fail;
307 				} else if (days == 1) {
308 					if (asprintf(&str,
309 					    "1 day %d:%02d:%02d.%02d",
310 					    hours, min, sec, csec) == -1)
311 						goto fail;
312 				} else {
313 					if (asprintf(&str,
314 					    "%d day %d:%02d:%02d.%02d",
315 					    days, hours, min, sec, csec) == -1)
316 						goto fail;
317 				}
318 			}
319 			break;
320 		}
321 		hint = "INTEGER: ";
322 		if (root->be_class == BER_CLASS_APPLICATION) {
323 			if (root->be_type == SNMP_T_COUNTER32)
324 				hint = "Counter32: ";
325 			else if (root->be_type == SNMP_T_GAUGE32)
326 				hint = "Gauge32: ";
327 			else if (root->be_type == SNMP_T_OPAQUE)
328 				hint = "Opaque: ";
329 			else if (root->be_type == SNMP_T_COUNTER64)
330 				hint = "Counter64: ";
331 		}
332 		if (asprintf(&str, "%s%lld", print_hint ? hint : "", v) == -1)
333 			goto fail;
334 		break;
335 	case BER_TYPE_BITSTRING:
336 		if (ober_get_bitstring(root, (void *)&buf, &len) == -1)
337 			goto fail;
338 		slen = len * 2 + 1 + sizeof("BITS: ");
339 		if ((str = calloc(1, slen)) == NULL)
340 			goto fail;
341 		p = str;
342 		if (print_hint) {
343 			strlcpy(str, "BITS: ", slen);
344 			p += sizeof("BITS: ");
345 		}
346 		for (i = 0; i < len; i++) {
347 			snprintf(p, 3, "%02x", buf[i]);
348 			p += 2;
349 		}
350 		break;
351 	case BER_TYPE_OBJECT:
352 		if (ober_get_oid(root, &o) == -1)
353 			goto fail;
354 		if (asprintf(&str, "%s%s",
355 		    print_hint ? "OID: " : "",
356 		    smi_oid2string(&o, strbuf, sizeof(strbuf), lookup)) == -1)
357 			goto fail;
358 		break;
359 	case BER_TYPE_OCTETSTRING:
360 		if (ober_get_string(root, &buf) == -1)
361 			goto fail;
362 		if (root->be_class == BER_CLASS_APPLICATION &&
363 		    root->be_type == SNMP_T_IPADDR) {
364 			if (asprintf(&str, "%s%s",
365 			    print_hint ? "IpAddress: " : "",
366 			    inet_ntoa(*(struct in_addr *)buf)) == -1)
367 				goto fail;
368 		} else if (root->be_class == BER_CLASS_CONTEXT) {
369 			if (root->be_type == SNMP_E_NOSUCHOBJECT)
370 				str = strdup("No Such Object available on this "
371 				    "agent at this OID");
372 			else if (root->be_type == SNMP_E_NOSUCHINSTANCE)
373 				str = strdup("No Such Instance currently "
374 				    "exists at this OID");
375 			else if (root->be_type == SNMP_E_ENDOFMIB)
376 				str = strdup("No more variables left in this "
377 				    "MIB View (It is past the end of the MIB "
378 				    "tree)");
379 			else
380 				str = strdup("Unknown status at this OID");
381 		} else {
382 			for (i = 0; i < root->be_len; i++) {
383 				if (!isprint(buf[i])) {
384 					if (output_string == smi_os_default)
385 						output_string = smi_os_hex;
386 					else if (output_string == smi_os_ascii)
387 						is_hex = 1;
388 					break;
389 				}
390 			}
391 			/*
392 			 * hex is 3 * n (2 digits + n - 1 spaces + NUL-byte)
393 			 * ascii can be max (2 * n) + 2 quotes + NUL-byte
394 			 */
395 			len = output_string == smi_os_hex ? 3 : 2;
396 			p = str = reallocarray(NULL, root->be_len + 2, len);
397 			if (p == NULL)
398 				goto fail;
399 			len *= root->be_len + 2;
400 			if (is_hex) {
401 				*str++ = '"';
402 				len--;
403 			}
404 			for (i = 0; i < root->be_len; i++) {
405 				switch (output_string) {
406 				case smi_os_default:
407 					/* FALLTHROUGH */
408 				case smi_os_ascii:
409 					/*
410 					 * There's probably more edgecases here,
411 					 * not fully investigated
412 					 */
413 					if (len < 2)
414 						goto fail;
415 					if (is_hex && buf[i] == '\\') {
416 						*str++ = '\\';
417 						len--;
418 					}
419 					*str++ = isprint(buf[i]) ? buf[i] : '.';
420 					len--;
421 					break;
422 				case smi_os_hex:
423 					ret = snprintf(str, len, "%s%02hhX",
424 					    i == 0 ? "" :
425 					    i % 16 == 0 ? "\n" : " ", buf[i]);
426 					if (ret == -1 || ret > (int) len)
427 						goto fail;
428 					len -= ret;
429 					str += ret;
430 					break;
431 				}
432 			}
433 			if (is_hex) {
434 				if (len < 2)
435 					goto fail;
436 				*str++ = '"';
437 				len--;
438 			}
439 			if (len == 0)
440 				goto fail;
441 			*str = '\0';
442 			str = NULL;
443 			if (asprintf(&str, "%s%s",
444 			    print_hint ?
445 			    output_string == smi_os_hex ? "Hex-STRING: " :
446 			    "STRING: " :
447 			    "", p) == -1) {
448 				free(p);
449 				goto fail;
450 			}
451 			free(p);
452 		}
453 		break;
454 	case BER_TYPE_NULL:	/* no payload */
455 	case BER_TYPE_EOC:
456 	case BER_TYPE_SEQUENCE:
457 	case BER_TYPE_SET:
458 	default:
459 		str = strdup("");
460 		break;
461 	}
462 
463 	return (str);
464 
465  fail:
466 	free(str);
467 	return (NULL);
468 }
469 
470 int
471 smi_string2oid(const char *oidstr, struct ber_oid *o)
472 {
473 	char			*sp, *p, str[BUFSIZ];
474 	const char		*errstr;
475 	struct oid		*oid;
476 	struct ber_oid		 ko;
477 
478 	if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str))
479 		return (-1);
480 	bzero(o, sizeof(*o));
481 
482 	/*
483 	 * Parse OID strings in the common form n.n.n or n-n-n.
484 	 * Based on ober_string2oid with additional support for symbolic names.
485 	 */
486 	p = sp = str[0] == '.' ? str + 1 : str;
487 	for (; p != NULL; sp = p) {
488 		if ((p = strpbrk(p, ".-")) != NULL)
489 			*p++ = '\0';
490 		if ((oid = smi_findkey(sp)) != NULL) {
491 			bcopy(&oid->o_id, &ko, sizeof(ko));
492 			if (o->bo_n && ober_oid_cmp(o, &ko) != 2)
493 				return (-1);
494 			bcopy(&ko, o, sizeof(*o));
495 			errstr = NULL;
496 		} else {
497 			o->bo_id[o->bo_n++] =
498 			    strtonum(sp, 0, UINT_MAX, &errstr);
499 		}
500 		if (errstr || o->bo_n > BER_MAX_OID_LEN)
501 			return (-1);
502 	}
503 
504 	return (0);
505 }
506 
507 unsigned int
508 smi_application(struct ber_element *elm)
509 {
510 	if (elm->be_class != BER_CLASS_APPLICATION)
511 		return (BER_TYPE_OCTETSTRING);
512 
513 	switch (elm->be_type) {
514 	case SNMP_T_IPADDR:
515 		return (BER_TYPE_OCTETSTRING);
516 	case SNMP_T_COUNTER32:
517 	case SNMP_T_GAUGE32:
518 	case SNMP_T_TIMETICKS:
519 	case SNMP_T_OPAQUE:
520 	case SNMP_T_COUNTER64:
521 		return (BER_TYPE_INTEGER);
522 	default:
523 		break;
524 	}
525 	return (BER_TYPE_OCTETSTRING);
526 
527 }
528 
529 char *
530 smi_oid2string(struct ber_oid *o, char *buf, size_t len,
531     enum smi_oid_lookup lookup)
532 {
533 	char		 str[256];
534 	struct oid	*value, key;
535 	size_t		 i;
536 
537 	bzero(buf, len);
538 	bzero(&key, sizeof(key));
539 	bcopy(o, &key.o_id, sizeof(struct ber_oid));
540 	key.o_flags |= OID_KEY;		/* do not match wildcards */
541 
542 	for (i = 0; i < o->bo_n; i++) {
543 		key.o_oidlen = i + 1;
544 		if (lookup != smi_oidl_numeric &&
545 		    (value = RB_FIND(oidtree, &smi_oidtree, &key)) != NULL) {
546 			snprintf(str, sizeof(str), "%s", value->o_name);
547 			if (lookup == smi_oidl_short && i + 1 < o->bo_n) {
548 				key.o_oidlen = i + 2;
549 				if (RB_FIND(oidtree, &smi_oidtree, &key) != NULL)
550 					continue;
551 			}
552 		} else
553 			snprintf(str, sizeof(str), "%d", key.o_oid[i]);
554 		if (*buf != '\0' || i == 0)
555 			strlcat(buf, ".", len);
556 		strlcat(buf, str, len);
557 	}
558 
559 	return (buf);
560 }
561 
562 void
563 smi_mibtree(struct oid *oids)
564 {
565 	struct oid	*oid, *decl;
566 	size_t		 i;
567 
568 	for (i = 0; oids[i].o_oid[0] != 0; i++) {
569 		oid = &oids[i];
570 		if (oid->o_name != NULL) {
571 			RB_INSERT(oidtree, &smi_oidtree, oid);
572 			RB_INSERT(keytree, &smi_keytree, oid);
573 			continue;
574 		}
575 		decl = RB_FIND(oidtree, &smi_oidtree, oid);
576 		decl->o_flags = oid->o_flags;
577 		decl->o_get = oid->o_get;
578 		decl->o_set = oid->o_set;
579 		decl->o_table = oid->o_table;
580 		decl->o_val = oid->o_val;
581 		decl->o_data = oid->o_data;
582 	}
583 }
584 
585 struct oid *
586 smi_findkey(char *name)
587 {
588 	struct oid	oid;
589 	if (name == NULL)
590 		return (NULL);
591 	oid.o_name = name;
592 	return (RB_FIND(keytree, &smi_keytree, &oid));
593 }
594 
595 struct oid *
596 smi_foreach(struct oid *oid, u_int flags)
597 {
598 	/*
599 	 * Traverse the tree of MIBs with the option to check
600 	 * for specific OID flags.
601 	 */
602 	if (oid == NULL) {
603 		oid = RB_MIN(oidtree, &smi_oidtree);
604 		if (oid == NULL)
605 			return (NULL);
606 		if (flags == 0 || (oid->o_flags & flags))
607 			return (oid);
608 	}
609 	for (;;) {
610 		oid = RB_NEXT(oidtree, &smi_oidtree, oid);
611 		if (oid == NULL)
612 			break;
613 		if (flags == 0 || (oid->o_flags & flags))
614 			return (oid);
615 	}
616 
617 	return (oid);
618 }
619 
620 int
621 smi_oid_cmp(struct oid *a, struct oid *b)
622 {
623 	size_t	 i;
624 
625 	for (i = 0; i < MINIMUM(a->o_oidlen, b->o_oidlen); i++) {
626 		if (a->o_oid[i] != b->o_oid[i])
627 			return (a->o_oid[i] - b->o_oid[i]);
628 	}
629 
630 	/*
631 	 * Return success if the matched object is a table
632 	 * or a MIB registered by a subagent
633 	 * (it will match any sub-elements)
634 	 */
635 	if ((b->o_flags & OID_TABLE ||
636 	    b->o_flags & OID_REGISTERED) &&
637 	    (a->o_flags & OID_KEY) == 0 &&
638 	    (a->o_oidlen > b->o_oidlen))
639 		return (0);
640 
641 	return (a->o_oidlen - b->o_oidlen);
642 }
643 
644 int
645 smi_key_cmp(struct oid *a, struct oid *b)
646 {
647 	if (a->o_name == NULL || b->o_name == NULL)
648 		return (-1);
649 	return (strcasecmp(a->o_name, b->o_name));
650 }
651 
652 RB_GENERATE(oidtree, oid, o_element, smi_oid_cmp)
653 RB_GENERATE(keytree, oid, o_keyword, smi_key_cmp)
654