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