Index | Thread | Search

From:
Martijn van Duren <openbsd+tech@list.imperialat.at>
Subject:
snmpd: testers wanted: varbind syntax validation
To:
tech@openbsd.org
Date:
Mon, 2 Nov 2026 13:47:43 +0100

Download raw body.

Thread
  • Martijn van Duren:

    snmpd: testers wanted: varbind syntax validation

Hello tech@,

I'm not sure in what direction I want to take this diff, but below is an
early attempt at syntax validation for varbinds in snmpd. With this diff
I found the invalid carpGroupDemote INTEGER range for which I just
committed a fix. If you run snmpd on a low risk machine I would be
interested to see what varbinds break and if we can fix them. Since
metrics can change over time, I'm not expecting every failure (assuming
there are) to show up on the first fly-over.

If you connect external metrics via AgentX, make sure you include their
corresponding MIB files via `mib directory` in snmpd.conf, or manually
place them (and their dependencies) inside /usr/share/snmp/mibs.

How will a failure show up? A GENERR will be returned, and a a
"not MIB compliant" line will show up in the log, like:
AgentX(2948596552/1757229136): 780061712 OPENBSD-CARP-MIB::carpGroupDemote.1: not MIB compliant
After this, the AgentX connection is closed. Meaning that if the data
came from snmpd_metrics (spawned by snmpd itself), snmpd_metrics will
exit and take snmpd with it. If it's an external daemon it's dependent
on the daemon; vmd, and relayd should reconnect automatically, other
daemons might behave differently.

martijn@

diff refs/heads/master refs/heads/snmpd/mib_syntax
commit - 8c9ea0ff3fd3788b066415bd61a787c53e6c77b7
commit + 2e6836dc56c30b72cf51908e715184381a0337ee
blob - da9643f9aa602ae007006920d1f162624a0f39f2
blob + 27cd3bda1230b4823b718cdde34de1f91857bbc4
--- usr.sbin/snmpd/application.c
+++ usr.sbin/snmpd/application.c
@@ -1485,6 +1485,13 @@ appl_varbind_valid(struct appl_varbind *varbind,
 		}
 	}
 
