smi.c revision 1.6
1/*	$OpenBSD: smi.c,v 1.6 2019/10/24 12:39:26 tb 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
39int smi_oid_cmp(struct oid *, struct oid *);
40int smi_key_cmp(struct oid *, struct oid *);
41struct oid * smi_findkey(char *);
42
43RB_HEAD(oidtree, oid);
44RB_PROTOTYPE(oidtree, oid, o_element, smi_oid_cmp)
45struct oidtree smi_oidtree;
46
47RB_HEAD(keytree, oid);
48RB_PROTOTYPE(keytree, oid, o_keyword, smi_key_cmp)
49struct keytree smi_keytree;
50
51int
52smi_init(void)
53{
54	/* Initialize the Structure of Managed Information (SMI) */
55	RB_INIT(&smi_oidtree);
56	mib_init();
57	return (0);
58}
59
60void
61smi_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
238char *
239smi_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		    root->be_type == BER_TYPE_EOC) {
370			str = strdup("No Such Object available on this agent at this OID");
371		} else {
372			for (i = 0; i < root->be_len; i++) {
373				if (!isprint(buf[i])) {
374					if (output_string == smi_os_default)
375						output_string = smi_os_hex;
376					else if (output_string == smi_os_ascii)
377						is_hex = 1;
378					break;
379				}
380			}
381			/*
382			 * hex is 3 * n (2 digits + n - 1 spaces + NUL-byte)
383			 * ascii can be max (2 * n) + 2 quotes + NUL-byte
384			 */
385			len = output_string == smi_os_hex ? 3 : 2;
386			p = str = reallocarray(NULL, root->be_len + 2, len);
387			if (p == NULL)
388				goto fail;
389			len *= root->be_len + 2;
390			if (is_hex) {
391				*str++ = '"';
392				len--;
393			}
394			for (i = 0; i < root->be_len; i++) {
395				switch (output_string) {
396				case smi_os_default:
397					/* FALLTHROUGH */
398				case smi_os_ascii:
399					/*
400					 * There's probably more edgecases here,
401					 * not fully investigated
402					 */
403					if (len < 2)
404						goto fail;
405					if (is_hex && buf[i] == '\\') {
406						*str++ = '\\';
407						len--;
408					}
409					*str++ = isprint(buf[i]) ? buf[i] : '.';
410					len--;
411					break;
412				case smi_os_hex:
413					ret = snprintf(str, len, "%s%02hhX",
414					    i == 0 ? "" :
415					    i % 16 == 0 ? "\n" : " ", buf[i]);
416					if (ret == -1 || ret > (int) len)
417						goto fail;
418					len -= ret;
419					str += ret;
420					break;
421				}
422			}
423			if (is_hex) {
424				if (len < 2)
425					goto fail;
426				*str++ = '"';
427				len--;
428			}
429			if (len == 0)
430				goto fail;
431			*str = '\0';
432			str = NULL;
433			if (asprintf(&str, "%s%s",
434			    print_hint ?
435			    output_string == smi_os_hex ? "Hex-STRING: " :
436			    "STRING: " :
437			    "", p) == -1) {
438				free(p);
439				goto fail;
440			}
441			free(p);
442		}
443		break;
444	case BER_TYPE_NULL:	/* no payload */
445	case BER_TYPE_EOC:
446	case BER_TYPE_SEQUENCE:
447	case BER_TYPE_SET:
448	default:
449		str = strdup("");
450		break;
451	}
452
453	return (str);
454
455 fail:
456	free(str);
457	return (NULL);
458}
459
460int
461smi_string2oid(const char *oidstr, struct ber_oid *o)
462{
463	char			*sp, *p, str[BUFSIZ];
464	const char		*errstr;
465	struct oid		*oid;
466	struct ber_oid		 ko;
467
468	if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str))
469		return (-1);
470	bzero(o, sizeof(*o));
471
472	/*
473	 * Parse OID strings in the common form n.n.n or n-n-n.
474	 * Based on ober_string2oid with additional support for symbolic names.
475	 */
476	p = sp = str[0] == '.' ? str + 1 : str;
477	for (; p != NULL; sp = p) {
478		if ((p = strpbrk(p, ".-")) != NULL)
479			*p++ = '\0';
480		if ((oid = smi_findkey(sp)) != NULL) {
481			bcopy(&oid->o_id, &ko, sizeof(ko));
482			if (o->bo_n && ober_oid_cmp(o, &ko) != 2)
483				return (-1);
484			bcopy(&ko, o, sizeof(*o));
485			errstr = NULL;
486		} else {
487			o->bo_id[o->bo_n++] =
488			    strtonum(sp, 0, UINT_MAX, &errstr);
489		}
490		if (errstr || o->bo_n > BER_MAX_OID_LEN)
491			return (-1);
492	}
493
494	return (0);
495}
496
497unsigned int
498smi_application(struct ber_element *elm)
499{
500	if (elm->be_class != BER_CLASS_APPLICATION)
501		return (BER_TYPE_OCTETSTRING);
502
503	switch (elm->be_type) {
504	case SNMP_T_IPADDR:
505		return (BER_TYPE_OCTETSTRING);
506	case SNMP_T_COUNTER32:
507	case SNMP_T_GAUGE32:
508	case SNMP_T_TIMETICKS:
509	case SNMP_T_OPAQUE:
510	case SNMP_T_COUNTER64:
511		return (BER_TYPE_INTEGER);
512	default:
513		break;
514	}
515	return (BER_TYPE_OCTETSTRING);
516
517}
518
519char *
520smi_oid2string(struct ber_oid *o, char *buf, size_t len,
521    enum smi_oid_lookup lookup)
522{
523	char		 str[256];
524	struct oid	*value, key;
525	size_t		 i;
526
527	bzero(buf, len);
528	bzero(&key, sizeof(key));
529	bcopy(o, &key.o_id, sizeof(struct ber_oid));
530	key.o_flags |= OID_KEY;		/* do not match wildcards */
531
532	for (i = 0; i < o->bo_n; i++) {
533		key.o_oidlen = i + 1;
534		if (lookup != smi_oidl_numeric &&
535		    (value = RB_FIND(oidtree, &smi_oidtree, &key)) != NULL) {
536			snprintf(str, sizeof(str), "%s", value->o_name);
537			if (lookup == smi_oidl_short && i + 1 < o->bo_n) {
538				key.o_oidlen = i + 2;
539				if (RB_FIND(oidtree, &smi_oidtree, &key) != NULL)
540					continue;
541			}
542		} else
543			snprintf(str, sizeof(str), "%d", key.o_oid[i]);
544		if (*buf != '\0' || i == 0)
545			strlcat(buf, ".", len);
546		strlcat(buf, str, len);
547	}
548
549	return (buf);
550}
551
552void
553smi_mibtree(struct oid *oids)
554{
555	struct oid	*oid, *decl;
556	size_t		 i;
557
558	for (i = 0; oids[i].o_oid[0] != 0; i++) {
559		oid = &oids[i];
560		if (oid->o_name != NULL) {
561			RB_INSERT(oidtree, &smi_oidtree, oid);
562			RB_INSERT(keytree, &smi_keytree, oid);
563			continue;
564		}
565		decl = RB_FIND(oidtree, &smi_oidtree, oid);
566		decl->o_flags = oid->o_flags;
567		decl->o_get = oid->o_get;
568		decl->o_set = oid->o_set;
569		decl->o_table = oid->o_table;
570		decl->o_val = oid->o_val;
571		decl->o_data = oid->o_data;
572	}
573}
574
575struct oid *
576smi_findkey(char *name)
577{
578	struct oid	oid;
579	if (name == NULL)
580		return (NULL);
581	oid.o_name = name;
582	return (RB_FIND(keytree, &smi_keytree, &oid));
583}
584
585struct oid *
586smi_foreach(struct oid *oid, u_int flags)
587{
588	/*
589	 * Traverse the tree of MIBs with the option to check
590	 * for specific OID flags.
591	 */
592	if (oid == NULL) {
593		oid = RB_MIN(oidtree, &smi_oidtree);
594		if (oid == NULL)
595			return (NULL);
596		if (flags == 0 || (oid->o_flags & flags))
597			return (oid);
598	}
599	for (;;) {
600		oid = RB_NEXT(oidtree, &smi_oidtree, oid);
601		if (oid == NULL)
602			break;
603		if (flags == 0 || (oid->o_flags & flags))
604			return (oid);
605	}
606
607	return (oid);
608}
609
610int
611smi_oid_cmp(struct oid *a, struct oid *b)
612{
613	size_t	 i;
614
615	for (i = 0; i < MINIMUM(a->o_oidlen, b->o_oidlen); i++) {
616		if (a->o_oid[i] != b->o_oid[i])
617			return (a->o_oid[i] - b->o_oid[i]);
618	}
619
620	/*
621	 * Return success if the matched object is a table
622	 * or a MIB registered by a subagent
623	 * (it will match any sub-elements)
624	 */
625	if ((b->o_flags & OID_TABLE ||
626	    b->o_flags & OID_REGISTERED) &&
627	    (a->o_flags & OID_KEY) == 0 &&
628	    (a->o_oidlen > b->o_oidlen))
629		return (0);
630
631	return (a->o_oidlen - b->o_oidlen);
632}
633
634int
635smi_key_cmp(struct oid *a, struct oid *b)
636{
637	if (a->o_name == NULL || b->o_name == NULL)
638		return (-1);
639	return (strcasecmp(a->o_name, b->o_name));
640}
641
642RB_GENERATE(oidtree, oid, o_element, smi_oid_cmp)
643RB_GENERATE(keytree, oid, o_keyword, smi_key_cmp)
644