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