So according to RFC2579 an octetstring can contain UTF-8 characters if so described in the DISPLAY-HINT. One of the main consumers of this is SnmpAdminString, which is used quite a lot.
Now that we trimmed a little fat from snmp's oid, I wanted to fill it up again and implemented the bare minimum DISPLAY-HINT parsing that is required for SnmpAdminString. Other parts of the syntax can be atter at a later state if desirable. Now I decided to implement this a little different from net-snmp, because they throw incomplete sequences directly to the terminal, which I recon is not a sane approach. I choose to take the approach taken by the likes of wall(1) and replace invalid and incomplete sequences with a '?'. This because DISPLAY-HINT already states that too long sequences strings should be cut short at the final multibyte character fitting inside the specified length, meaning that output can already get mangled. This also brings me to the question how we should handle DISPLAY-HINT in the case of -Oa and -Ox. Net-snmp prioritizes DISPLAY-HINT over these, but it has the option to disable this by disabling MIB-loading via -m''; This implementation doesn't give us the that option (yet?). The current diff follows net-snmp, but there is something to say to prioritize -Ox over DISPLAY-HINT, so an admin can use it to debug snmp-output without it being mangled. Any feedback here is welcome. Once it's clear if this is the right approach I'll do a thorough search through mib.h on which objects actually can use this new definition. Example choosen because his copyright was in front of me: $ snmp walk 127.0.0.1 sysContact sysContact.0 = Hex-STRING: 52 65 79 6B 20 46 6C C3 B6 74 65 72 $ ./obj/snmp walk 127.0.0.1 sysContact sysContact.0 = Reyk Flöter $ LC_ALL=C $ ./obj/snmp walk 127.0.0.1 sysContact sysContact.0 = Reyk Fl?ter Thoughts? martijn@ Index: mib.c =================================================================== RCS file: /cvs/src/usr.bin/snmp/mib.c,v retrieving revision 1.2 diff -u -p -r1.2 mib.c --- mib.c 19 May 2020 13:41:01 -0000 1.2 +++ mib.c 19 May 2020 20:21:42 -0000 @@ -27,9 +27,11 @@ #include "smi.h" static struct oid mib_tree[] = MIB_TREE; +static struct textconv textconv_tree[] = TEXTCONV_TREE; void mib_init(void) { smi_mibtree(mib_tree); + smi_textconvtree(textconv_tree); } Index: mib.h =================================================================== RCS file: /cvs/src/usr.bin/snmp/mib.h,v retrieving revision 1.1 diff -u -p -r1.1 mib.h --- mib.h 9 Aug 2019 06:17:59 -0000 1.1 +++ mib.h 19 May 2020 20:21:42 -0000 @@ -751,7 +751,7 @@ { MIBDECL(sysDescr) }, \ { MIBDECL(sysOID) }, \ { MIBDECL(sysUpTime) }, \ - { MIBDECL(sysContact) }, \ + { MIBDECL(sysContact), "SnmpAdminString" }, \ { MIBDECL(sysName) }, \ { MIBDECL(sysLocation) }, \ { MIBDECL(sysServices) }, \ @@ -1345,6 +1345,11 @@ { MIBDECL(ipfRouteEntRouteMetric5) }, \ { MIBDECL(ipfRouteEntStatus) }, \ { MIBEND } \ +} + +#define TEXTCONV_TREE { \ + { "SnmpAdminString", "255t", BER_TYPE_OCTETSTRING }, \ + { NULL, NULL } \ } void mib_init(void); Index: smi.c =================================================================== RCS file: /cvs/src/usr.bin/snmp/smi.c,v retrieving revision 1.8 diff -u -p -r1.8 smi.c --- smi.c 19 May 2020 13:41:01 -0000 1.8 +++ smi.c 19 May 2020 20:21:42 -0000 @@ -24,10 +24,13 @@ #include <arpa/inet.h> #include <ctype.h> +#include <errno.h> +#include <langinfo.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <strings.h> +#include <wchar.h> #include "ber.h" #include "mib.h" @@ -36,8 +39,12 @@ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) +int isu8cont(unsigned char); +char *smi_displayhint_os(struct textconv *, const char *, size_t); + 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); @@ -48,6 +55,10 @@ 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) { @@ -181,7 +192,7 @@ smi_debug_elements(struct ber_element *r fprintf(stderr, "(%u) encoding %u ", root->be_type, root->be_encoding); - if ((value = smi_print_element(root, 1, smi_os_default, + if ((value = smi_print_element(NULL, root, 1, smi_os_default, smi_oidl_numeric)) == NULL) goto invalid; @@ -236,10 +247,13 @@ smi_debug_elements(struct ber_element *r } char * -smi_print_element(struct ber_element *root, int print_hint, +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) { 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 d; @@ -249,6 +263,19 @@ smi_print_element(struct ber_element *ro 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->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_BOOLEAN: if (ober_get_boolean(root, &d) == -1) @@ -379,6 +406,10 @@ smi_print_element(struct ber_element *ro 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, + buf, root->be_len); for (i = 0; i < root->be_len; i++) { if (!isprint(buf[i])) { if (output_string == smi_os_default) @@ -575,6 +606,15 @@ smi_mibtree(struct oid *oids) } } +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) { @@ -598,6 +638,60 @@ smi_foreach(struct oid *oid) } int +isu8cont(unsigned char c) +{ + return (c & (0x80 | 0x40)) == 0x80; +} + +char * +smi_displayhint_os(struct textconv *tc, const char *buf, size_t buflen) +{ + size_t octetlength, i, j, start; + char *displayformat; + char *rbuf; + mbstate_t mbs; + + errno = 0; + octetlength = (size_t) strtol(tc->tc_display_hint, &displayformat, 10); + if (octetlength == 0 && errno != 0) { + errno = EINVAL; + return NULL; + } + if (displayformat[0] == 't') { + rbuf = malloc(octetlength + 1); + if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0) { + bzero(&mbs, sizeof(mbs)); + for (start = j = i = 0; i < octetlength && i < buflen; i++) { + switch (mbrtowc(NULL, &(buf[i]), 1, &mbs)) { + case 0: + rbuf[j++] = '\0'; + break; + case -1: + rbuf[j++] = '?'; + bzero(&mbs, sizeof(mbs)); + break; + case -2: + break; + default: + memcpy(&(rbuf[j]), &(buf[start]), i - start + 1); + j += i - start + 1; + start = i + 1; + break; + } + } + } else { + for (j = i = 0; i < octetlength && i < buflen; i++) { + if (isu8cont(buf[i])) + continue; + rbuf[j++] = isprint(buf[i]) ? buf[i] : '?'; + } + } + return rbuf; + } + return NULL; +} + +int smi_oid_cmp(struct oid *a, struct oid *b) { size_t i; @@ -618,5 +712,12 @@ smi_key_cmp(struct oid *a, struct oid *b 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); Index: smi.h =================================================================== RCS file: /cvs/src/usr.bin/snmp/smi.h,v retrieving revision 1.2 diff -u -p -r1.2 smi.h --- smi.h 19 May 2020 13:41:01 -0000 1.2 +++ smi.h 19 May 2020 20:21:42 -0000 @@ -59,18 +59,28 @@ struct oid { #define o_oid o_id.bo_id #define o_oidlen o_id.bo_n - char *o_name; + const char *o_name; + const char *o_tcname; + struct textconv *o_textconv; RB_ENTRY(oid) o_element; RB_ENTRY(oid) o_keyword; }; +struct textconv { + const char *tc_name; + const char *tc_display_hint; + unsigned int tc_syntax; + RB_ENTRY(textconv) tc_entry; +}; + int smi_init(void); unsigned int smi_application(struct ber_element *); int smi_string2oid(const char *, struct ber_oid *); char *smi_oid2string(struct ber_oid *, char *, size_t, enum smi_oid_lookup); void smi_mibtree(struct oid *); +void smi_textconvtree(struct textconv *); struct oid *smi_foreach(struct oid *); void smi_debug_elements(struct ber_element *); -char *smi_print_element(struct ber_element *, int, enum smi_output_string, - enum smi_oid_lookup); +char *smi_print_element(struct ber_oid *, struct ber_element *, int, + enum smi_output_string, enum smi_oid_lookup); Index: snmpc.c =================================================================== RCS file: /cvs/src/usr.bin/snmp/snmpc.c,v retrieving revision 1.25 diff -u -p -r1.25 snmpc.c --- snmpc.c 19 May 2020 13:41:01 -0000 1.25 +++ snmpc.c 19 May 2020 20:21:42 -0000 @@ -29,6 +29,7 @@ #include <ctype.h> #include <err.h> #include <errno.h> +#include <locale.h> #include <netdb.h> #include <poll.h> #include <stdio.h> @@ -129,6 +130,8 @@ main(int argc, char *argv[]) int ch; size_t i; + setlocale(LC_CTYPE, ""); + if (pledge("stdio inet dns unix", NULL) == -1) err(1, "pledge"); @@ -1069,7 +1072,8 @@ snmpc_print(struct ber_element *elm) } elm = elm->be_next; - value = smi_print_element(elm, smi_print_hint, output_string, oid_lookup); + value = smi_print_element(&oid, elm, smi_print_hint, output_string, + oid_lookup); if (value == NULL) return 0;