diff options
author | Martijn van Duren <martijn@cvs.openbsd.org> | 2024-01-27 09:54:00 +0000 |
---|---|---|
committer | Martijn van Duren <martijn@cvs.openbsd.org> | 2024-01-27 09:54:00 +0000 |
commit | b557fd8deed1515eee1d65b1243882d4611c3234 (patch) | |
tree | 50da899dfced046b89fc8c9e23bbb34642962027 /usr.sbin/snmpd | |
parent | 6ff78d322d2213cf69bf19ddf74934a589d71971 (diff) |
Implement an initial SMIv2 parser based around RFC257[89]. RFC2580 isn't
supported yet. SMIv1 is not supported. Parsing is done in a strict
manner, but except for the (deprecated) IPV6-TC MIB everything from
IETF/IANA that I found parses.
For now this code will be used OID<->name translations, but other
functionality could be added in the future.
This commit just includes the parser, usage and including the MIB files
will be done in separate commits.
Go ahead from tb@
Diffstat (limited to 'usr.sbin/snmpd')
-rw-r--r-- | usr.sbin/snmpd/Makefile | 8 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.h | 18 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.y | 2260 |
3 files changed, 2283 insertions, 3 deletions
diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile index dfb72716902..9eb74e5dd58 100644 --- a/usr.sbin/snmpd/Makefile +++ b/usr.sbin/snmpd/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.25 2023/11/12 16:07:34 martijn Exp $ +# $OpenBSD: Makefile,v 1.26 2024/01/27 09:53:59 martijn Exp $ PROG= snmpd MAN= snmpd.8 snmpd.conf.5 -SRCS= parse.y log.c snmpe.c application.c application_blocklist.c \ +SRCS= mib.y parse.y log.c snmpe.c application.c application_blocklist.c \ application_internal.c application_agentx.c ax.c \ trap.c smi.c snmpd.c \ proc.c usm.c traphandler.c util.c @@ -16,4 +16,8 @@ CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+= -Wsign-compare YFLAGS= +mib.c: mib.y + ${YACC.y} -pmib -o ${.TARGET} ${.IMPSRC} + + .include <bsd.prog.mk> diff --git a/usr.sbin/snmpd/mib.h b/usr.sbin/snmpd/mib.h index 06774a9080c..fc2a428828d 100644 --- a/usr.sbin/snmpd/mib.h +++ b/usr.sbin/snmpd/mib.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mib.h,v 1.43 2023/12/21 13:54:05 martijn Exp $ */ +/* $OpenBSD: mib.h,v 1.44 2024/01/27 09:53:59 martijn Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org> @@ -19,6 +19,22 @@ #ifndef SNMPD_MIB_H #define SNMPD_MIB_H +#include <stddef.h> + +struct ber_oid; +enum mib_oidfmt { + MIB_OIDNUMERIC, + MIB_OIDSYMBOLIC +}; + +void mib_parsefile(const char *); +void mib_parsedir(const char *); +void mib_resolve(void); +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 *); + #define MIBDECL(...) { { MIB_##__VA_ARGS__ }, \ (sizeof((uint32_t []) { MIB_##__VA_ARGS__ }) / sizeof(uint32_t))}, #__VA_ARGS__ #define MIBEND { { 0 } }, NULL diff --git a/usr.sbin/snmpd/mib.y b/usr.sbin/snmpd/mib.y new file mode 100644 index 00000000000..5e18dbecc3b --- /dev/null +++ b/usr.sbin/snmpd/mib.y @@ -0,0 +1,2260 @@ +/* $OpenBSD: mib.y,v 1.1 2024/01/27 09:53:59 martijn Exp $ */ + +/* + * Copyright (c) 2023 Martijn van Duren <martijn@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/tree.h> + +#include <assert.h> +#include <ber.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> +#include <time.h> + +#include "log.h" +#include "mib.h" + +/* RFC2578 section 3.1 */ +#define DESCRIPTOR_MAX 64 + +/* Values from real life testing, could be adjusted */ +#define ITEM_MAX DESCRIPTOR_MAX +#define MODULENAME_MAX 64 +#define SYMBOLS_MAX 256 +#define IMPORTS_MAX 16 +#define TEXT_MAX 16384 + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +struct objidcomponent { + enum { + OCT_DESCRIPTOR, + OCT_NUMBER, + OCT_NAMEANDNUMBER + } type; + uint32_t number; + char name[DESCRIPTOR_MAX + 1]; +}; + +struct oid_unresolved { + /* Unusual to have long lists of unresolved components */ + struct objidcomponent bo_id[16]; + size_t bo_n; +}; + +struct oid_resolved { + uint32_t *bo_id; + size_t bo_n; +}; + +enum status { + CURRENT, + DEPRECATED, + OBSOLETE +}; + +enum access { + NOTACCESSIBLE, + ACCESSIBLEFORNOTIFY, + READONLY, + READWRITE, + READCREATE +}; + +struct objectidentity { + enum status status; + char *description; + char *reference; +}; + +struct objecttype { + void *syntax; + char *units; + enum access maxaccess; + enum status status; + char *description; + char *reference; + void *index; + void *defval; +}; + +struct notificationtype { + void *objects; + enum status status; + char *description; + char *reference; +}; + +struct textualconvention { + char *displayhint; + enum status status; + char *description; + char *reference; + void *syntax; +}; + +struct item { + char name[DESCRIPTOR_MAX + 1]; + enum item_type { + IT_OID, + IT_MACRO, + IT_MODULE_IDENTITY, + IT_OBJECT_IDENTITY, + IT_APPLICATIONSYNTAX, + IT_OBJECT_TYPE, + IT_NOTIFICATION_TYPE, + IT_TEXTUAL_CONVENTION, + IT_OBJECT_GROUP, + IT_NOTIFICATION_GROUP, + IT_MODULE_COMPLIANCE, + IT_AGENT_CAPABITIES + } type; + int resolved; + struct module *module; + + union { + struct oid_unresolved *oid_unresolved; + struct oid_resolved oid; + }; + + union { + struct objectidentity objectidentity; + struct objecttype objecttype; + struct notificationtype notificationtype; + struct textualconvention textualconvention; + }; + + /* Global case insensitive */ + RB_ENTRY(item) entrygci; + /* Module case insensitive */ + RB_ENTRY(item) entryci; + /* Module case sensitive */ + RB_ENTRY(item) entrycs; + /* Global oid */ + RB_ENTRY(item) entry; +}; + +struct import_symbol { + char name[DESCRIPTOR_MAX + 1]; + struct item *item; +}; + +struct import { + char name[MODULENAME_MAX + 1]; + struct module *module; + struct import_symbol symbols[SYMBOLS_MAX]; + size_t nsymbols; +}; + +static struct module { + char name[MODULENAME_MAX + 1]; + int8_t resolved; + + time_t lastupdated; + + struct import *imports; + + RB_HEAD(itemscs, item) itemscs; + RB_HEAD(itemsci, item) itemsci; + + RB_ENTRY(module) entryci; + RB_ENTRY(module) entrycs; +} *module; + +int yylex(void); +static void yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void mib_defaults(void); +void mib_modulefree(struct module *); +int mib_imports_add(char *, char **); +int mib_oid_append(struct oid_unresolved *, + const struct objidcomponent *); +int mib_macro(const char *); +int mib_oid_concat(struct oid_unresolved *, + const struct oid_unresolved *); +struct item *mib_item(const char *, enum item_type); +int mib_item_oid(struct item *, + const struct oid_unresolved *); +int mib_macro(const char *); +int mib_applicationsyntax(const char *); +struct item *mib_oid(const char *, const struct oid_unresolved *); +int mib_moduleidentity(const char *, time_t, const char *, + 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 *, + 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 *); +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 *); +struct item *mib_item_find(struct item *, const char *); +struct item *mib_item_parent(struct ber_oid *); +int mib_resolve_oid(struct oid_resolved *, + struct oid_unresolved *, struct item *); +int mib_resolve_item(struct item *); +int mib_resolve_module(struct module *); +int module_cmp_cs(struct module *, struct module *); +int module_cmp_ci(struct module *, struct module *); +int item_cmp_cs(struct item *, struct item *); +int item_cmp_ci(struct item *, struct item *); +int item_cmp_oid(struct item *, struct item *); + +RB_HEAD(modulesci, module) modulesci = RB_INITIALIZER(&modulesci); +RB_HEAD(modulescs, module) modulescs = RB_INITIALIZER(&modulescs); +RB_HEAD(items, item) items = RB_INITIALIZER(&items); +RB_HEAD(itemsgci, item) itemsci = RB_INITIALIZER(&itemsci); +/* + * Use case sensitive matching internally (for resolving IMPORTS) and + * case sensitive matching, followed by case insensitive matching + * for end-user resolving (e.g. mib_string2oid()). + * It shouldn't happen there's case-based overlap in module/item names, + * but allow all to be resolved in case there is. + */ +RB_PROTOTYPE_STATIC(modulesci, module, entryci, module_cmp_ci); +RB_PROTOTYPE_STATIC(modulescs, module, entrycs, module_cmp_cs); +/* + * mib_string2oid() should match case insensitive on: + * <module>::<descriptor> + * <descriptor> + */ +RB_PROTOTYPE_STATIC(itemsgci, item, entrygci, item_cmp_ci); +RB_PROTOTYPE_STATIC(itemsci, item, entryci, item_cmp_ci); +RB_PROTOTYPE_STATIC(itemscs, item, entrycs, item_cmp_cs); +RB_PROTOTYPE_STATIC(items, item, entry, item_cmp_oid); + +struct file { + FILE *stream; + const char *name; + size_t lineno; + enum { + FILE_UNDEFINED, + FILE_ASN1, + FILE_SMI2 + } state; +} file; + +typedef union { + char string[TEXT_MAX]; + unsigned long long number; + long long signednumber; + char symbollist[SYMBOLS_MAX][DESCRIPTOR_MAX + 1]; + struct objidcomponent objidcomponent; + struct oid_unresolved oid; + time_t time; + enum status status; + enum access access; +} YYSTYPE; + +%} + +%token ERROR +%token HSTRING BSTRING + +/* RFC2578 section 3.7 */ +%token ABSENT ACCESS AGENTCAPABILITIES ANY APPLICATION AUGMENTS BEGIN +%token BIT BITS BOOLEAN BY CHOICE COMPONENT COMPONENTS CONTACTINFO +%token CREATIONREQUIRES Counter32 Counter64 DEFAULT DEFINED +%token DEFINITIONS DEFVAL DESCRIPTION DISPLAYHINT END ENUMERATED +%token ENTERPRISE EXPLICIT EXPORTS EXTERNAL FALSE FROM GROUP Gauge32 +%token IDENTIFIER IMPLICIT IMPLIED IMPORTS INCLUDES INDEX INTEGER +%token Integer32 IpAddress LASTUPDATED MANDATORYGROUPS MAX MAXACCESS +%token MIN MINACCESS MINUSINFINITY MODULE MODULECOMPLIANCE MODULEIDENTITY +%token NOTIFICATIONGROUP NOTIFICATIONTYPE NOTIFICATIONS ASNNULL +%token OBJECT OBJECTGROUP OBJECTIDENTITY OBJECTTYPE OBJECTS OCTET OF +%token OPTIONAL ORGANIZATION Opaque PLUSINFINITY PRESENT PRIVATE +%token PRODUCTRELEASE REAL REFERENCE REVISION SEQUENCE SET SIZE STATUS +%token STRING SUPPORTS SYNTAX TAGS TEXTUALCONVENTION TRAPTYPE TRUE +%token TimeTicks UNITS UNIVERSAL Unsigned32 VARIABLES VARIATION WITH +%token WRITESYNTAX + +/* SMIv2 */ +%token SNMPv2SMI SNMPv2CONF SNMPv2TC + +/* X.208 */ +%token PRODUCTION RANGESEPARATOR + +%token <string> typereference identifier TEXT HSTRING BSTRING +%token <number> NUMBER +%token <signednumber> SIGNEDNUMBER +%type <string> moduleidentifier smiv2moduleidentifier +%type <import> symbolsfrom +%type <symbollist> symbollist +%type <string> descriptor symbol +%type <objidcomponent>objidcomponentfirst objidcomponent +%type <oid> objidcomponentlist objectidentifiervalue +%type <string> displaypart referpart unitspart +%type <time> lastupdated +%type <status> status +%type <access> access + +%% + +grammar : /* empty */ + | grammar module + ; + +module : moduleidentifier DEFINITIONS PRODUCTION BEGIN { + file.state = FILE_ASN1; + module = calloc(1, sizeof(*module)); + if (module == NULL) { + yyerror("malloc"); + YYERROR; + } + RB_INIT(&module->itemscs); + RB_INIT(&module->itemsci); + module->resolved = 0; + if (strlcpy(module->name, $1, + sizeof(module->name)) >= + sizeof(module->name)) { + yyerror("module name too long"); + free(module); + YYERROR; + } + } imports moduleidentity modulebody END { + struct module *mprev; + + if ((mprev = RB_INSERT(modulescs, &modulescs, + module)) != NULL) { + if (module->lastupdated > + mprev->lastupdated) { + mib_modulefree(mprev); + RB_INSERT(modulescs, &modulescs, + module); + RB_INSERT(modulesci, &modulesci, + module); + } else + mib_modulefree(module); + } else + RB_INSERT(modulesci, &modulesci, module); + module = NULL; + } + | smiv2moduleidentifier { + log_debug("%s: SMIv2 definitions: skipping", + file.name); + YYACCEPT; + } + ; + +moduleidentifier : typereference { strlcpy($$, $1, sizeof($$)); } + ; + +smiv2moduleidentifier : SNMPv2SMI { strlcpy($$, "SNMPv2-SMI", sizeof($$)); } + | SNMPv2CONF { strlcpy($$, "SNMPv2-CONF", sizeof($$)); } + | SNMPv2TC { strlcpy($$, "SNMPv2-TC", sizeof($$)); } + ; + +modulebody : assignmentlist + ; + +imports : IMPORTS importlist ';' + ; + +importlist : importlist symbolsfrom + | /* start */ + ; + +symbolsfrom : symbollist FROM moduleidentifier { + size_t i; + char *symbols[SYMBOLS_MAX]; + + for (i = 0; $1[i][0] != '\0'; i++) + symbols[i] = $1[i]; + symbols[i] = NULL; + + if (mib_imports_add($3, symbols) == -1) + YYERROR; + } + | symbollist FROM smiv2moduleidentifier { + size_t i; + char *symbols[SYMBOLS_MAX]; + + for (i = 0; $1[i][0] != '\0'; i++) + symbols[i] = $1[i]; + symbols[i] = NULL; + + if (mib_imports_add($3, symbols) == -1) + YYERROR; + + file.state = FILE_SMI2; + } + ; + +symbollist : symbollist ',' symbol { + size_t i; + for (i = 0; $1[i][0] != '\0'; i++) + strlcpy($$[i], $1[i], sizeof($$[i])); + if (i + 1 == nitems($$)) { + yyerror("too many symbols from module"); + YYERROR; + } + if (strlcpy($$[i], $3, sizeof($$[i])) >= + sizeof($$[i])) { + yyerror("symbol too long"); + YYERROR; + } + $$[i + 1][0] = '\0'; + } + | symbol { + if (strlcpy($$[0], $1, sizeof($$[0])) >= + sizeof($$[0])) { + yyerror("symbol too long"); + YYERROR; + } + $$[1][0] = '\0'; + } + ; + +symbol : typereference { strlcpy($$, $1, sizeof($$)); } + | descriptor { strlcpy($$, $1, sizeof($$)); } + /* SNMPv2-SMI */ + | MODULEIDENTITY { + strlcpy($$, "MODULE-IDENTITY", sizeof($$)); + } + | OBJECTIDENTITY { + strlcpy($$, "OBJECT-IDENTITY", sizeof($$)); + } + | Integer32 { strlcpy($$, "Integer32", sizeof($$)); } + | IpAddress { strlcpy($$, "IpAddress", sizeof($$)); } + | Counter32 { strlcpy($$, "Counter32", sizeof($$)); } + | Gauge32 { strlcpy($$, "Gauge32", sizeof($$)); } + | Unsigned32 { strlcpy($$, "Unsigned32", sizeof($$)); } + | TimeTicks { strlcpy($$, "TimeTicks", sizeof($$)); } + | Opaque { strlcpy($$, "Opaque", sizeof($$)); } + | Counter64 { strlcpy($$, "Counter64", sizeof($$)); } + | OBJECTTYPE { strlcpy($$, "OBJECT-TYPE", sizeof($$)); } + | NOTIFICATIONTYPE { + strlcpy($$, "NOTIFICATION-TYPE", sizeof($$)); + } + /* SNMPv2-TC */ + | TEXTUALCONVENTION { + strlcpy($$, "TEXTUAL-CONVENTION", sizeof($$)); + } + /* SNMPv2-CONF */ + | OBJECTGROUP { + strlcpy($$, "OBJECT-GROUP", sizeof($$)); + } + | NOTIFICATIONGROUP { + strlcpy($$, "NOTIFICATION-GROUP", sizeof($$)); + } + | MODULECOMPLIANCE { + strlcpy($$, "MODULE-COMPLIANCE", sizeof($$)); + } + | AGENTCAPABILITIES { + strlcpy($$, "AGENT-CAPABILITIES", sizeof($$)); + } + ; + +descriptor : identifier { + if (strlen($1) > DESCRIPTOR_MAX) { + yyerror("descriptor too long"); + YYERROR; + } + strlcpy($$, $1, sizeof($$)); + } + ; + +moduleidentity : descriptor MODULEIDENTITY lastupdated + ORGANIZATION TEXT CONTACTINFO TEXT DESCRIPTION TEXT + revisionpart PRODUCTION objectidentifiervalue { + if (mib_moduleidentity( + $1, $3, $5, $7, $9, &$12) == -1) + YYERROR; + } + ; + +lastupdated : LASTUPDATED TEXT { + char timebuf[14] = ""; + struct tm tm; + size_t len; + + if ((len = strlen($2)) == 11) + snprintf(timebuf, sizeof(timebuf), + "19%s", $2); + else if (len == 13) + strlcpy(timebuf, $2, sizeof(timebuf)); + else { + yyerror("Invalid LAST-UPDATED"); + YYERROR; + } + + if (strptime(timebuf, "%Y%M%d%H%MZ", &tm) == NULL) { + yyerror("Invalid LAST-UPDATED: %s", $2); + YYERROR; + } + + if (($$ = mktime(&tm)) == -1) { + yyerror("Invalid LAST-UPDATED: %s", $2); + YYERROR; + } + } + ; + +revisionpart : revisions + | /* empty */ + ; + +revisions : revision + | revisions revision + ; + +revision : REVISION TEXT DESCRIPTION TEXT + ; + +assignmentlist : assignment assignmentlist + | /* empty */ + ; + +assignment : descriptor OBJECT IDENTIFIER PRODUCTION + objectidentifiervalue { + if (mib_oid($1, &$5) == NULL) + YYERROR; + } + | descriptor OBJECTIDENTITY STATUS status + DESCRIPTION TEXT referpart PRODUCTION + objectidentifiervalue { + const char *reference; + + reference = $7[0] == '\0' ? NULL : $7; + + if (mib_objectidentity($1, $4, $6, reference, + &$9) == -1) + YYERROR; + } + | descriptor OBJECTTYPE SYNTAX syntax unitspart + MAXACCESS access STATUS status DESCRIPTION TEXT + referpart indexpart defvalpart PRODUCTION + objectidentifiervalue { + const char *units, *reference; + + units = $5[0] == '\0' ? NULL : $5; + reference = $12[0] == '\0' ? NULL : $12; + + if (mib_objecttype($1, NULL, units, $7, $9, $11, + reference, NULL, NULL, &$16) == -1) + YYERROR; + } + | descriptor NOTIFICATIONTYPE objectspart STATUS status + DESCRIPTION TEXT referpart PRODUCTION + objectidentifiervalue { + const char *reference; + + reference = $8[0] == '\0' ? NULL : $8; + + if (mib_notificationtype($1, NULL, $5, $7, + reference, &$10) == -1) + YYERROR; + } + | typereference PRODUCTION SEQUENCE '{' entries '}' { + /* Table entry, ignore for now */ + } + | typereference PRODUCTION TEXTUALCONVENTION displaypart + STATUS status DESCRIPTION TEXT referpart SYNTAX syntax { + const char *displayhint, *reference; + + displayhint = $4[0] == '\0' ? NULL : $4; + reference = $9[0] == '\0' ? NULL : $9; + + if (mib_textualconvetion($1, displayhint, $6, + $8, reference, NULL) == -1) + YYERROR; + } + | descriptor MODULECOMPLIANCE STATUS status + DESCRIPTION TEXT referpart compliancemodulepart + PRODUCTION objectidentifiervalue { + const char *reference; + + reference = $7[0] == '\0' ? NULL : $7; + + if (mib_modulecompliance($1, $4, $6, reference, + NULL, &$10) == -1) + YYERROR; + } + | descriptor OBJECTGROUP objectspart STATUS status + DESCRIPTION TEXT referpart PRODUCTION + objectidentifiervalue { + const char *reference; + + reference = $8[0] == '\0' ? NULL : $8; + + if (mib_objectgroup($1, NULL, $5, $7, reference, + &$10) == -1) + YYERROR; + } + | descriptor NOTIFICATIONGROUP notificationspart + STATUS status DESCRIPTION TEXT referpart PRODUCTION + objectidentifiervalue { + const char *reference; + + reference = $8[0] == '\0' ? NULL : $8; + + if (mib_notificationgroup($1, NULL, $5, $7, reference, + &$10) == -1) + YYERROR; + } + | typereference PRODUCTION syntax + ; + +notificationspart : NOTIFICATIONS '{' notifications '}' + ; + +notifications : notification + | notification ',' notifications + ; + +notification : descriptor + ; + +objectspart : OBJECTS '{' objects '}' + | /* empty */ + ; + +objects : object + | objects ',' object + ; + +object : descriptor + ; + +syntax : type + | SEQUENCE OF typereference + | integersubtype + | octetstringsubtype + | enumeration + | bits + ; + +unitspart : UNITS TEXT { + strlcpy($$, $2, sizeof($$)); + } + | /* empty */ { + $$[0] = '\0'; + } + ; + +referpart : REFERENCE TEXT { + strlcpy($$, $2, sizeof($$)); + } + | /* empty */ { + $$[0] = '\0'; + } + ; + +indexpart : INDEX '{' indextypes '}' + | AUGMENTS '{' descriptor '}' + | /* empty */ + ; + +indextypes : indextype + | indextypes ',' indextype + ; + +indextype : IMPLIED index + | index + ; + +index : descriptor + ; + +defvalpart : DEFVAL '{' defvalue '}' + | /* empty */ + ; + +defvalue : descriptor + | NUMBER + | SIGNEDNUMBER + | HSTRING + | TEXT + | bitsvalue + | objectidentifiervalue + ; + +/* single-value and empty are covered by objectidentifiervalue */ +bitsvalue : '{' bitscomponentlist '}' + ; + +bitscomponentlist : bitscomponentlist ',' bitscomponent + | bitscomponent + | /* empty */ + ; + +bitscomponent : identifier + ; + +displaypart : DISPLAYHINT TEXT { + strlcpy($$, $2, sizeof($$)); + } + | /* empty */ { + $$[0] = '\0'; + } + ; + +entries : entry ',' entries + | entry + ; + +entry : descriptor syntax + ; + +compliancemodulepart : compliancemodules + ; + +compliancemodules : compliancemodule + | compliancemodules compliancemodule + ; + +compliancemodule : MODULE modulename modulemandatorypart + modulecompliancepart + ; + +modulename : moduleidentifier + | /* empty */ + ; + +modulemandatorypart : MANDATORYGROUPS '{' modulegroups '}' + | /* empty */ + ; + +modulegroups : modulegroup + | modulegroups ',' modulegroup + ; + +modulegroup : objectidentifiervalue + | descriptor + ; + +modulecompliancepart : modulecompliances + | /* empty */ + ; + +modulecompliances : modulecompliance + | modulecompliances modulecompliance + ; + +modulecompliance : modulecompliancegroup + | moduleobject + ; + +modulecompliancegroup : GROUP modulegroupobjectname DESCRIPTION TEXT + ; + +moduleobject : OBJECT moduleobjectname modulesyntaxpart + writesyntaxpart moduleaccesspart DESCRIPTION TEXT + ; + +moduleobjectname : objectidentifiervalue + | descriptor + ; + +modulegroupobjectname : objectidentifiervalue + | descriptor + ; + +modulesyntaxpart : SYNTAX syntax + | /* empty */ + ; + +writesyntaxpart : WRITESYNTAX syntax + | /* empty */ + ; + +moduleaccesspart : MINACCESS access + | /* empty */ + ; + +/* + * An OBJECT IDENTIFIER needs at least 2 components and are needed to + * distinguish from BITS, which can have 0 elements and only differentiate by a + * comma separator. + */ +objectidentifiervalue : '{' objidcomponentfirst objidcomponent + objidcomponentlist '}' { + $$.bo_n = 0; + + if ($3.type == OCT_DESCRIPTOR) { + yyerror("only first component of " + "OBJECT IDENTIFIER can be a " + "descriptor"); + YYERROR; + } + if (mib_oid_append(&$$, &$2) == -1) + YYERROR; + if (mib_oid_append(&$$, &$3) == -1) + YYERROR; + if (mib_oid_concat(&$$, &$4) == -1) + YYERROR; + } + ; + +objidcomponentlist : objidcomponent objidcomponentlist { + if ($1.type == OCT_DESCRIPTOR) { + yyerror("only first component of " + "OBJECT IDENTIFIER can be a " + "descriptor"); + YYERROR; + } + + $$.bo_n = 0; + if (mib_oid_append(&$$, &$1) == -1) + YYERROR; + if (mib_oid_concat(&$$, &$2) == -1) + YYERROR; + } + | /* empty */ { + $$.bo_n = 0; + } + ; + +objidcomponentfirst : descriptor { + $$.type = OCT_DESCRIPTOR; + strlcpy($$.name, $1, sizeof($$.name)); + } + | objidcomponent { + $$.type = $1.type; + $$.number = $1.number; + strlcpy($$.name, $1.name, sizeof($$.name)); + } + ; + +objidcomponent : NUMBER { + $$.type = OCT_NUMBER; + if ($1 > UINT32_MAX) { + yyerror("OBJECT IDENTIFIER number " + "too large"); + YYERROR; + } + $$.number = $1; + $$.name[0] = '\0'; + } + | descriptor '(' NUMBER ')' { + $$.type = OCT_NAMEANDNUMBER; + if ($3 > UINT32_MAX) { + yyerror("OBJECT IDENTIFIER number " + "too large"); + YYERROR; + } + $$.number = $3; + strlcpy($$.name, $1, sizeof($$.name)); + } + ; + +type : typereference + | INTEGER + | OBJECT IDENTIFIER + | OCTET STRING + | Integer32 + | IpAddress + | Counter32 + | Gauge32 + | Unsigned32 + | TimeTicks + | Opaque + | Counter64 + | BITS + ; + +integersubtype : INTEGER '(' ranges ')' + | Integer32 '(' ranges ')' + | Unsigned32 '(' ranges ')' + | Gauge32 '(' ranges ')' + | typereference '(' ranges ')' + ; + +octetstringsubtype : OCTET STRING '(' SIZE '(' ranges ')' ')' + | Opaque '(' SIZE '(' ranges ')' ')' + | typereference '(' SIZE '(' ranges ')' ')' + ; + +bits : BITS '{' namedbits '}' + ; + +namedbits : namedbit + | namedbits ',' namedbit + ; + +namedbit : identifier '(' NUMBER ')' + ; + +enumeration : INTEGER '{' namednumbers '}' + | typereference '{' namednumbers '}' + ; + +namednumbers : namednumber + | namednumbers ',' namednumber + ; + +namednumber : identifier '(' NUMBER ')' + | identifier '(' SIGNEDNUMBER ')' + ; + +ranges : range '|' ranges + | range + ; + +range : value + | value RANGESEPARATOR value + ; + +value : SIGNEDNUMBER + | NUMBER + | HSTRING + | BSTRING + ; + +access : descriptor { + if (strcmp($1, "not-accessible") == 0) + $$ = NOTACCESSIBLE; + else if ( + strcmp($1, "accessible-for-notify") == 0) + $$ = ACCESSIBLEFORNOTIFY; + else if (strcmp($1, "read-only") == 0) + $$ = READONLY; + else if (strcmp($1, "read-write") == 0) + $$ = READWRITE; + else if (strcmp($1, "read-create") == 0) + $$ = READCREATE; + else { + yyerror("invalid access"); + YYERROR; + } + } + ; + +status : descriptor { + if (strcmp($1, "current") == 0) + $$ = CURRENT; + else if (strcmp($1, "deprecated") == 0) + $$ = DEPRECATED; + else if (strcmp($1, "obsolete") == 0) + $$ = OBSOLETE; + else { + yyerror("invalid status"); + YYERROR; + } + } + ; +%% + +void +yyerror(const char *fmt, ...) +{ + va_list ap; + char msg[1024] = ""; + + if (file.state == FILE_UNDEFINED) { + log_debug("%s: not an ASN.1 file: skipping", file.name); + return; + } + if (file.state == FILE_ASN1) { + if (strcmp(fmt, "syntax error") == 0) { + log_debug("%s: not an SMIv2 file: skipping", file.name); + return; + } + } + if (fmt != NULL) { + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + } + log_warnx("%s:%zu: %s", file.name, file.lineno, msg); +} + +/* + * X.208 + */ +int +yylex(void) +{ + const struct { + const char *name; + int token; + } keywords[] = { + /* RFC2578 section 3.7 */ + { "ABSENT", ABSENT }, + { "ACCESS", ACCESS }, + { "AGENT-CAPABILITIES", AGENTCAPABILITIES }, + { "ANY", ANY }, + { "APPLICATION", APPLICATION }, + { "AUGMENTS", AUGMENTS }, + { "BEGIN", BEGIN }, + { "BIT", BIT }, + { "BITS", BITS }, + { "BOOLEAN", BOOLEAN }, + { "BY", BY }, + { "CHOICE", CHOICE }, + { "COMPONENT", COMPONENT }, + { "COMPONENTS", COMPONENTS }, + { "CONTACT-INFO", CONTACTINFO }, + { "CREATION-REQUIRES", CREATIONREQUIRES }, + { "Counter32", Counter32 }, + { "Counter64", Counter64 }, + { "DEFAULT", DEFAULT }, + { "DEFINED", DEFINED }, + { "DEFINITIONS", DEFINITIONS }, + { "DEFVAL", DEFVAL }, + { "DESCRIPTION", DESCRIPTION }, + { "DISPLAY-HINT", DISPLAYHINT }, + { "END", END }, + { "ENUMERATED", ENUMERATED }, + { "ENTERPRISE", ENTERPRISE }, + { "EXPLICIT", EXPLICIT }, + { "EXPORTS", EXPORTS }, + { "EXTERNAL", EXTERNAL }, + { "FALSE", FALSE }, + { "FROM", FROM }, + { "GROUP", GROUP }, + { "Gauge32", Gauge32 }, + { "IDENTIFIER", IDENTIFIER }, + { "IMPLICIT", IMPLICIT }, + { "IMPLIED", IMPLIED }, + { "IMPORTS", IMPORTS }, + { "INCLUDES", INCLUDES }, + { "INDEX", INDEX }, + { "INTEGER", INTEGER }, + { "Integer32", Integer32 }, + { "IpAddress", IpAddress }, + { "LAST-UPDATED", LASTUPDATED }, + { "MANDATORY-GROUPS", MANDATORYGROUPS }, + { "MAX", MAX }, + { "MAX-ACCESS", MAXACCESS }, + { "MIN", MIN }, + { "MIN-ACCESS", MINACCESS }, + { "MINUS-INFINITY", MINUSINFINITY }, + { "MODULE", MODULE }, + { "MODULE-COMPLIANCE", MODULECOMPLIANCE }, + { "MODULE-IDENTITY", MODULEIDENTITY }, + { "NOTIFICATION-GROUP", NOTIFICATIONGROUP }, + { "NOTIFICATION-TYPE", NOTIFICATIONTYPE }, + { "NOTIFICATIONS", NOTIFICATIONS }, + { "NULL", ASNNULL }, + { "OBJECT", OBJECT }, + { "OBJECT-GROUP", OBJECTGROUP }, + { "OBJECT-IDENTITY", OBJECTIDENTITY }, + { "OBJECT-TYPE", OBJECTTYPE }, + { "OBJECTS", OBJECTS }, + { "OCTET", OCTET }, + { "OF", OF }, + { "OPTIONAL", OPTIONAL }, + { "ORGANIZATION", ORGANIZATION }, + { "Opaque", Opaque }, + { "PLUS-INFINITY", PLUSINFINITY }, + { "PRESENT", PRESENT }, + { "PRIVATE", PRIVATE }, + { "PRODUCT-RELEASE", PRODUCTRELEASE }, + { "REAL", REAL }, + { "REFERENCE", REFERENCE }, + { "REVISION", REVISION }, + { "SEQUENCE", SEQUENCE }, + { "SET", SET }, + { "SIZE", SIZE }, + { "STATUS", STATUS }, + { "STRING", STRING }, + { "SUPPORTS", SUPPORTS }, + { "SYNTAX", SYNTAX }, + { "TAGS", TAGS }, + { "TEXTUAL-CONVENTION", TEXTUALCONVENTION }, + { "TRAP-TYPE", TRAPTYPE }, + { "TRUE", TRUE }, + { "TimeTicks", TimeTicks }, + { "UNITS", UNITS }, + { "UNIVERSAL", UNIVERSAL }, + { "Unsigned32", Unsigned32 }, + { "VARIABLES", VARIABLES }, + { "VARIATION", VARIATION }, + { "WITH", WITH }, + { "WRITE-SYNTAX", WRITESYNTAX }, + + /* ASN.1 */ + { "::=", PRODUCTION }, + { "..", RANGESEPARATOR }, + + /* SMIv2 */ + { "SNMPv2-SMI", SNMPv2SMI }, + { "SNMPv2-CONF", SNMPv2CONF }, + { "SNMPv2-TC", SNMPv2TC }, + + {} + }; + char buf[TEXT_MAX]; + size_t i = 0, j; + int c, comment = 0; + const char *errstr; + char *endptr; + + while ((c = fgetc(file.stream)) != EOF) { + if (i == sizeof(buf)) { + yyerror("token too large"); + return ERROR; + } + if (i > 0 && buf[0] == '"') { + if (c == '"') { + buf[i] = '\0'; + (void)strlcpy(yylval.string, buf + 1, + sizeof(yylval.string)); + return TEXT; + } + if (c == '\n') + file.lineno++; + buf[i++] = c; + continue; + } + if (comment) { + if (c == '-') { + if (++comment == 3) + comment = 0; + } else if (c == '\n') { + file.lineno++; + comment = 0; + } else + comment = 1; + continue; + } + if (c == '\n') { + if (i != 0) { + if (buf[i - 1] == '\r') { + i--; + if (i == 0) { + file.lineno++; + continue; + } + } + ungetc(c, file.stream); + goto token; + } + file.lineno++; + continue; + } + if (c == ' ' || c == '\t') { + if (i == 0) + continue; + goto token; + } + if (c == '.' || c == ':') { + if (i > 0 && buf[0] != '.' && buf[0] != ':') { + ungetc(c, file.stream); + goto token; + } + } + if (i > 0 && (buf[0] == '.' || buf[0] == ':')) { + if (c != '.' && c != ':' && c != '=') { + ungetc(c, file.stream); + goto token; + } + } + if (c == ',' || c == ';' || c == '{' || c == '}' || + c == '(' || c == ')' || c == '[' || c == ']' || c == '|') { + if (i == 0) + return c; + ungetc(c, file.stream); + goto token; + } + buf[i++] = c; + if (i >= 2) { + if (buf[i - 2] == '-' && buf[i - 1] == '-') { + if (i > 2) { + ungetc('-', file.stream); + ungetc('-', file.stream); + i -= 2; + goto token; + } + comment = 1; + i = 0; + continue; + } + } + } + + if (ferror(file.stream)) { + yyerror(NULL); + return ERROR; + } + if (i == 0) + return 0; + + token: + buf[i] = '\0'; + + for (i = 0; keywords[i].name != NULL; i++) { + if (strcmp(keywords[i].name, buf) == 0) + return keywords[i].token; + } + + if (isupper(buf[0])) { + for (i = 1; buf[i] != '\0'; i++) { + if (!isalnum(buf[i]) && buf[i] != '-') + break; + } + if (buf[i] == '\0' && buf[i - 1] != '-') { + strlcpy(yylval.string, buf, sizeof(yylval.string)); + return typereference; + } + } + if (islower(buf[0])) { + for (i = 1; buf[i] != '\0'; i++) { + if (!isalnum(buf[i]) && buf[i] != '-') + break; + } + if (buf[i] == '\0'&& buf[i - 1] != '-') { + strlcpy(yylval.string, buf, sizeof(yylval.string)); + return identifier; + } + } + + if (buf[0] == '\'') { + for (i = 1; buf[i] != '\0'; i++) + continue; + if (i < 3 || buf[i - 2] != '\'') { + yyerror("incomplete binary or hexadecimal string"); + return ERROR; + } + if (tolower(buf[i - 1]) == 'b') { + for (j = 1; j < i - 2; j++) { + if (buf[j] != '0' && buf[j] != '1') { + yyerror("invalid character in bstring"); + return ERROR; + } + } + strlcpy(yylval.string, buf + 1, sizeof(yylval.string)); + yylval.string[i - 2] = '\0'; + return BSTRING; + } else if (tolower(buf[i - 1]) == 'h') { + for (j = 1; j < i - 2; j++) { + if (!isxdigit(buf[j])) { + yyerror("invalid character in hstring"); + return ERROR; + } + } + strlcpy(yylval.string, buf + 1, sizeof(yylval.string)); + yylval.string[i - 2] = '\0'; + return HSTRING; + } + yyerror("no valid binary or hexadecimal string"); + return ERROR; + } + for (i = 0; buf[i] != '\0'; i++) { + if (i == 0 && buf[i] == '-') + continue; + if (!isdigit(buf[i])) + break; + } + if ((i == 1 && isdigit(buf[0])) || i > 1) { + yylval.signednumber = + strtonum(buf, LLONG_MIN, LLONG_MAX, &errstr); + if (errstr != NULL) { + if (errno == ERANGE && isdigit(buf[0])) { + errno = 0; + yylval.number = strtoull(buf, &endptr, 10); + if (errno == 0) + return NUMBER; + } + yyerror("invalid number: %s: %s", buf, errstr); + return ERROR; + } + if (buf[0] == '-') + return SIGNEDNUMBER; + yylval.number = yylval.signednumber; + return NUMBER; + } + + yyerror("unknown token: %s", buf); + return ERROR; +} + +void +mib_clear(void) +{ + struct module *m; + struct item *iso; + + while ((m = RB_ROOT(&modulesci)) != NULL) + mib_modulefree(m); + + /* iso */ + iso = RB_ROOT(&items); + assert(strcmp(iso->name, "iso") == 0); + RB_REMOVE(itemsgci, &itemsci, iso); + RB_REMOVE(items, &items, iso); + free(iso->oid.bo_id); + free(iso); + assert(RB_EMPTY(&modulesci)); + assert(RB_EMPTY(&modulescs)); + assert(RB_EMPTY(&items)); + assert(RB_EMPTY(&itemsci)); +} + +void +mib_modulefree(struct module *m) +{ + struct item *item; + + if (m == NULL) + return; + + if (RB_FIND(modulesci, &modulesci, m) == m) + RB_REMOVE(modulesci, &modulesci, m); + if (RB_FIND(modulescs, &modulescs, m) == m) + RB_REMOVE(modulescs, &modulescs, m); + free(m->imports); + + while ((item = RB_ROOT(&m->itemscs)) != NULL) { + RB_REMOVE(itemscs, &m->itemscs, item); + if (RB_FIND(itemsci, &m->itemsci, item) == item) + RB_REMOVE(itemsci, &m->itemsci, item); + if (RB_FIND(items, &items, item) == item) + RB_REMOVE(items, &items, item); + if (RB_FIND(itemsgci, &itemsci, item) == item) + RB_REMOVE(itemsgci, &itemsci, item); + if (!item->resolved) + free(item->oid_unresolved); + else + free(item->oid.bo_id); + free(item); + } + + free(m); +} + +int +mib_imports_add(char *name, char **symbols) +{ + size_t im, ism, isi; + struct import *import; + + for (im = 0; module->imports != NULL && + module->imports[im].name[0] != '\0'; im++) { + if (strcmp(module->imports[im].name, name) == 0) + break; + } + if (module->imports == NULL || module->imports[im].name[0] == '\0') { + if ((import = reallocarray(module->imports, im + 2, + sizeof(*module->imports))) == NULL) { + yyerror("malloc"); + return -1; + } + module->imports = import; + strlcpy(module->imports[im].name, name, + sizeof(module->imports[im].name)); + module->imports[im].nsymbols = 0; + + module->imports[im + 1].name[0] = '\0'; + } + + import = &module->imports[im]; + for (isi = 0; symbols[isi] != NULL; isi++) { + for (ism = 0; ism < import->nsymbols; ism++) { + if (strcmp(symbols[isi], + import->symbols[ism].name) == 0) { + yyerror("symbol %s already imported", + symbols[isi]); + break; + } + } + if (ism != import->nsymbols) + continue; + + if (import->nsymbols == nitems(import->symbols)) { + yyerror("Too many symbols imported"); + return -1; + } + strlcpy(import->symbols[ism].name, symbols[isi], + sizeof(import->symbols[ism].name)); + import->symbols[ism].item = NULL; + import->nsymbols++; + } + return 0; +} + +int +mib_oid_append(struct oid_unresolved *oid, const struct objidcomponent *subid) +{ + if (oid->bo_n == nitems(oid->bo_id)) { + yyerror("oid too long"); + return -1; + } + + switch (oid->bo_id[oid->bo_n].type = subid->type) { + case OCT_DESCRIPTOR: + strlcpy(oid->bo_id[oid->bo_n].name, subid->name, + sizeof(oid->bo_id[oid->bo_n].name)); + break; + case OCT_NUMBER: + oid->bo_id[oid->bo_n].number = subid->number; + break; + case OCT_NAMEANDNUMBER: + oid->bo_id[oid->bo_n].number = subid->number; + strlcpy(oid->bo_id[oid->bo_n].name, subid->name, + sizeof(oid->bo_id[oid->bo_n].name)); + } + oid->bo_n++; + return 0; +} + +int +mib_oid_concat(struct oid_unresolved *dst, const struct oid_unresolved *src) +{ + size_t i; + + for (i = 0; i < src->bo_n; i++) + if (mib_oid_append(dst, &src->bo_id[i]) == -1) + return -1; + return 0; +} + +struct item * +mib_item(const char *name, enum item_type type) +{ + struct item *item; + + if ((item = calloc(1, sizeof(*item))) == NULL) { + log_warn("malloc"); + return NULL; + } + + item->type = type; + item->resolved = 0; + item->module = module; + (void)strlcpy(item->name, name, sizeof(item->name)); + + if (RB_INSERT(itemscs, &module->itemscs, item) != NULL) { + yyerror("duplicate item %s", name); + free(item); + return NULL; + } + + return item; +} + +int +mib_item_oid(struct item *item, const struct oid_unresolved *oid) +{ + if ((item->oid_unresolved = calloc(1, + sizeof(*item->oid_unresolved))) == NULL) { + yyerror("malloc"); + return -1; + } + + *item->oid_unresolved = *oid; + return 0; +} + +int +mib_macro(const char *name) +{ + return mib_item(name, IT_MACRO) == NULL ? -1 : 0; +} + +struct item * +mib_oid(const char *name, const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_OID)) == NULL) + return NULL; + + if (mib_item_oid(item, oid) == -1) + return NULL; + return item; +} + +int +mib_applicationsyntax(const char *name) +{ + return mib_item(name, IT_APPLICATIONSYNTAX) == NULL ? -1 : 0; +} + +int +mib_moduleidentity(const char *name, time_t lastupdated, + const char *organization, const char *contactinfo, const char *description, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_MODULE_IDENTITY)) == NULL) + return -1; + + if (mib_item_oid(item, oid) == -1) + return -1; + + module->lastupdated = lastupdated; + return 0; +} + +int +mib_objectidentity(const char *name, enum status status, + const char *description, const char *reference, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_OBJECT_IDENTITY)) == NULL) + return -1; + + item->objectidentity.status = status; + if (mib_item_oid(item, oid) == -1) + return -1; + + return 0; +} + +int +mib_objecttype(const char *name, void *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) +{ + struct item *item; + + if ((item = mib_item(name, IT_OBJECT_TYPE)) == NULL) + return -1; + + item->objecttype.maxaccess = maxaccess; + item->objecttype.status = status; + if (mib_item_oid(item, oid) == -1) + return -1; + return 0; +} + +int +mib_notificationtype(const char *name, void *objects, enum status status, + const char *description, const char *reference, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_NOTIFICATION_TYPE)) == NULL) + return -1; + + item->notificationtype.status = status; + if (mib_item_oid(item, oid) == -1) + return -1; + return 0; +} + +int +mib_textualconvetion(const char *name, const char *displayhint, + enum status status, const char *description, const char *reference, + void *syntax) +{ + struct item *item; + + if ((item = mib_item(name, IT_TEXTUAL_CONVENTION)) == NULL) + return -1; + item->textualconvention.status = status; + return 0; +} + +int +mib_objectgroup(const char *name, void *objects, enum status status, + const char *description, const char *reference, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_OBJECT_GROUP)) == NULL) + return -1; + + if (mib_item_oid(item, oid) == -1) + return -1; + return 0; +} + +int +mib_notificationgroup(const char *name, void *notifications, enum status status, + const char *description, const char *reference, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_NOTIFICATION_GROUP)) == NULL) + return -1; + + if (mib_item_oid(item, oid) == -1) + return -1; + return 0; +} + +int +mib_modulecompliance(const char *name, enum status status, + const char *description, const char *reference, void *mods, + const struct oid_unresolved *oid) +{ + struct item *item; + + if ((item = mib_item(name, IT_MODULE_COMPLIANCE)) == NULL) + return -1; + + if (mib_item_oid(item, oid) == -1) + return -1; + return 0; +} + +void +mib_parsefile(const char *path) +{ + mib_defaults(); + + log_debug("mib parsing %s", path); + if ((file.stream = fopen(path, "r")) == NULL) { + log_warn("fopen"); + return; + } + file.lineno = 1; + file.name = path; + file.state = FILE_UNDEFINED; + + if (yyparse() != 0) { + mib_modulefree(module); + module = NULL; + } + + fclose(file.stream); +} + +void +mib_parsedir(const char *path) +{ + DIR *dir; + struct dirent *dirent; + char mibfile[PATH_MAX]; + + if ((dir = opendir(path)) == NULL) { + log_warn("opendir(%s)", path); + return; + } + + errno = 0; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + if (snprintf(mibfile, sizeof(mibfile), "%s/%s", + path, dirent->d_name) >= (int)sizeof(mibfile)) + continue; + if (dirent->d_type == DT_DIR) { + mib_parsedir(mibfile); + continue; + } + if (dirent->d_type != DT_REG) + continue; + mib_parsefile(mibfile); + } + + closedir(dir); +} + +struct item * +mib_item_parent(struct ber_oid *oid) +{ + struct item *item, search; + + search.oid.bo_n = oid->bo_n; + search.oid.bo_id = oid->bo_id; + + while (search.oid.bo_n > 0) { + if ((item = RB_FIND(items, &items, &search)) != NULL) + return item; + search.oid.bo_n--; + } + return NULL; +} + +const char * +mib_string2oid(const char *str, struct ber_oid *oid) +{ + char mname[512], *descriptor, *digits; + struct module *m = NULL, msearch; + struct item *item = NULL, isearch; + struct ber_oid oidbuf; + size_t i; + + oid->bo_n = 0; + + if (isdigit(str[0])) { + if (ober_string2oid(str, oid) == -1) + return "invalid OID"; + return NULL; + } + + if (strlcpy(mname, str, sizeof(mname)) >= sizeof(mname)) + return "OID name too long"; + + if ((descriptor = strchr(mname, ':')) != NULL) { + descriptor[0] = '\0'; + if (descriptor[1] != ':') + return "module and descriptor must be separated by " + "double colon"; + descriptor += 2; + if (strlcpy(msearch.name, mname, sizeof(msearch.name)) >= + sizeof(msearch.name)) + return "module not found"; + if ((m = RB_FIND(modulescs, &modulescs, &msearch)) == NULL) { + m = RB_FIND(modulesci, &modulesci, &msearch); + if (m == NULL) + return "module not found"; + } + } else + descriptor = mname; + + if ((digits = strchr(descriptor, '.')) != NULL) { + digits++[0] = '\0'; + if (ober_string2oid(digits, &oidbuf) == -1) + return "invalid OID"; + } else + oidbuf.bo_n = 0; + + if (strlcpy(isearch.name, descriptor, sizeof(isearch.name)) >= + sizeof(isearch.name)) + return "descriptor not found"; + + if (m != NULL) { + item = RB_FIND(itemscs, &m->itemscs, &isearch); + if (item == NULL) + item = RB_FIND(itemsci, &m->itemsci, &isearch); + } else + item = RB_FIND(itemsgci, &itemsci, &isearch); + if (item == NULL) + return "descriptor not found"; + + if (item->oid.bo_n + oidbuf.bo_n > nitems(oid->bo_id)) + return "OID too long"; + + for (i = 0; i < item->oid.bo_n; i++) + oid->bo_id[oid->bo_n++] = item->oid.bo_id[i]; + for (i = 0; i < oidbuf.bo_n; i++) + oid->bo_id[oid->bo_n++] = oidbuf.bo_id[i]; + + return NULL; +} + +char * +mib_oid2string(struct ber_oid *oid, char *buf, size_t buflen, + enum mib_oidfmt fmt) +{ + struct item *item; + char digit[11]; + size_t i = 0; + + buf[0] = '\0'; + if (fmt == MIB_OIDSYMBOLIC && (item = mib_item_parent(oid)) != NULL) { + snprintf(buf, buflen, "%s::%s", item->module->name, + item->name); + i = item->oid.bo_n; + } + + for (; i < oid->bo_n; i++) { + if (i != 0) + strlcat(buf, ".", buflen); + snprintf(digit, sizeof(digit), "%"PRIu32, oid->bo_id[i]); + strlcat(buf, digit, buflen); + } + + return buf; +} + +void +mib_defaults(void) +{ + struct oid_unresolved oid; + struct item *iso; + + if (!RB_EMPTY(&modulesci)) + return; + + /* ASN.1 constant, not part of a module */ + if ((iso = calloc(1, sizeof(*iso))) == NULL) + fatal("malloc"); + iso->type = IT_OID; + iso->resolved = 1; + iso->module = NULL; + strlcpy(iso->name, "iso", sizeof(iso->name)); + if ((iso->oid.bo_id = calloc(1, sizeof(*iso->oid.bo_id))) == NULL) + fatal("malloc"); + iso->oid.bo_id[0] = 1; + iso->oid.bo_n = 1; + RB_INSERT(items, &items, iso); + RB_INSERT(itemsgci, &itemsci, iso); + + file.state = FILE_SMI2; + file.lineno = 0; + + if ((module = calloc(1, sizeof(*module))) == NULL) + fatal("malloc"); + RB_INIT(&module->itemscs); + RB_INIT(&module->itemsci); + module->resolved = 0; + + strlcpy(module->name, "SNMPv2-SMI", sizeof(module->name)); + file.name = module->name; + oid.bo_id[0].type = oid.bo_id[1].type = OCT_NUMBER; + oid.bo_n = 2; + + oid.bo_id[0].number = 1; + oid.bo_id[1].number = 3; + if (mib_oid("org", &oid) == NULL) + exit(1); + + oid.bo_id[0].number = oid.bo_id[1].number = 0; + if (mib_oid("zeroDotZero", &oid) == NULL) + exit(1); + + oid.bo_id[0].type = OCT_DESCRIPTOR; + strlcpy(oid.bo_id[0].name, "org", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 6; + if (mib_oid("dod", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "dod", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 1; + if (mib_oid("internet", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "internet", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 1; + if (mib_oid("directory", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 2; + if (mib_oid("mgmt", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "mgmt", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 1; + if (mib_oid("mib-2", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "mib-2", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 10; + if (mib_oid("transmission", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "internet", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 3; + if (mib_oid("experimental", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 4; + if (mib_oid("private", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 5; + if (mib_oid("security", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 6; + if (mib_oid("snmpV2", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "private", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 1; + if (mib_oid("enterprises", &oid) == NULL) + exit(1); + + strlcpy(oid.bo_id[0].name, "snmpV2", sizeof(oid.bo_id[0].name)); + oid.bo_id[1].number = 1; + if (mib_oid("snmpDomains", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 2; + if (mib_oid("snmpProxys", &oid) == NULL) + exit(1); + + oid.bo_id[1].number = 3; + if (mib_oid("snmpModules", &oid) == NULL) + exit(1); + + if (mib_macro("MODULE-IDENTITY") == -1 || + mib_macro("OBJECT-IDENTITY") == -1 || + mib_macro("OBJECT-TYPE") == -1 || + mib_macro("NOTIFICATION-TYPE") == -1) + exit(1); + + if (mib_applicationsyntax("Integer32") == -1 || + mib_applicationsyntax("IpAddress") == -1 || + mib_applicationsyntax("Counter32") == -1 || + mib_applicationsyntax("Gauge32") == -1 || + mib_applicationsyntax("Unsigned32") == -1 || + mib_applicationsyntax("TimeTicks") == -1 || + mib_applicationsyntax("Opaque") == -1 || + mib_applicationsyntax("Counter64") == -1) + exit(1); + + RB_INSERT(modulesci, &modulesci, module); + RB_INSERT(modulescs, &modulescs, module); + + if ((module = calloc(1, sizeof(*module))) == NULL) + fatal("malloc"); + RB_INIT(&module->itemscs); + RB_INIT(&module->itemsci); + module->resolved = 0; + + strlcpy(module->name, "SNMPv2-TC", sizeof(module->name)); + file.name = module->name; + + if (mib_macro("TEXTUAL-CONVENTION") == -1) + exit(1); + + if (mib_textualconvetion( + "DisplayString", "255a", CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "PhysAddress", "1x:", CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "MacAddress", "1x:", CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TruthValue", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TestAndIncr", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "AutonomousType", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "InstancePointer", NULL, OBSOLETE, "", NULL, NULL) == -1 || + mib_textualconvetion( + "VariablePointer", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "RowPointer", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "RowStatus", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TimeStamp", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TimeInterval", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "DateAndTime", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "StorageType", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TDomain", NULL, CURRENT, "", NULL, NULL) == -1 || + mib_textualconvetion( + "TAddress", NULL, CURRENT, "", NULL, NULL) == -1) + exit(1); + + RB_INSERT(modulesci, &modulesci, module); + RB_INSERT(modulescs, &modulescs, module); + + if ((module = calloc(1, sizeof(*module))) == NULL) + fatal("malloc"); + RB_INIT(&module->itemscs); + RB_INIT(&module->itemsci); + module->resolved = 0; + + strlcpy(module->name, "SNMPv2-CONF", sizeof(module->name)); + file.name = module->name; + + if (mib_macro("OBJECT-GROUP") == -1 || + mib_macro("NOTIFICATION-GROUP") == -1 || + mib_macro("MODULE-COMPLIANCE") == -1 || + mib_macro("AGENT-CAPABILITIES") == -1) + exit(1); + + RB_INSERT(modulesci, &modulesci, module); + RB_INSERT(modulescs, &modulescs, module); + + module = NULL; +} + +/* + * Used only for resolving phase + */ +struct item * +mib_item_find(struct item *orig, const char *name) +{ + struct module *m = orig->module; + struct item *item, search; + struct import *import; + size_t i, j; + + strlcpy(search.name, name, sizeof(search.name)); + if ((item = RB_FIND(itemscs, &m->itemscs, &search)) != NULL) { + if (mib_resolve_item(item) == -1) + return NULL; + return item; + } + + for (i = 0; m->imports != NULL && m->imports[i].name[0] != '\0'; i++) { + import = &m->imports[i]; + for (j = 0; j < import->nsymbols; j++) { + if (strcmp(name, import->symbols[j].name) == 0) + return import->symbols[j].item; + } + } + + log_warnx("%s::%s: item %s not found: disabling", + m->name, orig->name, name); + + return NULL; +} + +int +mib_resolve_oid(struct oid_resolved *dst, struct oid_unresolved *src, + struct item *item) +{ + struct module *m = item->module; + struct item *reference, search; + struct ber_oid oid; + size_t i, j, bo_n; + + oid.bo_n = 0; + for (i = 0; i < src->bo_n; i++) { + switch (src->bo_id[i].type) { + case OCT_DESCRIPTOR: + if ((reference = mib_item_find(item, + src->bo_id[i].name)) == NULL) + return -1; + for (j = 0; j < reference->oid.bo_n; j++) + oid.bo_id[oid.bo_n++] = + reference->oid.bo_id[j]; + break; + case OCT_NUMBER: + if (oid.bo_n == nitems(oid.bo_id)) { + log_warnx("%s::%s: OID too long: disabling", + m->name, item->name); + return -1; + } + oid.bo_id[oid.bo_n++] = src->bo_id[i].number; + break; + case OCT_NAMEANDNUMBER: + if (oid.bo_n == nitems(oid.bo_id)) { + log_warnx("%s::%s: OID too long: disabling", + m->name, item->name); + return -1; + } + oid.bo_id[oid.bo_n++] = src->bo_id[i].number; + if (i == src->bo_n - 1) { + if (strcmp(src->bo_id[i].name, item->name) != 0) { + log_warnx("%s::%s: last OBJECT " + "IDENTIFIER component name doesn't " + "match item: disabling", m->name, + item->name); + return -1; + } + break; + } + strlcpy(search.name, src->bo_id[i].name, + sizeof(search.name)); + reference = RB_FIND(itemscs, &m->itemscs, &search); + if (reference != NULL) { + search.oid.bo_n = oid.bo_n; + search.oid.bo_id = oid.bo_id; + if (item_cmp_oid(reference, &search) != 0) { + log_warnx("%s::%s: two different OIDs " + "for same descriptor: disabling", + m->name, item->name); + return -1; + } + break; + } + + bo_n = src->bo_n; + src->bo_n = i + 1; + module = m; + reference = mib_oid(src->bo_id[i].name, src); + module = NULL; + if (reference == NULL) + return -1; + if (mib_resolve_item(reference) == -1) + return -1; + src->bo_n = bo_n; + break; + } + } + + dst->bo_n = oid.bo_n; + if ((dst->bo_id = calloc(dst->bo_n, sizeof(*dst->bo_id))) == NULL) { + log_warn("malloc"); + return -1; + } + for (i = 0; i < oid.bo_n; i++) + dst->bo_id[i] = oid.bo_id[i]; + + return 0; +} + +/* + * No recursion protection. Assume MIBs do the right thing + */ +int +mib_resolve_item(struct item *item) +{ + struct item *prev; + struct oid_resolved oid; + + if (item->resolved) + return 0; + + item->resolved = 1; + + if (item->type == IT_MACRO || + item->type == IT_APPLICATIONSYNTAX || + item->type == IT_TEXTUAL_CONVENTION) + return 0; + + if (mib_resolve_oid(&oid, item->oid_unresolved, item) == -1) + return -1; + free(item->oid_unresolved); + item->oid = oid; + + 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) { + RB_REMOVE(items, &items, prev); + RB_INSERT(items, &items, item); + } + } + RB_INSERT(itemsgci, &itemsci, item); + RB_INSERT(itemsci, &item->module->itemsci, item); + + return 0; +} + +int +mib_resolve_module(struct module *m) +{ + struct module msearch; + struct import *import; + struct import_symbol *symbol; + struct item *item, isearch; + size_t i, j; + + if (m->resolved) + return 0; + + m->resolved = 1; + + for (i = 0; m->imports != NULL && m->imports[i].name[0] != '\0'; i++) { + import = &m->imports[i]; + strlcpy(msearch.name, import->name, sizeof(msearch.name)); + import->module = RB_FIND(modulescs, &modulescs, &msearch); + if (import->module == NULL || + mib_resolve_module(import->module) == -1) { + log_warnx("%s: import %s not found: disabling", + m->name, import->name); + goto fail; + } + + for (j = 0; j < import->nsymbols; j++) { + symbol = &import->symbols[j]; + strlcpy(isearch.name, symbol->name, + sizeof(isearch.name)); + symbol->item = RB_FIND(itemscs, + &import->module->itemscs, &isearch); + if (symbol->item == NULL) { + log_warnx("%s: symbol %s not found in %s: " + "disabling", m->name, symbol->name, + import->name); + goto fail; + } + } + } + + RB_FOREACH(item, itemscs, &m->itemscs) { + if (mib_resolve_item(item) == -1) + goto fail; + } + + free(m->imports); + m->imports = NULL; + + return 0; + fail: + mib_modulefree(m); + return -1; +} + +void +mib_resolve(void) +{ + struct module *m; + + mib_defaults(); + next: + RB_FOREACH(m, modulescs, &modulescs) { + if (mib_resolve_module(m) == -1) { + /* + * mib_resolve_module can recurse and remove, + * for which RB_FOEACH_SAFE doesn't protect. + */ + goto next; + } + } +} + +int +module_cmp_cs(struct module *m1, struct module *m2) +{ + return strcmp(m1->name, m2->name); +} + +int +module_cmp_ci(struct module *m1, struct module *m2) +{ + return strcasecmp(m1->name, m2->name); +} + +int +item_cmp_cs(struct item *d1, struct item *d2) +{ + return strcmp(d1->name, d2->name); +} + +int +item_cmp_ci(struct item *d1, struct item *d2) +{ + return strcasecmp(d1->name, d2->name); +} + +int +item_cmp_oid(struct item *i1, struct item *i2) +{ + size_t i, min; + + min = i1->oid.bo_n < i2->oid.bo_n ? i1->oid.bo_n : i2->oid.bo_n; + for (i = 0; i < min; i++) { + if (i1->oid.bo_id[i] < i2->oid.bo_id[i]) + return (-1); + if (i1->oid.bo_id[i] > i2->oid.bo_id[i]) + return (1); + } + /* i1 is parent of i2 */ + if (i1->oid.bo_n < i2->oid.bo_n) + return (-2); + /* i1 is child of i2 */ + if (i1->oid.bo_n > i2->oid.bo_n) + return 2; + return (0); +} + +RB_GENERATE_STATIC(modulesci, module, entryci, module_cmp_ci); +RB_GENERATE_STATIC(modulescs, module, entrycs, module_cmp_cs); +RB_GENERATE_STATIC(itemsgci, item, entrygci, item_cmp_ci); +RB_GENERATE_STATIC(itemsci, item, entryci, item_cmp_ci); +RB_GENERATE_STATIC(itemscs, item, entrycs, item_cmp_cs); +RB_GENERATE_STATIC(items, item, entry, item_cmp_oid); |