+	if (varbind->av_value->be_class == BER_CLASS_UNIVERSAL ||
+	    varbind->av_value->be_class == BER_CLASS_APPLICATION || 0) {
+		if (!mib_varbind_valid(&varbind->av_oid, varbind->av_value)) {
+			*errstr = "not MIB compliant";
+			return 0;
+		}
+	}
 	if (varbind->av_value->be_class == BER_CLASS_UNIVERSAL) {
 		switch (varbind->av_value->be_type) {
 		case BER_TYPE_NULL:
blob - fc2a428828de5e9ca1761e25223fc7eeee165e2f
blob + 8c41d11fb48a3821be32387e91bf3b7e21161be6
--- usr.sbin/snmpd/mib.h
+++ usr.sbin/snmpd/mib.h
@@ -34,6 +34,7 @@ void		 mib_clear(void);
 char		*mib_oid2string(struct ber_oid *, char *, size_t,
 		    enum mib_oidfmt);
 const char	*mib_string2oid(const char *, struct ber_oid *);
+int		 mib_varbind_valid(struct ber_oid *, struct ber_element *);
 
 #define MIBDECL(...)		{ { MIB_##__VA_ARGS__ },		\
     (sizeof((uint32_t []) { MIB_##__VA_ARGS__ }) / sizeof(uint32_t))}, #__VA_ARGS__
blob - eb7a1a1272dbfe1ac06fc5b3aebbde9e3fb65b41
blob + a18d5ceebda51285765cc76ced6a803a3e95cf72
--- usr.sbin/snmpd/mib.y
+++ usr.sbin/snmpd/mib.y
@@ -35,6 +35,7 @@
 
 #include "log.h"
 #include "mib.h"
+#include "snmp.h"
 
 /* RFC2578 section 3.1 */
 #define DESCRIPTOR_MAX 64
@@ -71,6 +72,45 @@ struct oid_resolved {
 	size_t			 bo_n;
 };
 
+struct range {
+	long long		 start;
+	long long		 end;
+};
+
+struct ranges {
+	struct range		 range[20];
+	size_t			 nranges;
+};
+
+enum type {
+	TYPE_REFERENCE,
+	TYPE_INTEGER,
+	TYPE_OID,
+	TYPE_OSTRING,
+	TYPE_IPADDRESS,
+	TYPE_COUNTER32,
+	TYPE_GAUGE32,
+	TYPE_UNSIGNED32,
+	TYPE_TIMETICKS,
+	TYPE_OPAQUE,
+	TYPE_COUNTER64,
+	TYPE_BITS,
+	TYPE_ENUMERATED,
+	TYPE_SEQUENCEOF
+};
+
+struct syntax {
+	enum type		 type;
+	union {
+		char		 reference[DESCRIPTOR_MAX + 1];
+		struct item	*refitem;
+	};
+	union {
+		struct ranges	 ranges;
+		/* TODO support for enumerated */
+	};
+};
+
 enum status {
 	CURRENT,
 	DEPRECATED,
@@ -92,7 +132,7 @@ struct objectidentity {
 };
 
 struct objecttype {
-	void			*syntax;
+	struct syntax		 syntax;
 	char			*units;
 	enum access		 maxaccess;
 	enum status		 status;
@@ -114,7 +154,7 @@ struct textualconvention {
 	enum status		 status;
 	char			*description;
 	char			*reference;
-	void			*syntax;
+	struct syntax		 syntax;
 };
 
 struct item {
@@ -131,7 +171,8 @@ struct item {
 		IT_OBJECT_GROUP,
 		IT_NOTIFICATION_GROUP,
 		IT_MODULE_COMPLIANCE,
-		IT_AGENT_CAPABITIES
+		IT_AGENT_CAPABITIES,
+		IT_SEQUENCE
 	}			 type;
 	int			 resolved;
 	struct module		*module;
@@ -206,19 +247,20 @@ int		 mib_moduleidentity(const char *, time_t, const c
 		    const char *, const char *, const struct oid_unresolved *);
 int		 mib_objectidentity(const char *, enum status, const char *,
 		    const char *, const struct oid_unresolved *);
-int		 mib_objecttype(const char *, void  *, const char *,
+int		 mib_objecttype(const char *, struct syntax  *, const char *,
 		    enum access, enum status, const char *, const char *,
 		    void *, void *, const struct oid_unresolved *);
 int		 mib_notificationtype(const char *, void *, enum status,
 		    const char *, const char *, const struct oid_unresolved *);
 int		 mib_textualconvetion(const char *, const char *, enum status,
-		    const char *, const char *, void *);
+		    const char *, const char *, const struct syntax *);
 int		 mib_objectgroup(const char *, void *, enum status,
 		    const char *, const char *, const struct oid_unresolved *);
 int		 mib_notificationgroup(const char *, void *, enum status,
 		    const char *, const char *, const struct oid_unresolved *);
 int		 mib_modulecompliance(const char *, enum status, const char *,
 		    const char *, void *, const struct oid_unresolved *);
+int		 mib_item_sequence(const char *, void *);
 struct item	*mib_item_find(struct item *, const char *);
 struct item	*mib_item_parent(struct ber_oid *);
 int		  mib_resolve_oid(struct oid_resolved *,
@@ -272,6 +314,9 @@ typedef union {
 	char			 symbollist[SYMBOLS_MAX][DESCRIPTOR_MAX + 1];
 	struct objidcomponent	 objidcomponent;
 	struct oid_unresolved	 oid;
+	struct syntax		 syntax;
+	struct range		 range;
+	struct ranges		 ranges;
 	time_t			 time;
 	enum status		 status;
 	enum access		 access;
@@ -309,6 +354,7 @@ typedef union {
 %token	<number>	NUMBER
 %token	<signednumber>	SIGNEDNUMBER
 %type	<string>	moduleidentifier smiv2moduleidentifier
+%type	<signednumber>	value
 %type	<import>	symbolsfrom
 %type	<symbollist>	symbollist
 %type	<string>	descriptor symbol
@@ -318,6 +364,10 @@ typedef union {
 %type	<time>		lastupdated
 %type	<status>	status
 %type	<access>	access
+%type	<range>		range
+%type	<ranges>	ranges
+%type	<syntax>	syntax type integersubtype octetstringsubtype
+%type	<syntax>	enumeration bits
 
 %%
 
@@ -561,7 +611,7 @@ assignment		: descriptor OBJECT IDENTIFIER PRODUCTION
 				units = $5[0] == '\0' ? NULL : $5;
 				reference = $12[0] == '\0' ? NULL : $12;
 
-				if (mib_objecttype($1, NULL, units, $7, $9, $11,
+				if (mib_objecttype($1, &$4, units, $7, $9, $11,
 				    reference, NULL, NULL, &$16) == -1)
 					YYERROR;
 			}
@@ -577,6 +627,8 @@ assignment		: descriptor OBJECT IDENTIFIER PRODUCTION
 					YYERROR;
 			}
 			| typereference PRODUCTION SEQUENCE '{' entries '}' {
+				if (mib_item_sequence($1, NULL) == -1)
+					YYERROR;
 				/* Table entry, ignore for now */
 			}
 			| typereference PRODUCTION TEXTUALCONVENTION displaypart
@@ -587,7 +639,7 @@ assignment		: descriptor OBJECT IDENTIFIER PRODUCTION
 				reference = $9[0] == '\0' ? NULL : $9;
 
 				if (mib_textualconvetion($1, displayhint, $6,
-				    $8, reference, NULL) == -1)
+				    $8, reference, &$11) == -1)
 					YYERROR;
 			}
 			| descriptor MODULECOMPLIANCE STATUS status
@@ -647,12 +699,27 @@ objects			: object
 object			: descriptor
 			;
 
-syntax			: type
-			| SEQUENCE OF typereference
-			| integersubtype
-			| octetstringsubtype
-			| enumeration
-			| bits
+syntax			: type {
+				$$ = $1;
+			}
+			| SEQUENCE OF typereference {
+				$$ = (struct syntax){};
+				$$.type = TYPE_SEQUENCEOF;
+				strlcpy($$.reference, $3,
+				    sizeof($$.reference));
+			}
+			| integersubtype {
+				$$ = $1;
+			}
+			| octetstringsubtype {
+				$$ = $1;
+			}
+			| enumeration {
+				$$ = $1;
+			}
+			| bits {
+				$$ = $1;
+			}
 			;
 
 unitspart		: UNITS TEXT {
@@ -869,34 +936,132 @@ objidcomponent		: NUMBER {
 			}
 			;
 
-type			: typereference
-			| INTEGER
-			| OBJECT IDENTIFIER
-			| OCTET STRING
-			| Integer32
-			| IpAddress
-			| Counter32
-			| Gauge32
-			| Unsigned32
-			| TimeTicks
-			| Opaque
-			| Counter64
-			| BITS
+type			: typereference {
+				$$ = (struct syntax){};
+				$$.type = TYPE_REFERENCE;
+				strlcpy($$.reference, $1, sizeof($$.reference));
+			}
+			| INTEGER {
+				$$ = (struct syntax){
+					.type = TYPE_INTEGER
+				};
+			}
+			| OBJECT IDENTIFIER {
+				$$ = (struct syntax){
+					.type = TYPE_OID
+				};
+			}
+			| OCTET STRING {
+				$$ = (struct syntax){
+					.type = TYPE_OSTRING
+				};
+			}
+			| Integer32 {
+				$$ = (struct syntax){
+					.type = TYPE_INTEGER
+				};
+			}
+			| IpAddress {
+				$$ = (struct syntax){
+					.type = TYPE_IPADDRESS
+				};
+			}
+			| Counter32 {
+				$$ = (struct syntax){
+					.type = TYPE_COUNTER32
+				};
+			}
+			| Gauge32 {
+				$$ = (struct syntax){
+					.type = TYPE_GAUGE32
+				};
+			}
+			| Unsigned32 {
+				$$ = (struct syntax){
+					.type = TYPE_UNSIGNED32
+				};
+			}
+			| TimeTicks {
+				$$ = (struct syntax){
+					.type = TYPE_TIMETICKS
+				};
+			}
+			| Opaque {
+				$$ = (struct syntax){
+					.type = TYPE_OPAQUE
+				};
+			}
+			| Counter64 {
+				$$ = (struct syntax){
+					.type = TYPE_COUNTER64
+				};
+			}
+			| BITS {
+				$$ = (struct syntax){
+					.type = TYPE_BITS
+				};
+			}
 			;
 
-integersubtype		: INTEGER '(' ranges ')'
-			| Integer32 '(' ranges ')'
-			| Unsigned32 '(' ranges ')'
-			| Gauge32 '(' ranges ')'
-			| typereference '(' ranges ')'
+integersubtype		: INTEGER '(' ranges ')' {
+				$$ = (struct syntax){
+					.type = TYPE_INTEGER,
+					.ranges = $3
+				};
+			}
+			| Integer32 '(' ranges ')' {
+				$$ = (struct syntax){
+					.type = TYPE_INTEGER,
+					.ranges = $3
+				};
+			}
+			| Unsigned32 '(' ranges ')' {
+				$$ = (struct syntax){
+					.type = TYPE_UNSIGNED32,
+					.ranges = $3
+				};
+			}
+			| Gauge32 '(' ranges ')' {
+				$$ = (struct syntax){
+					.type = TYPE_GAUGE32,
+					.ranges = $3
+				};
+			}
+			| typereference '(' ranges ')' {
+				$$ = (struct syntax){
+					.type = TYPE_REFERENCE,
+					.ranges = $3
+				};
+				strlcpy($$.reference, $1, sizeof($$.reference));
+			}
 			;
 
-octetstringsubtype	: OCTET STRING '(' SIZE '(' ranges ')' ')'
-			| Opaque '(' SIZE '(' ranges ')' ')'
-			| typereference '(' SIZE '(' ranges ')' ')'
+octetstringsubtype	: OCTET STRING '(' SIZE '(' ranges ')' ')' {
+				$$ = (struct syntax) {
+					.type = TYPE_OSTRING,
+					.ranges = $6
+				};
+			}
+			| Opaque '(' SIZE '(' ranges ')' ')' {
+				$$ = (struct syntax) {
+					.type = TYPE_OPAQUE,
+					.ranges = $5
+				};
+			}
+			| typereference '(' SIZE '(' ranges ')' ')' {
+				$$ = (struct syntax) {
+					.type = TYPE_REFERENCE,
+					.ranges = $5
+				};
+				strlcpy($$.reference, $1, sizeof($$.reference));
+			}
 			;
 
-bits			: BITS '{' namedbits '}'
+bits			: BITS '{' namedbits '}' {
+				$$ = (struct syntax) {
+					.type = TYPE_BITS
+				};
+			}
 			;
 
 namedbits		: namedbit
@@ -906,8 +1071,17 @@ namedbits		: namedbit
 namedbit		: identifier '(' NUMBER ')'
 			;
 
-enumeration		: INTEGER '{' namednumbers '}'
-			| typereference '{' namednumbers '}'
+enumeration		: INTEGER '{' namednumbers '}' {
+				$$ = (struct syntax) {
+					.type = TYPE_ENUMERATED,
+				};
+			}
+			| typereference '{' namednumbers '}' {
+				$$ = (struct syntax) {
+					.type = TYPE_ENUMERATED
+				};
+				strlcpy($$.reference, $1, sizeof($$.reference));
+			}
 			;
 
 namednumbers		: namednumber
@@ -918,18 +1092,105 @@ namednumber		: identifier '(' NUMBER ')'
 			| identifier '(' SIGNEDNUMBER ')'
 			;
 
-ranges			: range '|' ranges
-			| range
+ranges			: ranges '|' range {
+				size_t i, j;
+				int inserted = 0;
+
+				$$.nranges = $1.nranges;
+				for (i = j = 0; i < $1.nranges; i++, j++) {
+					if (inserted) {
+						$$.range[i] = $1.range[j];
+						continue;
+					}
+					if (($3.start >= $1.range[j].start &&
+					    $3.start <= $1.range[j].end) ||
+					    ($3.end >= $1.range[j].start &&
+					    $3.end <= $1.range[j].end)) {
+						yyerror("Ranges can't overlap");
+						YYERROR;
+					}
+					if ($3.end == $1.range[j].start - 1) {
+						$$.range[i].start = $3.start;
+						$$.range[i].end =
+						    $1.range[j].end;
+						inserted = 1;
+						continue;
+					} else if (
+					    $3.start == $1.range[j].end + 1) {
+						$$.range[i].start =
+						    $1.range[j].start;
+						$$.range[i].end = $3.end;
+						inserted = 1;
+						continue;
+					} else if (
+					    $3.start < $1.range[j].start) {
+						if ($$.nranges ==
+						    nitems($$.range)) {
+							yyerror(
+							    "too many ranges");
+							YYERROR;
+						}
+						$$.range[i] = $3;
+						$$.nranges++;
+						i++;
+						inserted = 1;
+					}
+					$$.range[i] = $1.range[j];
+				}
+			}
+			| range {
+				$$.nranges = 1;
+				$$.range[0] = $1;
+			}
 			;
 
-range			: value
-			| value RANGESEPARATOR value
+range			: value {
+				$$.start = $1;
+				$$.end = $1;
+			}
+			| value RANGESEPARATOR value {
+				if ($1 >= $3) {
+					yyerror("invalid range: "
+					    "%lld not smaller than %lld",
+					    $1, $3);
+					YYERROR;
+				}
+				$$.start = $1;
+				$$.end = $3;
+			}
 			;
 
-value			: SIGNEDNUMBER
-			| NUMBER
-			| HSTRING
-			| BSTRING
+value			: SIGNEDNUMBER {
+				$$ = $1;
+			}
+			| NUMBER {
+				$$ = $1;
+			}
+			| HSTRING {
+				size_t i;
+				int val;
+				char digit;
+
+				$$ = 0;
+				for (i = 1; $1[i] != '\''; i++) {
+					$$ <<= 4;
+					digit = tolower($1[i]);
+					if (digit >= '0' && digit <= '9')
+						val = digit - '0';
+					else if (digit >= 'a' && digit <= 'f')
+						val = digit - 'a' + 10;
+					$$ |= val;
+				}
+			}
+			| BSTRING {
+				size_t i;
+
+				$$ = 0;
+				for (i = 1; $1[i] != '\''; i++) {
+					$$ <<= 1;
+					$$ |= $1[i] - '0';
+				}
+			}
 			;
 
 access			: descriptor {
@@ -1528,7 +1789,7 @@ mib_objectidentity(const char *name, enum status statu
 }
 
 int
-mib_objecttype(const char *name, void *syntax, const char *units,
+mib_objecttype(const char *name, struct syntax *syntax, const char *units,
     enum access maxaccess, enum status status, const char *description,
     const char *reference, void *index, void *defval,
     const struct oid_unresolved *oid)
@@ -1539,6 +1800,7 @@ mib_objecttype(const char *name, void *syntax, const c
 		return -1;
 
 	item->objecttype.maxaccess = maxaccess;
+	item->objecttype.syntax = *syntax;
 	item->objecttype.status = status;
 	if (mib_item_oid(item, oid) == -1)
 		return -1;
@@ -1564,13 +1826,18 @@ mib_notificationtype(const char *name, void *objects, 
 int
 mib_textualconvetion(const char *name, const char *displayhint,
     enum status status, const char *description, const char *reference,
-    void *syntax)
+    const struct syntax *syntax)
 {
 	struct item *item;
 
 	if ((item = mib_item(name, IT_TEXTUAL_CONVENTION)) == NULL)
 		return -1;
 	item->textualconvention.status = status;
+	if (syntax->reference[0] != '\0') {
+		yyerror("TEXTUAL-CONVENTION SYNTAX can't be typerefence");
+		return -1;
+	}
+	item->textualconvention.syntax = *syntax;
 	return 0;
 }
 
@@ -1619,6 +1886,16 @@ mib_modulecompliance(const char *name, enum status sta
 	return 0;
 }
 
+int
+mib_item_sequence(const char *name, void *entries)
+{
+	struct item *item;
+
+	if ((item = mib_item(name, IT_SEQUENCE)) == NULL)
+		return -1;
+	return 0;
+}
+
 void
 mib_parsefile(const char *path)
 {
@@ -1781,11 +2058,175 @@ mib_oid2string(struct ber_oid *oid, char *buf, size_t 
 	return buf;
 }
 
+int
+mib_varbind_valid(struct ber_oid *oid, struct ber_element *value)
+{
+	struct item *item;
+	struct syntax *syntax;
+	long long ival;
+	void *sval;
+	size_t i, slen;
+
+	item = mib_item_parent(oid);
+	if (item == NULL || item->type != IT_OBJECT_TYPE)
+		return 1;
+
+	if (item->objecttype.syntax.type == TYPE_REFERENCE) {
+		if (item->objecttype.syntax.refitem->type == IT_SEQUENCE)
+			return 1;
+		syntax =
+		    &item->objecttype.syntax.refitem->textualconvention.syntax;
+	} else
+		syntax = &item->objecttype.syntax;
+
+	switch (syntax->type) {
+	case TYPE_REFERENCE:
+		fatal("%s: can't happen", __func__);
+	case TYPE_INTEGER:
+	case TYPE_ENUMERATED:
+		if (value->be_class != BER_CLASS_UNIVERSAL ||
+		    value->be_type != BER_TYPE_INTEGER)
+			return 0;
+		if (ober_get_integer(value, &ival) == -1)
+			return 0;
+		if (ival < INT32_MIN || ival > INT32_MAX)
+			return 0;
+		for (i = 0; i < syntax->ranges.nranges; i++) {
+			if (ival >= syntax->ranges.range[i].start &&
+			    ival <= syntax->ranges.range[i].end)
+				break;
+		}
+		if (syntax->ranges.nranges > 0 && i == syntax->ranges.nranges)
+			return 0;
+		return 1;
+	case TYPE_OID:
+		if (value->be_class != BER_CLASS_UNIVERSAL ||
+		    value->be_type != BER_TYPE_OBJECT)
+			return 0;
+		return 1;
+	case TYPE_OSTRING:
+		if (value->be_class != BER_CLASS_UNIVERSAL ||
+		    value->be_type != BER_TYPE_OCTETSTRING)
+			return 0;
+		if (ober_get_nstring(value, &sval, &slen) == -1)
+			return 0;
+		if (slen > SMI_OCTETSTRING_MAX)
+			return 0;
+		for (i = 0; i < syntax->ranges.nranges; i++) {
+			if (slen >= (size_t)syntax->ranges.range[i].start &&
+			    slen <= (size_t)syntax->ranges.range[i].end)
+				break;
+		}
+		if (syntax->ranges.nranges > 0 && i == syntax->ranges.nranges)
+			return 0;
+		return 1;
+	case TYPE_IPADDRESS:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_IPADDR)
+			return 0;
+		if (ober_get_nstring(value, &sval, &slen) == -1)
+			return 0;
+		if (slen != 4)
+			return 0;
+		return 1;
+	case TYPE_COUNTER32:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_COUNTER32)
+			return 0;
+		if (ober_get_integer(value, &ival) == -1)
+			return 0;
+		if (ival < 0 || ival > UINT32_MAX)
+			return 0;
+		return 1;
+	case TYPE_GAUGE32:
+	case TYPE_UNSIGNED32:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_GAUGE32)
+			return 0;
+		if (ober_get_integer(value, &ival) == -1)
+			return 0;
+		if (ival < 0 || ival > UINT32_MAX)
+			return 0;
+		return 1;
+	case TYPE_TIMETICKS:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_TIMETICKS)
+			return 0;
+		if (ober_get_integer(value, &ival) == -1)
+			return 0;
+		if (ival < 0 || ival > UINT32_MAX)
+			return 0;
+		return 1;
+	case TYPE_OPAQUE:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_OPAQUE)
+			return 0;
+		if (ober_get_nstring(value, &sval, &slen) == -1)
+			return 0;
+		/* No size limit */
+		return 1;
+	case TYPE_COUNTER64:
+		if (value->be_class != BER_CLASS_APPLICATION ||
+		    value->be_type != SNMP_T_COUNTER64)
+			return 0;
+		if (ober_get_integer(value, &ival) == -1)
+			return 0;
+		/* 64 bits value, no range check */
+		return 1;
+	case TYPE_BITS:
+		if (value->be_class != BER_CLASS_UNIVERSAL ||
+		    value->be_type != BER_TYPE_OCTETSTRING)
+			return 0;
+		if (ober_get_nstring(value, &sval, &slen) == -1)
+			return 0;
+		/* No explicit limit, 128 recommended */
+		return 1;
+	case TYPE_SEQUENCEOF:
+		return 0;
+	}
+}
+
 void
 mib_defaults(void)
 {
 	struct oid_unresolved oid;
 	struct item *iso;
+	const struct syntax stdisplay = {
+		.type = TYPE_OSTRING,
+		.ranges.nranges = 1,
+		.ranges.range[0].start = 0,
+		.ranges.range[0].end = 255
+	}, stostring = {
+		.type = TYPE_OSTRING
+	}, stmacaddr = {
+		.type = TYPE_OSTRING,
+		.ranges.nranges = 1,
+		.ranges.range[0].start = 6,
+		.ranges.range[0].end =6
+	}, stenumerated = {
+		.type = TYPE_ENUMERATED
+	}, stmaxint = {
+		.type = TYPE_INTEGER,
+		.ranges.nranges = 1,
+		.ranges.range[0].start = 0,
+		.ranges.range[0].end = 2147483647
+	}, stoid = {
+		.type = TYPE_OID
+	}, sttimestamp = {
+		.type = TYPE_TIMETICKS
+	}, stdateandtime = {
+		.type = TYPE_OSTRING,
+		.ranges.nranges = 2,
+		.ranges.range[0].start = 8,
+		.ranges.range[0].end = 8,
+		.ranges.range[1].start = 11,
+		.ranges.range[1].end = 11
+	}, sttaddress = {
+		.type = TYPE_OSTRING,
+		.ranges.nranges = 1,
+		.ranges.range[0].start = 1,
+		.ranges.range[0].end = 255
+	};
 
 	if (!RB_EMPTY(&modulesci))
 		return;
@@ -1924,37 +2365,37 @@ mib_defaults(void)
 		exit(1);
 
 	if (mib_textualconvetion(
-	    "DisplayString", "255a", CURRENT, "", NULL, NULL) == -1 ||
+	    "DisplayString", "255a", CURRENT, "", NULL, &stdisplay) == -1 ||
 	    mib_textualconvetion(
-	    "PhysAddress", "1x:", CURRENT, "", NULL, NULL) == -1 ||
+	    "PhysAddress", "1x:", CURRENT, "", NULL, &stostring) == -1 ||
 	    mib_textualconvetion(
-	    "MacAddress", "1x:", CURRENT, "", NULL, NULL) == -1 ||
+	    "MacAddress", "1x:", CURRENT, "", NULL, &stmacaddr) == -1 ||
 	    mib_textualconvetion(
-	    "TruthValue", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "TruthValue", NULL, CURRENT, "", NULL, &stenumerated) == -1 ||
 	    mib_textualconvetion(
-	    "TestAndIncr", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "TestAndIncr", NULL, CURRENT, "", NULL, &stmaxint) == -1 ||
 	    mib_textualconvetion(
-	    "AutonomousType", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "AutonomousType", NULL, CURRENT, "", NULL, &stoid) == -1 ||
 	    mib_textualconvetion(
-	    "InstancePointer", NULL, OBSOLETE, "", NULL, NULL) == -1 ||
+	    "InstancePointer", NULL, OBSOLETE, "", NULL, &stoid) == -1 ||
 	    mib_textualconvetion(
-	    "VariablePointer", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "VariablePointer", NULL, CURRENT, "", NULL, &stoid) == -1 ||
 	    mib_textualconvetion(
-	    "RowPointer", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "RowPointer", NULL, CURRENT, "", NULL, &stoid) == -1 ||
 	    mib_textualconvetion(
-	    "RowStatus", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "RowStatus", NULL, CURRENT, "", NULL, &stenumerated) == -1 ||
 	    mib_textualconvetion(
-	    "TimeStamp", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "TimeStamp", NULL, CURRENT, "", NULL, &sttimestamp) == -1 ||
 	    mib_textualconvetion(
-	    "TimeInterval", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "TimeInterval", NULL, CURRENT, "", NULL, &stmaxint) == -1 ||
 	    mib_textualconvetion(
-	    "DateAndTime", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "DateAndTime", NULL, CURRENT, "", NULL, &stdateandtime) == -1 ||
 	    mib_textualconvetion(
-	    "StorageType", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "StorageType", NULL, CURRENT, "", NULL, &stenumerated) == -1 ||
 	    mib_textualconvetion(
-	    "TDomain", NULL, CURRENT, "", NULL, NULL) == -1 ||
+	    "TDomain", NULL, CURRENT, "", NULL, &stoid) == -1 ||
 	    mib_textualconvetion(
-	    "TAddress", NULL, CURRENT, "", NULL, NULL) == -1)
+	    "TAddress", NULL, CURRENT, "", NULL, &sttaddress) == -1)
 		exit(1);
 
 	RB_INSERT(modulesci, &modulesci, module);
@@ -2104,7 +2545,7 @@ mib_resolve_oid(struct oid_resolved *dst, struct oid_u
 int
 mib_resolve_item(struct item *item)
 {
-	struct item *prev;
+	struct item *prev, *typeref;
 	struct oid_resolved oid;
 
 	if (item->resolved)
@@ -2114,7 +2555,8 @@ mib_resolve_item(struct item *item)
 
 	if (item->type == IT_MACRO ||
 	    item->type == IT_APPLICATIONSYNTAX ||
-	    item->type == IT_TEXTUAL_CONVENTION)
+	    item->type == IT_TEXTUAL_CONVENTION ||
+	    item->type == IT_SEQUENCE)
 		return 0;
 
 	if (mib_resolve_oid(&oid, item->oid_unresolved, item) == -1)
@@ -2122,6 +2564,29 @@ mib_resolve_item(struct item *item)
 	free(item->oid_unresolved);
 	item->oid = oid;
 
+	if (item->type == IT_OBJECT_TYPE) {
+		if (item->objecttype.syntax.reference[0] != '\0') {
+			if ((typeref = mib_item_find(
+			    item, item->objecttype.syntax.reference)) == NULL) {
+				log_warnx("%s::%s: OBJECT-TYPE SYNTAX "
+				    "reference %s not found",
+				    item->module->name, item->name,
+				    item->objecttype.syntax.reference);
+				return -1;
+			}
+			if (typeref->type != IT_TEXTUAL_CONVENTION &&
+			    typeref->type != IT_SEQUENCE) {
+				log_warnx("%s::%s: OBJECT-TYPE SYNTAX "
+				    "reference %s not TEXTUAL-CONVENTION",
+				    item->module->name, item->name,
+				    item->objecttype.syntax.reference);
+				return -1;
+			}
+			item->objecttype.syntax.refitem = typeref;
+		} else
+			item->objecttype.syntax.refitem = NULL;
+	}
+
 	if ((prev = RB_INSERT(items, &items, item)) != NULL) {
 		/* Prioritize an OID derived from a MACRO over a plain OID */
 		if (prev->type == IT_OID && item->type != IT_OID) {