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