[BACK]Return to smi.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / snmp

File: [local] / src / usr.bin / snmp / smi.c (download)

Revision 1.11, Sat Aug 8 07:18:08 2020 UTC (3 years, 10 months ago) by martijn
Branch: MAIN
Changes since 1.10: +13 -13 lines

Somewhere during working on UTF-8 support I thought it was a good idea to
use sysContact as a testcase, because it's readily available in snmpd, but
forgetting it's actual textual convention as time went by, which is
DisplayString (which is ASCII).

Add support for the DisplayString textual convention, which is similar to
having no textual convention, except that invalid bytes (value 128 and up)
are printed as UTF-8 replacement characters, or question mark, depending on
LC_CTYPE.

Feedback and OK schwarze@

/*	$OpenBSD: smi.c,v 1.11 2020/08/08 07:18:08 martijn Exp $	*/

/*
 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
 * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org>
 *
 * 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 <sys/limits.h>
#include <sys/tree.h>
#include <sys/queue.h>

#include <arpa/inet.h>

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <wctype.h>

#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);

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_BOOLEAN:
			fprintf(stderr, "boolean");
			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: <INVALID>(%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_BOOLEAN:
		fprintf(stderr, "%s", value);
		break;
	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, "<INVALID>");
	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		 d;
	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_BOOLEAN:
		if (ober_get_boolean(root, &d) == -1)
			goto fail;
		if (print_hint) {
			if (asprintf(&str, "INTEGER: %s(%d)",
			    d ? "true" : "false", d) == -1)
				goto fail;
		} else
			if (asprintf(&str, "%s", d ? "true" : "false") == -1)
				goto fail;
		break;
	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 (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)
{
	struct oid	*oid, *decl;
	size_t		 i;

	for (i = 0; oids[i].o_oid[0] != 0; i++) {
		oid = &oids[i];
		if (oid->o_name != NULL) {
			RB_INSERT(oidtree, &smi_oidtree, oid);
			RB_INSERT(keytree, &smi_keytree, oid);
			continue;
		}
		decl = RB_FIND(oidtree, &smi_oidtree, oid);
	}
}

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);
}

#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);