/* $OpenBSD: smi.c,v 1.14 2021/01/04 08:00:29 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren * Copyright (c) 2007, 2008 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "ber.h" #include "mib.h" #include "snmp.h" #include "smi.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) char *smi_displayhint_os(struct textconv *, int, const char *, size_t, int); char *smi_displayhint_int(struct textconv*, int, long long); int smi_oid_cmp(struct oid *, struct oid *); int smi_key_cmp(struct oid *, struct oid *); int smi_textconv_cmp(struct textconv *, struct textconv *); struct oid * smi_findkey(char *); RB_HEAD(oidtree, oid); RB_PROTOTYPE(oidtree, oid, o_element, smi_oid_cmp) struct oidtree smi_oidtree; RB_HEAD(keytree, oid); RB_PROTOTYPE(keytree, oid, o_keyword, smi_key_cmp) struct keytree smi_keytree; RB_HEAD(textconvtree, textconv); RB_PROTOTYPE(textconvtree, textconv, tc_entry, smi_textconv_cmp); struct textconvtree smi_tctree; int smi_init(void) { /* Initialize the Structure of Managed Information (SMI) */ RB_INIT(&smi_oidtree); mib_init(); return (0); } void smi_debug_elements(struct ber_element *root, int utf8) { static int indent = 0; char *value; int constructed; /* calculate lengths */ ober_calc_len(root); switch (root->be_encoding) { case BER_TYPE_SEQUENCE: case BER_TYPE_SET: constructed = root->be_encoding; break; default: constructed = 0; break; } fprintf(stderr, "%*slen %lu ", indent, "", root->be_len); switch (root->be_class) { case BER_CLASS_UNIVERSAL: fprintf(stderr, "class: universal(%u) type: ", root->be_class); switch (root->be_type) { case BER_TYPE_EOC: fprintf(stderr, "end-of-content"); break; case BER_TYPE_INTEGER: fprintf(stderr, "integer"); break; case BER_TYPE_BITSTRING: fprintf(stderr, "bit-string"); break; case BER_TYPE_OCTETSTRING: fprintf(stderr, "octet-string"); break; case BER_TYPE_NULL: fprintf(stderr, "null"); break; case BER_TYPE_OBJECT: fprintf(stderr, "object"); break; case BER_TYPE_ENUMERATED: fprintf(stderr, "enumerated"); break; case BER_TYPE_SEQUENCE: fprintf(stderr, "sequence"); break; case BER_TYPE_SET: fprintf(stderr, "set"); break; } break; case BER_CLASS_APPLICATION: fprintf(stderr, "class: application(%u) type: ", root->be_class); switch (root->be_type) { case SNMP_T_IPADDR: fprintf(stderr, "ipaddr"); break; case SNMP_T_COUNTER32: fprintf(stderr, "counter32"); break; case SNMP_T_GAUGE32: fprintf(stderr, "gauge32"); break; case SNMP_T_TIMETICKS: fprintf(stderr, "timeticks"); break; case SNMP_T_OPAQUE: fprintf(stderr, "opaque"); break; case SNMP_T_COUNTER64: fprintf(stderr, "counter64"); break; } break; case BER_CLASS_CONTEXT: fprintf(stderr, "class: context(%u) type: ", root->be_class); switch (root->be_type) { case SNMP_C_GETREQ: fprintf(stderr, "getreq"); break; case SNMP_C_GETNEXTREQ: fprintf(stderr, "nextreq"); break; case SNMP_C_GETRESP: fprintf(stderr, "getresp"); break; case SNMP_C_SETREQ: fprintf(stderr, "setreq"); break; case SNMP_C_TRAP: fprintf(stderr, "trap"); break; case SNMP_C_GETBULKREQ: fprintf(stderr, "getbulkreq"); break; case SNMP_C_INFORMREQ: fprintf(stderr, "informreq"); break; case SNMP_C_TRAPV2: fprintf(stderr, "trapv2"); break; case SNMP_C_REPORT: fprintf(stderr, "report"); break; } break; case BER_CLASS_PRIVATE: fprintf(stderr, "class: private(%u) type: ", root->be_class); break; default: fprintf(stderr, "class: (%u) type: ", root->be_class); break; } fprintf(stderr, "(%u) encoding %u ", root->be_type, root->be_encoding); if ((value = smi_print_element(NULL, root, 1, smi_os_default, smi_oidl_numeric, utf8)) == NULL) goto invalid; switch (root->be_encoding) { case BER_TYPE_INTEGER: case BER_TYPE_ENUMERATED: fprintf(stderr, "value %s", value); break; case BER_TYPE_BITSTRING: fprintf(stderr, "hexdump %s", value); break; case BER_TYPE_OBJECT: fprintf(stderr, "oid %s", value); break; case BER_TYPE_OCTETSTRING: if (root->be_class == BER_CLASS_APPLICATION && root->be_type == SNMP_T_IPADDR) { fprintf(stderr, "addr %s", value); } else { fprintf(stderr, "string %s", value); } break; case BER_TYPE_NULL: /* no payload */ case BER_TYPE_EOC: case BER_TYPE_SEQUENCE: case BER_TYPE_SET: default: fprintf(stderr, "%s", value); break; } invalid: if (value == NULL) fprintf(stderr, ""); else free(value); fprintf(stderr, "\n"); if (constructed) root->be_encoding = constructed; if (constructed && root->be_sub) { indent += 2; smi_debug_elements(root->be_sub, utf8); indent -= 2; } if (root->be_next) smi_debug_elements(root->be_next, utf8); } char * smi_print_element(struct ber_oid *oid, struct ber_element *root, int print_hint, enum smi_output_string output_string, enum smi_oid_lookup lookup, int utf8) { char *str = NULL, *buf, *p; struct oid okey; struct oid *object = NULL; struct textconv tckey; size_t len, i, slen; long long v, ticks; int is_hex = 0, ret; struct ber_oid o; char strbuf[BUFSIZ]; char *hint; int days, hours, min, sec, csec; if (oid != NULL) { bcopy(oid, &(okey.o_id), sizeof(okey)); do { object = RB_FIND(oidtree, &smi_oidtree, &okey); okey.o_id.bo_n--; } while (object == NULL && okey.o_id.bo_n > 0); if (object != NULL && object->o_textconv == NULL && object->o_tcname != NULL) { tckey.tc_name = object->o_tcname; object->o_textconv = RB_FIND(textconvtree, &smi_tctree, &tckey); } } switch (root->be_encoding) { case BER_TYPE_INTEGER: case BER_TYPE_ENUMERATED: if (ober_get_integer(root, &v) == -1) goto fail; if (root->be_class == BER_CLASS_APPLICATION && root->be_type == SNMP_T_TIMETICKS) { ticks = v; days = ticks / (60 * 60 * 24 * 100); ticks %= (60 * 60 * 24 * 100); hours = ticks / (60 * 60 * 100); ticks %= (60 * 60 * 100); min = ticks / (60 * 100); ticks %= (60 * 100); sec = ticks / 100; ticks %= 100; csec = ticks; if (print_hint) { if (days == 0) { if (asprintf(&str, "Timeticks: (%lld) " "%d:%02d:%02d.%02d", v, hours, min, sec, csec) == -1) goto fail; } else if (days == 1) { if (asprintf(&str, "Timeticks: (%lld) " "1 day %d:%02d:%02d.%02d", v, hours, min, sec, csec) == -1) goto fail; } else { if (asprintf(&str, "Timeticks: (%lld) " "%d day %d:%02d:%02d.%02d", v, days, hours, min, sec, csec) == -1) goto fail; } } else { if (days == 0) { if (asprintf(&str, "%d:%02d:%02d.%02d", hours, min, sec, csec) == -1) goto fail; } else if (days == 1) { if (asprintf(&str, "1 day %d:%02d:%02d.%02d", hours, min, sec, csec) == -1) goto fail; } else { if (asprintf(&str, "%d day %d:%02d:%02d.%02d", days, hours, min, sec, csec) == -1) goto fail; } } break; } hint = "INTEGER: "; if (object != NULL && object->o_textconv != NULL && object->o_textconv->tc_syntax == root->be_encoding) return smi_displayhint_int(object->o_textconv, print_hint, v); if (root->be_class == BER_CLASS_APPLICATION) { if (root->be_type == SNMP_T_COUNTER32) hint = "Counter32: "; else if (root->be_type == SNMP_T_GAUGE32) hint = "Gauge32: "; else if (root->be_type == SNMP_T_OPAQUE) hint = "Opaque: "; else if (root->be_type == SNMP_T_COUNTER64) hint = "Counter64: "; } if (asprintf(&str, "%s%lld", print_hint ? hint : "", v) == -1) goto fail; break; case BER_TYPE_BITSTRING: if (ober_get_bitstring(root, (void *)&buf, &len) == -1) goto fail; slen = len * 2 + 1 + sizeof("BITS: "); if ((str = calloc(1, slen)) == NULL) goto fail; p = str; if (print_hint) { strlcpy(str, "BITS: ", slen); p += sizeof("BITS: "); } for (i = 0; i < len; i++) { snprintf(p, 3, "%02x", buf[i]); p += 2; } break; case BER_TYPE_OBJECT: if (ober_get_oid(root, &o) == -1) goto fail; if (asprintf(&str, "%s%s", print_hint ? "OID: " : "", smi_oid2string(&o, strbuf, sizeof(strbuf), lookup)) == -1) goto fail; break; case BER_TYPE_OCTETSTRING: if (ober_get_string(root, &buf) == -1) goto fail; if (root->be_class == BER_CLASS_APPLICATION && root->be_type == SNMP_T_IPADDR) { if (asprintf(&str, "%s%s", print_hint ? "IpAddress: " : "", inet_ntoa(*(struct in_addr *)buf)) == -1) goto fail; } else if (root->be_class == BER_CLASS_CONTEXT) { if (root->be_type == SNMP_E_NOSUCHOBJECT) str = strdup("No Such Object available on this " "agent at this OID"); else if (root->be_type == SNMP_E_NOSUCHINSTANCE) str = strdup("No Such Instance currently " "exists at this OID"); else if (root->be_type == SNMP_E_ENDOFMIB) str = strdup("No more variables left in this " "MIB View (It is past the end of the MIB " "tree)"); else str = strdup("Unknown status at this OID"); } else { if (object != NULL && object->o_textconv != NULL && object->o_textconv->tc_syntax == root->be_encoding) return smi_displayhint_os(object->o_textconv, print_hint, buf, root->be_len, utf8); for (i = 0; i < root->be_len; i++) { if (!isprint(buf[i])) { if (output_string == smi_os_default) output_string = smi_os_hex; else if (output_string == smi_os_ascii) is_hex = 1; break; } } /* * hex is 3 * n (2 digits + n - 1 spaces + NUL-byte) * ascii can be max (2 * n) + 2 quotes + NUL-byte */ len = output_string == smi_os_hex ? 3 : 2; p = str = reallocarray(NULL, root->be_len + 2, len); if (p == NULL) goto fail; len *= root->be_len + 2; if (is_hex) { *str++ = '"'; len--; } for (i = 0; i < root->be_len; i++) { switch (output_string) { case smi_os_default: /* FALLTHROUGH */ case smi_os_ascii: /* * There's probably more edgecases here, * not fully investigated */ if (len < 2) goto fail; if (is_hex && buf[i] == '\\') { *str++ = '\\'; len--; } *str++ = isprint(buf[i]) ? buf[i] : '.'; len--; break; case smi_os_hex: ret = snprintf(str, len, "%s%02hhX", i == 0 ? "" : i % 16 == 0 ? "\n" : " ", buf[i]); if (ret == -1 || ret > (int) len) goto fail; len -= ret; str += ret; break; } } if (is_hex) { if (len < 2) goto fail; *str++ = '"'; len--; } if (len == 0) goto fail; *str = '\0'; str = NULL; if (asprintf(&str, "%s%s", print_hint ? output_string == smi_os_hex ? "Hex-STRING: " : "STRING: " : "", p) == -1) { free(p); goto fail; } free(p); } break; case BER_TYPE_NULL: /* no payload */ case BER_TYPE_EOC: case BER_TYPE_SEQUENCE: case BER_TYPE_SET: default: str = strdup(""); break; } return (str); fail: free(str); return (NULL); } int smi_string2oid(const char *oidstr, struct ber_oid *o) { char *sp, *p, str[BUFSIZ]; const char *errstr; struct oid *oid; struct ber_oid ko; if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str)) return (-1); bzero(o, sizeof(*o)); /* * Parse OID strings in the common form n.n.n or n-n-n. * Based on ober_string2oid with additional support for symbolic names. */ p = sp = str[0] == '.' ? str + 1 : str; for (; p != NULL; sp = p) { if ((p = strpbrk(p, ".-")) != NULL) *p++ = '\0'; if ((oid = smi_findkey(sp)) != NULL) { bcopy(&oid->o_id, &ko, sizeof(ko)); if (o->bo_n && ober_oid_cmp(o, &ko) != 2) return (-1); bcopy(&ko, o, sizeof(*o)); errstr = NULL; } else { o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr); } if (errstr || o->bo_n > BER_MAX_OID_LEN) return (-1); } return (0); } unsigned int smi_application(struct ber_element *elm) { if (elm->be_class != BER_CLASS_APPLICATION) return (BER_TYPE_OCTETSTRING); switch (elm->be_type) { case SNMP_T_IPADDR: return (BER_TYPE_OCTETSTRING); case SNMP_T_COUNTER32: case SNMP_T_GAUGE32: case SNMP_T_TIMETICKS: case SNMP_T_OPAQUE: case SNMP_T_COUNTER64: return (BER_TYPE_INTEGER); default: break; } return (BER_TYPE_OCTETSTRING); } char * smi_oid2string(struct ber_oid *o, char *buf, size_t len, enum smi_oid_lookup lookup) { char str[256]; struct oid *value, key; size_t i; bzero(buf, len); bzero(&key, sizeof(key)); bcopy(o, &key.o_id, sizeof(struct ber_oid)); for (i = 0; i < o->bo_n; i++) { key.o_oidlen = i + 1; if (lookup != smi_oidl_numeric && (value = RB_FIND(oidtree, &smi_oidtree, &key)) != NULL) { snprintf(str, sizeof(str), "%s", value->o_name); if (lookup == smi_oidl_short && i + 1 < o->bo_n) { key.o_oidlen = i + 2; if (RB_FIND(oidtree, &smi_oidtree, &key) != NULL) continue; } } else snprintf(str, sizeof(str), "%u", key.o_oid[i]); if (*buf != '\0' || i == 0) strlcat(buf, ".", len); strlcat(buf, str, len); } return (buf); } void smi_mibtree(struct oid *oids) { size_t i; for (i = 0; oids[i].o_name != NULL; i++) { RB_INSERT(oidtree, &smi_oidtree, &(oids[i])); RB_INSERT(keytree, &smi_keytree, &(oids[i])); } } void smi_textconvtree(struct textconv *textconvs) { size_t i = 0; for (i = 0; textconvs[i].tc_name != NULL; i++) RB_INSERT(textconvtree, &smi_tctree, &(textconvs[i])); } struct oid * smi_findkey(char *name) { struct oid oid; if (name == NULL) return (NULL); oid.o_name = name; return (RB_FIND(keytree, &smi_keytree, &oid)); } struct oid * smi_foreach(struct oid *oid) { /* * Traverse the tree of MIBs with the option to check * for specific OID flags. */ if (oid == NULL) return RB_MIN(oidtree, &smi_oidtree); return RB_NEXT(oidtree, &smi_oidtree, oid); } char * smi_displayhint_int(struct textconv *tc, int print_hint, long long v) { size_t i; char *rbuf; for (i = 0; tc->tc_enum[i].tce_name != NULL; i++) { if (tc->tc_enum[i].tce_number == v) { if (print_hint) { if (asprintf(&rbuf, "INTEGER: %s(%lld)", tc->tc_enum[i].tce_name, v) == -1) return NULL; } else { if (asprintf(&rbuf, "%s", tc->tc_enum[i].tce_name) == -1) return NULL; } return rbuf; } } if (asprintf(&rbuf, "%s%lld", print_hint ? "INTEGER: " : "", v) == -1) return NULL; return rbuf; } #define REPLACEMENT "\357\277\275" char * smi_displayhint_os(struct textconv *tc, int print_hint, const char *src, size_t srclen, int utf8) { size_t octetlength, i = 0, j = 0; size_t prefixlen; unsigned long ulval; int clen; char *displayformat; const char *prefix; char *rbuf, *dst; wchar_t wc; prefix = print_hint ? "STRING: " : ""; prefixlen = strlen(prefix); errno = 0; ulval = strtoul(tc->tc_display_hint, &displayformat, 10); octetlength = ulval; if (!isdigit(tc->tc_display_hint[0]) || (errno != 0 && (ulval == 0 || ulval == ULONG_MAX)) || (unsigned long) octetlength != ulval) { errno = EINVAL; return NULL; } if (displayformat[0] == 't' || displayformat[0] == 'a') { if ((rbuf = malloc(prefixlen + octetlength + 1)) == NULL) return NULL; (void)strlcpy(rbuf, prefix, prefixlen + octetlength + 1); dst = rbuf + prefixlen; while (j < octetlength && i < srclen) { clen = mbtowc(&wc, &(src[i]), srclen - i); if (displayformat[0] == 'a' && clen > 1) clen = -1; switch (clen) { case 0: dst[j++] = '.'; i++; break; case -1: mbtowc(NULL, NULL, MB_CUR_MAX); if (utf8) { if (octetlength - j < sizeof(REPLACEMENT) - 1) { dst[j] = '\0'; return rbuf; } memcpy(&(dst[j]), REPLACEMENT, sizeof(REPLACEMENT) - 1); j += sizeof(REPLACEMENT) - 1; } else dst[j++] = '?'; i++; break; default: if (!iswprint(wc) || (!utf8 && clen > 1)) dst[j++] = '.'; else if (octetlength - j < (size_t)clen) { dst[j] = '\0'; return rbuf; } else { memcpy(&(dst[j]), &(src[i]), clen); j += clen; } i += clen; break; } } dst[j] = '\0'; return rbuf; } errno = EINVAL; return NULL; } int smi_oid_cmp(struct oid *a, struct oid *b) { size_t i; for (i = 0; i < MINIMUM(a->o_oidlen, b->o_oidlen); i++) { if (a->o_oid[i] != b->o_oid[i]) return (a->o_oid[i] - b->o_oid[i]); } return (a->o_oidlen - b->o_oidlen); } int smi_key_cmp(struct oid *a, struct oid *b) { if (a->o_name == NULL || b->o_name == NULL) return (-1); return (strcasecmp(a->o_name, b->o_name)); } int smi_textconv_cmp(struct textconv *a, struct textconv *b) { return strcmp(a->tc_name, b->tc_name); } RB_GENERATE(oidtree, oid, o_element, smi_oid_cmp) RB_GENERATE(keytree, oid, o_keyword, smi_key_cmp) RB_GENERATE(textconvtree, textconv, tc_entry, smi_textconv_cmp);