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