/* * Copyright (c) 1990, 1991, 1993, 1994, 1995, 1996, 1997 * John Robert LoVerso. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * This implementation has been influenced by the CMU SNMP release, * by Steve Waldbusser. However, this shares no code with that system. * Additional ASN.1 insight gained from Marshall T. Rose's _The_Open_Book_. * Earlier forms of this implementation were derived and/or inspired by an * awk script originally written by C. Philip Wood of LANL (but later * heavily modified by John Robert LoVerso). The copyright notice for * that work is preserved below, even though it may not rightly apply * to this file. * * Support for SNMPv2c/SNMPv3 and the ability to link the module against * the libsmi was added by J. Schoenwaelder, Copyright (c) 1999. * * This started out as a very simple program, but the incremental decoding * (into the BE structure) complicated things. * # Los Alamos National Laboratory # # Copyright (c) 1990, 1991, 1993, 1994, 1995, 1996, 1997 # This software was produced under a U.S. Government contract # (W-7405-ENG-36) by Los Alamos National Laboratory, which is # operated by the University of California for the U.S. Department # of Energy. The U.S. Government is licensed to use, reproduce, # and distribute this software. Permission is granted to the # public to copy and use this software without charge, provided # that this Notice and any statement of authorship are reproduced # on all copies. Neither the Government nor the University makes # any warranty, express or implied, or assumes any liability or # responsibility for the use of this software. # @(#)snmp.awk.x 1.1 (LANL) 1/15/90 */ /* \summary: Simple Network Management Protocol (SNMP) printer */ #include #include "netdissect-stdinc.h" #include #include #include #ifdef USE_LIBSMI #include #endif #include "netdissect-ctype.h" #include "netdissect.h" #include "extract.h" #undef OPAQUE /* defined in */ /* * Universal ASN.1 types * (we only care about the tag values for those allowed in the Internet SMI) */ static const char *Universal[] = { "U-0", "Boolean", "Integer", #define INTEGER 2 "Bitstring", "String", #define STRING 4 "Null", #define ASN_NULL 5 "ObjID", #define OBJECTID 6 "ObjectDes", "U-8","U-9","U-10","U-11", /* 8-11 */ "U-12","U-13","U-14","U-15", /* 12-15 */ "Sequence", #define SEQUENCE 16 "Set" }; /* * Application-wide ASN.1 types from the Internet SMI and their tags */ static const char *Application[] = { "IpAddress", #define IPADDR 0 "Counter", #define COUNTER 1 "Gauge", #define GAUGE 2 "TimeTicks", #define TIMETICKS 3 "Opaque", #define OPAQUE 4 "C-5", "Counter64" #define COUNTER64 6 }; /* * Context-specific ASN.1 types for the SNMP PDUs and their tags */ static const char *Context[] = { "GetRequest", #define GETREQ 0 "GetNextRequest", #define GETNEXTREQ 1 "GetResponse", #define GETRESP 2 "SetRequest", #define SETREQ 3 "Trap", #define TRAP 4 "GetBulk", #define GETBULKREQ 5 "Inform", #define INFORMREQ 6 "V2Trap", #define V2TRAP 7 "Report" #define REPORT 8 }; #define NOTIFY_CLASS(x) (x == TRAP || x == V2TRAP || x == INFORMREQ) #define READ_CLASS(x) (x == GETREQ || x == GETNEXTREQ || x == GETBULKREQ) #define WRITE_CLASS(x) (x == SETREQ) #define RESPONSE_CLASS(x) (x == GETRESP) #define INTERNAL_CLASS(x) (x == REPORT) /* * Context-specific ASN.1 types for the SNMP Exceptions and their tags */ static const char *Exceptions[] = { "noSuchObject", #define NOSUCHOBJECT 0 "noSuchInstance", #define NOSUCHINSTANCE 1 "endOfMibView", #define ENDOFMIBVIEW 2 }; /* * Private ASN.1 types * The Internet SMI does not specify any */ static const char *Private[] = { "P-0" }; /* * error-status values for any SNMP PDU */ static const char *ErrorStatus[] = { "noError", "tooBig", "noSuchName", "badValue", "readOnly", "genErr", "noAccess", "wrongType", "wrongLength", "wrongEncoding", "wrongValue", "noCreation", "inconsistentValue", "resourceUnavailable", "commitFailed", "undoFailed", "authorizationError", "notWritable", "inconsistentName" }; #define DECODE_ErrorStatus(e) \ ( e >= 0 && (size_t)e < sizeof(ErrorStatus)/sizeof(ErrorStatus[0]) \ ? ErrorStatus[e] \ : (snprintf(errbuf, sizeof(errbuf), "err=%u", e), errbuf)) /* * generic-trap values in the SNMP Trap-PDU */ static const char *GenericTrap[] = { "coldStart", "warmStart", "linkDown", "linkUp", "authenticationFailure", "egpNeighborLoss", "enterpriseSpecific" #define GT_ENTERPRISE 6 }; #define DECODE_GenericTrap(t) \ ( t >= 0 && (size_t)t < sizeof(GenericTrap)/sizeof(GenericTrap[0]) \ ? GenericTrap[t] \ : (snprintf(buf, sizeof(buf), "gt=%d", t), buf)) /* * ASN.1 type class table * Ties together the preceding Universal, Application, Context, and Private * type definitions. */ #define defineCLASS(x) { "x", x, sizeof(x)/sizeof(x[0]) } /* not ANSI-C */ static const struct { const char *name; const char **Id; int numIDs; } Class[] = { defineCLASS(Universal), #define UNIVERSAL 0 defineCLASS(Application), #define APPLICATION 1 defineCLASS(Context), #define CONTEXT 2 defineCLASS(Private), #define PRIVATE 3 defineCLASS(Exceptions), #define EXCEPTIONS 4 }; /* * defined forms for ASN.1 types */ static const char *Form[] = { "Primitive", #define PRIMITIVE 0 "Constructed", #define CONSTRUCTED 1 }; /* * A structure for the OID tree for the compiled-in MIB. * This is stored as a general-order tree. */ static struct obj { const char *desc; /* name of object */ u_char oid; /* sub-id following parent */ u_char type; /* object type (unused) */ struct obj *child, *next; /* child and next sibling pointers */ } *objp = NULL; /* * Include the compiled in SNMP MIB. "mib.h" is produced by feeding * RFC-1156 format files into "makemib". "mib.h" MUST define at least * a value for `mibroot'. * * In particular, this is gross, as this is including initialized structures, * and by right shouldn't be an "include" file. */ #include "mib.h" /* * This defines a list of OIDs which will be abbreviated on output. * Currently, this includes the prefixes for the Internet MIB, the * private enterprises tree, and the experimental tree. */ #define OID_FIRST_OCTET(x, y) (((x)*40) + (y)) /* X.690 8.19.4 */ #ifndef NO_ABREV_MIB static const uint8_t mib_oid[] = { OID_FIRST_OCTET(1, 3), 6, 1, 2, 1 }; #endif #ifndef NO_ABREV_ENTER static const uint8_t enterprises_oid[] = { OID_FIRST_OCTET(1, 3), 6, 1, 4, 1 }; #endif #ifndef NO_ABREV_EXPERI static const uint8_t experimental_oid[] = { OID_FIRST_OCTET(1, 3), 6, 1, 3 }; #endif #ifndef NO_ABBREV_SNMPMODS static const uint8_t snmpModules_oid[] = { OID_FIRST_OCTET(1, 3), 6, 1, 6, 3 }; #endif #define OBJ_ABBREV_ENTRY(prefix, obj) \ { prefix, &_ ## obj ## _obj, obj ## _oid, sizeof (obj ## _oid) } static const struct obj_abrev { const char *prefix; /* prefix for this abrev */ struct obj *node; /* pointer into object table */ const uint8_t *oid; /* ASN.1 encoded OID */ size_t oid_len; /* length of OID */ } obj_abrev_list[] = { #ifndef NO_ABREV_MIB /* .iso.org.dod.internet.mgmt.mib */ OBJ_ABBREV_ENTRY("", mib), #endif #ifndef NO_ABREV_ENTER /* .iso.org.dod.internet.private.enterprises */ OBJ_ABBREV_ENTRY("E:", enterprises), #endif #ifndef NO_ABREV_EXPERI /* .iso.org.dod.internet.experimental */ OBJ_ABBREV_ENTRY("X:", experimental), #endif #ifndef NO_ABBREV_SNMPMODS /* .iso.org.dod.internet.snmpV2.snmpModules */ OBJ_ABBREV_ENTRY("S:", snmpModules), #endif { 0,0,0,0 } }; /* * This is used in the OID print routine to walk down the object tree * rooted at `mibroot'. */ #define OBJ_PRINT(o, suppressdot) \ { \ if (objp) { \ do { \ if ((o) == objp->oid) \ break; \ } while ((objp = objp->next) != NULL); \ } \ if (objp) { \ ND_PRINT(suppressdot?"%s":".%s", objp->desc); \ objp = objp->child; \ } else \ ND_PRINT(suppressdot?"%u":".%u", (o)); \ } /* * This is the definition for the Any-Data-Type storage used purely for * temporary internal representation while decoding an ASN.1 data stream. */ struct be { uint32_t asnlen; union { const uint8_t *raw; int32_t integer; uint32_t uns; const u_char *str; uint64_t uns64; } data; u_short id; u_char form, class; /* tag info */ u_char type; #define BE_ANY 255 #define BE_NONE 0 #define BE_NULL 1 #define BE_OCTET 2 #define BE_OID 3 #define BE_INT 4 #define BE_UNS 5 #define BE_STR 6 #define BE_SEQ 7 #define BE_INETADDR 8 #define BE_PDU 9 #define BE_UNS64 10 #define BE_NOSUCHOBJECT 128 #define BE_NOSUCHINST 129 #define BE_ENDOFMIBVIEW 130 }; /* * SNMP versions recognized by this module */ static const char *SnmpVersion[] = { "SNMPv1", #define SNMP_VERSION_1 0 "SNMPv2c", #define SNMP_VERSION_2 1 "SNMPv2u", #define SNMP_VERSION_2U 2 "SNMPv3" #define SNMP_VERSION_3 3 }; /* * Defaults for SNMP PDU components */ #define DEF_COMMUNITY "public" /* * constants for ASN.1 decoding */ #define OIDMUX 40 #define ASNLEN_INETADDR 4 #define ASN_SHIFT7 7 #define ASN_SHIFT8 8 #define ASN_BIT8 0x80 #define ASN_LONGLEN 0x80 #define ASN_ID_BITS 0x1f #define ASN_FORM_BITS 0x20 #define ASN_FORM_SHIFT 5 #define ASN_CLASS_BITS 0xc0 #define ASN_CLASS_SHIFT 6 #define ASN_ID_EXT 0x1f /* extension ID in tag field */ /* * This decodes the next ASN.1 object in the stream pointed to by "p" * (and of real-length "len") and stores the intermediate data in the * provided BE object. * * This returns -l if it fails (i.e., the ASN.1 stream is not valid). * O/w, this returns the number of bytes parsed from "p". */ static int asn1_parse(netdissect_options *ndo, const u_char *p, u_int len, struct be *elem) { u_char form, class, id; u_int i, hdr; elem->asnlen = 0; elem->type = BE_ANY; if (len < 1) { ND_PRINT("[nothing to parse]"); return -1; } /* * it would be nice to use a bit field, but you can't depend on them. * +---+---+---+---+---+---+---+---+ * + class |frm| id | * +---+---+---+---+---+---+---+---+ * 7 6 5 4 3 2 1 0 */ id = GET_U_1(p) & ASN_ID_BITS; /* lower 5 bits, range 00-1f */ #ifdef notdef form = (GET_U_1(p) & 0xe0) >> 5; /* move upper 3 bits to lower 3 */ class = form >> 1; /* bits 7&6 -> bits 1&0, range 0-3 */ form &= 0x1; /* bit 5 -> bit 0, range 0-1 */ #else form = (u_char)(GET_U_1(p) & ASN_FORM_BITS) >> ASN_FORM_SHIFT; class = (u_char)(GET_U_1(p) & ASN_CLASS_BITS) >> ASN_CLASS_SHIFT; #endif elem->form = form; elem->class = class; elem->id = id; p++; len--; hdr = 1; /* extended tag field */ if (id == ASN_ID_EXT) { /* * The ID follows, as a sequence of octets with the * 8th bit set and the remaining 7 bits being * the next 7 bits of the value, terminated with * an octet with the 8th bit not set. * * First, assemble all the octets with the 8th * bit set. XXX - this doesn't handle a value * that won't fit in 32 bits. */ id = 0; while (GET_U_1(p) & ASN_BIT8) { if (len < 1) { ND_PRINT("[Xtagfield?]"); return -1; } id = (id << 7) | (GET_U_1(p) & ~ASN_BIT8); len--; hdr++; p++; } if (len < 1) { ND_PRINT("[Xtagfield?]"); return -1; } elem->id = id = (id << 7) | GET_U_1(p); --len; ++hdr; ++p; } if (len < 1) { ND_PRINT("[no asnlen]"); return -1; } elem->asnlen = GET_U_1(p); p++; len--; hdr++; if (elem->asnlen & ASN_BIT8) { uint32_t noct = elem->asnlen % ASN_BIT8; elem->asnlen = 0; if (len < noct) { ND_PRINT("[asnlen? %d<%d]", len, noct); return -1; } ND_TCHECK_LEN(p, noct); for (; noct != 0; len--, hdr++, noct--) { elem->asnlen = (elem->asnlen << ASN_SHIFT8) | GET_U_1(p); p++; } } if (len < elem->asnlen) { ND_PRINT("[len%dasnlen); return -1; } if (form >= sizeof(Form)/sizeof(Form[0])) { ND_PRINT("[form?%d]", form); return -1; } if (class >= sizeof(Class)/sizeof(Class[0])) { ND_PRINT("[class?%c/%d]", *Form[form], class); return -1; } if ((int)id >= Class[class].numIDs) { ND_PRINT("[id?%c/%s/%d]", *Form[form], Class[class].name, id); return -1; } ND_TCHECK_LEN(p, elem->asnlen); switch (form) { case PRIMITIVE: switch (class) { case UNIVERSAL: switch (id) { case STRING: elem->type = BE_STR; elem->data.str = p; break; case INTEGER: { uint32_t data; elem->type = BE_INT; data = 0; if (elem->asnlen == 0) { ND_PRINT("[asnlen=0]"); return -1; } if (GET_U_1(p) & ASN_BIT8) /* negative */ data = UINT_MAX; for (i = elem->asnlen; i != 0; p++, i--) data = (data << ASN_SHIFT8) | GET_U_1(p); elem->data.integer = data; break; } case OBJECTID: elem->type = BE_OID; elem->data.raw = (const uint8_t *)p; break; case ASN_NULL: elem->type = BE_NULL; elem->data.raw = NULL; break; default: elem->type = BE_OCTET; elem->data.raw = (const uint8_t *)p; ND_PRINT("[P/U/%s]", Class[class].Id[id]); break; } break; case APPLICATION: switch (id) { case IPADDR: elem->type = BE_INETADDR; elem->data.raw = (const uint8_t *)p; break; case COUNTER: case GAUGE: case TIMETICKS: { uint32_t data; elem->type = BE_UNS; data = 0; for (i = elem->asnlen; i != 0; p++, i--) data = (data << 8) + GET_U_1(p); elem->data.uns = data; break; } case COUNTER64: { uint64_t data64; elem->type = BE_UNS64; data64 = 0; for (i = elem->asnlen; i != 0; p++, i--) data64 = (data64 << 8) + GET_U_1(p); elem->data.uns64 = data64; break; } default: elem->type = BE_OCTET; elem->data.raw = (const uint8_t *)p; ND_PRINT("[P/A/%s]", Class[class].Id[id]); break; } break; case CONTEXT: switch (id) { case NOSUCHOBJECT: elem->type = BE_NOSUCHOBJECT; elem->data.raw = NULL; break; case NOSUCHINSTANCE: elem->type = BE_NOSUCHINST; elem->data.raw = NULL; break; case ENDOFMIBVIEW: elem->type = BE_ENDOFMIBVIEW; elem->data.raw = NULL; break; } break; default: ND_PRINT("[P/%s/%s]", Class[class].name, Class[class].Id[id]); elem->type = BE_OCTET; elem->data.raw = (const uint8_t *)p; break; } break; case CONSTRUCTED: switch (class) { case UNIVERSAL: switch (id) { case SEQUENCE: elem->type = BE_SEQ; elem->data.raw = (const uint8_t *)p; break; default: elem->type = BE_OCTET; elem->data.raw = (const uint8_t *)p; ND_PRINT("C/U/%s", Class[class].Id[id]); break; } break; case CONTEXT: elem->type = BE_PDU; elem->data.raw = (const uint8_t *)p; break; default: elem->type = BE_OCTET; elem->data.raw = (const uint8_t *)p; ND_PRINT("C/%s/%s", Class[class].name, Class[class].Id[id]); break; } break; } p += elem->asnlen; len -= elem->asnlen; return elem->asnlen + hdr; trunc: nd_print_trunc(ndo); return -1; } static int asn1_print_octets(netdissect_options *ndo, struct be *elem) { const u_char *p = (const u_char *)elem->data.raw; uint32_t asnlen = elem->asnlen; uint32_t i; ND_TCHECK_LEN(p, asnlen); for (i = asnlen; i != 0; p++, i--) ND_PRINT("_%.2x", GET_U_1(p)); return 0; trunc: nd_print_trunc(ndo); return -1; } static int asn1_print_string(netdissect_options *ndo, struct be *elem) { int printable = 1, first = 1; const u_char *p; uint32_t asnlen = elem->asnlen; uint32_t i; p = elem->data.str; ND_TCHECK_LEN(p, asnlen); for (i = asnlen; printable && i != 0; p++, i--) printable = ND_ASCII_ISPRINT(GET_U_1(p)); p = elem->data.str; if (printable) { ND_PRINT("\""); if (nd_printn(ndo, p, asnlen, ndo->ndo_snapend)) { ND_PRINT("\""); goto trunc; } ND_PRINT("\""); } else { for (i = asnlen; i != 0; p++, i--) { ND_PRINT(first ? "%.2x" : "_%.2x", GET_U_1(p)); first = 0; } } return 0; trunc: nd_print_trunc(ndo); return -1; } /* * Display the ASN.1 object represented by the BE object. * This used to be an integral part of asn1_parse() before the intermediate * BE form was added. */ static int asn1_print(netdissect_options *ndo, struct be *elem) { const u_char *p; uint32_t asnlen = elem->asnlen; uint32_t i; switch (elem->type) { case BE_OCTET: if (asn1_print_octets(ndo, elem) == -1) return -1; break; case BE_NULL: break; case BE_OID: { int first = -1; uint32_t o = 0; p = (const u_char *)elem->data.raw; i = asnlen; if (!ndo->ndo_nflag && asnlen > 2) { const struct obj_abrev *a = &obj_abrev_list[0]; for (; a->node; a++) { if (i < a->oid_len) continue; if (!ND_TTEST_LEN(p, a->oid_len)) continue; if (memcmp(a->oid, p, a->oid_len) == 0) { objp = a->node->child; i -= a->oid_len; p += a->oid_len; ND_PRINT("%s", a->prefix); first = 1; break; } } } for (; i != 0; p++, i--) { o = (o << ASN_SHIFT7) + (GET_U_1(p) & ~ASN_BIT8); if (GET_U_1(p) & ASN_LONGLEN) continue; /* * first subitem encodes two items with * 1st*OIDMUX+2nd * (see X.690:1997 clause 8.19 for the details) */ if (first < 0) { int s; if (!ndo->ndo_nflag) objp = mibroot; first = 0; s = o / OIDMUX; if (s > 2) s = 2; OBJ_PRINT(s, first); o -= s * OIDMUX; } OBJ_PRINT(o, first); if (--first < 0) first = 0; o = 0; } break; } case BE_INT: ND_PRINT("%d", elem->data.integer); break; case BE_UNS: ND_PRINT("%u", elem->data.uns); break; case BE_UNS64: ND_PRINT("%" PRIu64, elem->data.uns64); break; case BE_STR: if (asn1_print_string(ndo, elem) == -1) return -1; break; case BE_SEQ: ND_PRINT("Seq(%u)", elem->asnlen); break; case BE_INETADDR: if (asnlen != ASNLEN_INETADDR) ND_PRINT("[inetaddr len!=%d]", ASNLEN_INETADDR); p = (const u_char *)elem->data.raw; ND_TCHECK_LEN(p, asnlen); for (i = asnlen; i != 0; p++, i--) { ND_PRINT((i == asnlen) ? "%u" : ".%u", GET_U_1(p)); } break; case BE_NOSUCHOBJECT: case BE_NOSUCHINST: case BE_ENDOFMIBVIEW: ND_PRINT("[%s]", Class[EXCEPTIONS].Id[elem->id]); break; case BE_PDU: ND_PRINT("%s(%u)", Class[CONTEXT].Id[elem->id], elem->asnlen); break; case BE_ANY: ND_PRINT("[BE_ANY!?]"); break; default: ND_PRINT("[be!?]"); break; } return 0; trunc: nd_print_trunc(ndo); return -1; } #ifdef notdef /* * This is a brute force ASN.1 printer: recurses to dump an entire structure. * This will work for any ASN.1 stream, not just an SNMP PDU. * * By adding newlines and spaces at the correct places, this would print in * Rose-Normal-Form. * * This is not currently used. */ static void asn1_decode(u_char *p, u_int length) { struct be elem; int i = 0; while (i >= 0 && length > 0) { i = asn1_parse(ndo, p, length, &elem); if (i >= 0) { ND_PRINT(" "); if (asn1_print(ndo, &elem) < 0) return; if (elem.type == BE_SEQ || elem.type == BE_PDU) { ND_PRINT(" {"); asn1_decode(elem.data.raw, elem.asnlen); ND_PRINT(" }"); } length -= i; p += i; } } } #endif #ifdef USE_LIBSMI struct smi2be { SmiBasetype basetype; int be; }; static const struct smi2be smi2betab[] = { { SMI_BASETYPE_INTEGER32, BE_INT }, { SMI_BASETYPE_OCTETSTRING, BE_STR }, { SMI_BASETYPE_OCTETSTRING, BE_INETADDR }, { SMI_BASETYPE_OBJECTIDENTIFIER, BE_OID }, { SMI_BASETYPE_UNSIGNED32, BE_UNS }, { SMI_BASETYPE_INTEGER64, BE_NONE }, { SMI_BASETYPE_UNSIGNED64, BE_UNS64 }, { SMI_BASETYPE_FLOAT32, BE_NONE }, { SMI_BASETYPE_FLOAT64, BE_NONE }, { SMI_BASETYPE_FLOAT128, BE_NONE }, { SMI_BASETYPE_ENUM, BE_INT }, { SMI_BASETYPE_BITS, BE_STR }, { SMI_BASETYPE_UNKNOWN, BE_NONE } }; static int smi_decode_oid(netdissect_options *ndo, struct be *elem, unsigned int *oid, unsigned int oidsize, unsigned int *oidlen) { const u_char *p = (const u_char *)elem->data.raw; uint32_t asnlen = elem->asnlen; uint32_t i = asnlen; int o = 0, first = -1; unsigned int firstval; for (*oidlen = 0; i != 0; p++, i--) { o = (o << ASN_SHIFT7) + (GET_U_1(p) & ~ASN_BIT8); if (GET_U_1(p) & ASN_LONGLEN) continue; /* * first subitem encodes two items with 1st*OIDMUX+2nd * (see X.690:1997 clause 8.19 for the details) */ if (first < 0) { first = 0; firstval = o / OIDMUX; if (firstval > 2) firstval = 2; o -= firstval * OIDMUX; if (*oidlen < oidsize) { oid[(*oidlen)++] = firstval; } } if (*oidlen < oidsize) { oid[(*oidlen)++] = o; } o = 0; } return 0; } static int smi_check_type(SmiBasetype basetype, int be) { int i; for (i = 0; smi2betab[i].basetype != SMI_BASETYPE_UNKNOWN; i++) { if (smi2betab[i].basetype == basetype && smi2betab[i].be == be) { return 1; } } return 0; } static int smi_check_a_range(SmiType *smiType, SmiRange *smiRange, struct be *elem) { int ok = 1; switch (smiType->basetype) { case SMI_BASETYPE_OBJECTIDENTIFIER: case SMI_BASETYPE_OCTETSTRING: if (smiRange->minValue.value.unsigned32 == smiRange->maxValue.value.unsigned32) { ok = (elem->asnlen == smiRange->minValue.value.unsigned32); } else { ok = (elem->asnlen >= smiRange->minValue.value.unsigned32 && elem->asnlen <= smiRange->maxValue.value.unsigned32); } break; case SMI_BASETYPE_INTEGER32: ok = (elem->data.integer >= smiRange->minValue.value.integer32 && elem->data.integer <= smiRange->maxValue.value.integer32); break; case SMI_BASETYPE_UNSIGNED32: ok = (elem->data.uns >= smiRange->minValue.value.unsigned32 && elem->data.uns <= smiRange->maxValue.value.unsigned32); break; case SMI_BASETYPE_UNSIGNED64: /* XXX */ break; /* case SMI_BASETYPE_INTEGER64: SMIng */ /* case SMI_BASETYPE_FLOAT32: SMIng */ /* case SMI_BASETYPE_FLOAT64: SMIng */ /* case SMI_BASETYPE_FLOAT128: SMIng */ case SMI_BASETYPE_ENUM: case SMI_BASETYPE_BITS: case SMI_BASETYPE_UNKNOWN: ok = 1; break; default: ok = 0; break; } return ok; } static int smi_check_range(SmiType *smiType, struct be *elem) { SmiRange *smiRange; int ok = 1; for (smiRange = smiGetFirstRange(smiType); smiRange; smiRange = smiGetNextRange(smiRange)) { ok = smi_check_a_range(smiType, smiRange, elem); if (ok) { break; } } if (ok) { SmiType *parentType; parentType = smiGetParentType(smiType); if (parentType) { ok = smi_check_range(parentType, elem); } } return ok; } static SmiNode * smi_print_variable(netdissect_options *ndo, struct be *elem, int *status) { unsigned int oid[128], oidlen; SmiNode *smiNode = NULL; unsigned int i; if (!nd_smi_module_loaded) { *status = asn1_print(ndo, elem); return NULL; } *status = smi_decode_oid(ndo, elem, oid, sizeof(oid) / sizeof(unsigned int), &oidlen); if (*status < 0) return NULL; smiNode = smiGetNodeByOID(oidlen, oid); if (! smiNode) { *status = asn1_print(ndo, elem); return NULL; } if (ndo->ndo_vflag) { ND_PRINT("%s::", smiGetNodeModule(smiNode)->name); } ND_PRINT("%s", smiNode->name); if (smiNode->oidlen < oidlen) { for (i = smiNode->oidlen; i < oidlen; i++) { ND_PRINT(".%u", oid[i]); } } *status = 0; return smiNode; } static int smi_print_value(netdissect_options *ndo, SmiNode *smiNode, u_short pduid, struct be *elem) { unsigned int i, oid[128], oidlen; SmiType *smiType; SmiNamedNumber *nn; int done = 0; if (! smiNode || ! (smiNode->nodekind & (SMI_NODEKIND_SCALAR | SMI_NODEKIND_COLUMN))) { return asn1_print(ndo, elem); } if (elem->type == BE_NOSUCHOBJECT || elem->type == BE_NOSUCHINST || elem->type == BE_ENDOFMIBVIEW) { return asn1_print(ndo, elem); } if (NOTIFY_CLASS(pduid) && smiNode->access < SMI_ACCESS_NOTIFY) { ND_PRINT("[notNotifiable]"); } if (READ_CLASS(pduid) && smiNode->access < SMI_ACCESS_READ_ONLY) { ND_PRINT("[notReadable]"); } if (WRITE_CLASS(pduid) && smiNode->access < SMI_ACCESS_READ_WRITE) { ND_PRINT("[notWritable]"); } if (RESPONSE_CLASS(pduid) && smiNode->access == SMI_ACCESS_NOT_ACCESSIBLE) { ND_PRINT("[noAccess]"); } smiType = smiGetNodeType(smiNode); if (! smiType) { return asn1_print(ndo, elem); } if (! smi_check_type(smiType->basetype, elem->type)) { ND_PRINT("[wrongType]"); } if (! smi_check_range(smiType, elem)) { ND_PRINT("[outOfRange]"); } /* resolve bits to named bits */ /* check whether instance identifier is valid */ /* apply display hints (integer, octetstring) */ /* convert instance identifier to index type values */ switch (elem->type) { case BE_OID: if (smiType->basetype == SMI_BASETYPE_BITS) { /* print bit labels */ } else { if (nd_smi_module_loaded && smi_decode_oid(ndo, elem, oid, sizeof(oid)/sizeof(unsigned int), &oidlen) == 0) { smiNode = smiGetNodeByOID(oidlen, oid); if (smiNode) { if (ndo->ndo_vflag) { ND_PRINT("%s::", smiGetNodeModule(smiNode)->name); } ND_PRINT("%s", smiNode->name); if (smiNode->oidlen < oidlen) { for (i = smiNode->oidlen; i < oidlen; i++) { ND_PRINT(".%u", oid[i]); } } done++; } } } break; case BE_INT: if (smiType->basetype == SMI_BASETYPE_ENUM) { for (nn = smiGetFirstNamedNumber(smiType); nn; nn = smiGetNextNamedNumber(nn)) { if (nn->value.value.integer32 == elem->data.integer) { ND_PRINT("%s", nn->name); ND_PRINT("(%d)", elem->data.integer); done++; break; } } } break; } if (! done) { return asn1_print(ndo, elem); } return 0; } #endif /* * General SNMP header * SEQUENCE { * version INTEGER {version-1(0)}, * community OCTET STRING, * data ANY -- PDUs * } * PDUs for all but Trap: (see rfc1157 from page 15 on) * SEQUENCE { * request-id INTEGER, * error-status INTEGER, * error-index INTEGER, * varbindlist SEQUENCE OF * SEQUENCE { * name ObjectName, * value ObjectValue * } * } * PDU for Trap: * SEQUENCE { * enterprise OBJECT IDENTIFIER, * agent-addr NetworkAddress, * generic-trap INTEGER, * specific-trap INTEGER, * time-stamp TimeTicks, * varbindlist SEQUENCE OF * SEQUENCE { * name ObjectName, * value ObjectValue * } * } */ /* * Decode SNMP varBind */ static void varbind_print(netdissect_options *ndo, u_short pduid, const u_char *np, u_int length) { struct be elem; int count = 0; #ifdef USE_LIBSMI SmiNode *smiNode = NULL; #endif int status; /* Sequence of varBind */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!SEQ of varbind]"); asn1_print(ndo, &elem); return; } if ((u_int)count < length) ND_PRINT("[%d extra after SEQ of varbind]", length - count); /* descend */ length = elem.asnlen; np = (const u_char *)elem.data.raw; while (length) { const u_char *vbend; u_int vblength; ND_PRINT(" "); /* Sequence */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!varbind]"); asn1_print(ndo, &elem); return; } vbend = np + count; vblength = length - count; /* descend */ length = elem.asnlen; np = (const u_char *)elem.data.raw; /* objName (OID) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_OID) { ND_PRINT("[objName!=OID]"); asn1_print(ndo, &elem); return; } #ifdef USE_LIBSMI smiNode = smi_print_variable(ndo, &elem, &status); #else status = asn1_print(ndo, &elem); #endif if (status < 0) return; length -= count; np += count; if (pduid != GETREQ && pduid != GETNEXTREQ && pduid != GETBULKREQ) ND_PRINT("="); /* objVal (ANY) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (pduid == GETREQ || pduid == GETNEXTREQ || pduid == GETBULKREQ) { if (elem.type != BE_NULL) { ND_PRINT("[objVal!=NULL]"); if (asn1_print(ndo, &elem) < 0) return; } } else { if (elem.type != BE_NULL) { #ifdef USE_LIBSMI status = smi_print_value(ndo, smiNode, pduid, &elem); #else status = asn1_print(ndo, &elem); #endif } if (status < 0) return; } length = vblength; np = vbend; } } /* * Decode SNMP PDUs: GetRequest, GetNextRequest, GetResponse, SetRequest, * GetBulk, Inform, V2Trap, and Report */ static void snmppdu_print(netdissect_options *ndo, u_short pduid, const u_char *np, u_int length) { struct be elem; int count = 0, error_status; /* reqId (Integer) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[reqId!=INT]"); asn1_print(ndo, &elem); return; } if (ndo->ndo_vflag) ND_PRINT("R=%d ", elem.data.integer); length -= count; np += count; /* errorStatus (Integer) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[errorStatus!=INT]"); asn1_print(ndo, &elem); return; } error_status = 0; if ((pduid == GETREQ || pduid == GETNEXTREQ || pduid == SETREQ || pduid == INFORMREQ || pduid == V2TRAP || pduid == REPORT) && elem.data.integer != 0) { char errbuf[20]; ND_PRINT("[errorStatus(%s)!=0]", DECODE_ErrorStatus(elem.data.integer)); } else if (pduid == GETBULKREQ) { ND_PRINT(" N=%d", elem.data.integer); } else if (elem.data.integer != 0) { char errbuf[20]; ND_PRINT(" %s", DECODE_ErrorStatus(elem.data.integer)); error_status = elem.data.integer; } length -= count; np += count; /* errorIndex (Integer) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[errorIndex!=INT]"); asn1_print(ndo, &elem); return; } if ((pduid == GETREQ || pduid == GETNEXTREQ || pduid == SETREQ || pduid == INFORMREQ || pduid == V2TRAP || pduid == REPORT) && elem.data.integer != 0) ND_PRINT("[errorIndex(%d)!=0]", elem.data.integer); else if (pduid == GETBULKREQ) ND_PRINT(" M=%d", elem.data.integer); else if (elem.data.integer != 0) { if (!error_status) ND_PRINT("[errorIndex(%d) w/o errorStatus]", elem.data.integer); else ND_PRINT("@%d", elem.data.integer); } else if (error_status) { ND_PRINT("[errorIndex==0]"); } length -= count; np += count; varbind_print(ndo, pduid, np, length); } /* * Decode SNMP Trap PDU */ static void trappdu_print(netdissect_options *ndo, const u_char *np, u_int length) { struct be elem; int count = 0, generic; ND_PRINT(" "); /* enterprise (oid) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_OID) { ND_PRINT("[enterprise!=OID]"); asn1_print(ndo, &elem); return; } if (asn1_print(ndo, &elem) < 0) return; length -= count; np += count; ND_PRINT(" "); /* agent-addr (inetaddr) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INETADDR) { ND_PRINT("[agent-addr!=INETADDR]"); asn1_print(ndo, &elem); return; } if (asn1_print(ndo, &elem) < 0) return; length -= count; np += count; /* generic-trap (Integer) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[generic-trap!=INT]"); asn1_print(ndo, &elem); return; } generic = elem.data.integer; { char buf[20]; ND_PRINT(" %s", DECODE_GenericTrap(generic)); } length -= count; np += count; /* specific-trap (Integer) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[specific-trap!=INT]"); asn1_print(ndo, &elem); return; } if (generic != GT_ENTERPRISE) { if (elem.data.integer != 0) ND_PRINT("[specific-trap(%d)!=0]", elem.data.integer); } else ND_PRINT(" s=%d", elem.data.integer); length -= count; np += count; ND_PRINT(" "); /* time-stamp (TimeTicks) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_UNS) { /* XXX */ ND_PRINT("[time-stamp!=TIMETICKS]"); asn1_print(ndo, &elem); return; } if (asn1_print(ndo, &elem) < 0) return; length -= count; np += count; varbind_print(ndo, TRAP, np, length); } /* * Decode arbitrary SNMP PDUs. */ static void pdu_print(netdissect_options *ndo, const u_char *np, u_int length, int version) { struct be pdu; int count = 0; /* PDU (Context) */ if ((count = asn1_parse(ndo, np, length, &pdu)) < 0) return; if (pdu.type != BE_PDU) { ND_PRINT("[no PDU]"); return; } if ((u_int)count < length) ND_PRINT("[%d extra after PDU]", length - count); if (ndo->ndo_vflag) { ND_PRINT("{ "); } if (asn1_print(ndo, &pdu) < 0) return; ND_PRINT(" "); /* descend into PDU */ length = pdu.asnlen; np = (const u_char *)pdu.data.raw; if (version == SNMP_VERSION_1 && (pdu.id == GETBULKREQ || pdu.id == INFORMREQ || pdu.id == V2TRAP || pdu.id == REPORT)) { ND_PRINT("[v2 PDU in v1 message]"); return; } if (version == SNMP_VERSION_2 && pdu.id == TRAP) { ND_PRINT("[v1 PDU in v2 message]"); return; } switch (pdu.id) { case TRAP: trappdu_print(ndo, np, length); break; case GETREQ: case GETNEXTREQ: case GETRESP: case SETREQ: case GETBULKREQ: case INFORMREQ: case V2TRAP: case REPORT: snmppdu_print(ndo, pdu.id, np, length); break; } if (ndo->ndo_vflag) { ND_PRINT(" } "); } } /* * Decode a scoped SNMP PDU. */ static void scopedpdu_print(netdissect_options *ndo, const u_char *np, u_int length, int version) { struct be elem; int count = 0; /* Sequence */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!scoped PDU]"); asn1_print(ndo, &elem); return; } length = elem.asnlen; np = (const u_char *)elem.data.raw; /* contextEngineID (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[contextEngineID!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; ND_PRINT("E="); if (asn1_print_octets(ndo, &elem) == -1) return; ND_PRINT(" "); /* contextName (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[contextName!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; ND_PRINT("C="); if (asn1_print_string(ndo, &elem) == -1) return; ND_PRINT(" "); pdu_print(ndo, np, length, version); } /* * Decode SNMP Community Header (SNMPv1 and SNMPv2c) */ static void community_print(netdissect_options *ndo, const u_char *np, u_int length, int version) { struct be elem; int count = 0; /* Community (String) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[comm!=STR]"); asn1_print(ndo, &elem); return; } /* default community */ if (!(elem.asnlen == sizeof(DEF_COMMUNITY) - 1 && strncmp((const char *)elem.data.str, DEF_COMMUNITY, sizeof(DEF_COMMUNITY) - 1) == 0)) { /* ! "public" */ ND_PRINT("C="); if (asn1_print_string(ndo, &elem) == -1) return; ND_PRINT(" "); } length -= count; np += count; pdu_print(ndo, np, length, version); } /* * Decode SNMPv3 User-based Security Message Header (SNMPv3) */ static void usm_print(netdissect_options *ndo, const u_char *np, u_int length) { struct be elem; int count = 0; /* Sequence */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!usm]"); asn1_print(ndo, &elem); return; } length = elem.asnlen; np = (const u_char *)elem.data.raw; /* msgAuthoritativeEngineID (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgAuthoritativeEngineID!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; /* msgAuthoritativeEngineBoots (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[msgAuthoritativeEngineBoots!=INT]"); asn1_print(ndo, &elem); return; } if (ndo->ndo_vflag) ND_PRINT("B=%d ", elem.data.integer); length -= count; np += count; /* msgAuthoritativeEngineTime (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[msgAuthoritativeEngineTime!=INT]"); asn1_print(ndo, &elem); return; } if (ndo->ndo_vflag) ND_PRINT("T=%d ", elem.data.integer); length -= count; np += count; /* msgUserName (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgUserName!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; ND_PRINT("U="); if (asn1_print_string(ndo, &elem) == -1) return; ND_PRINT(" "); /* msgAuthenticationParameters (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgAuthenticationParameters!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; /* msgPrivacyParameters (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgPrivacyParameters!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; if ((u_int)count < length) ND_PRINT("[%d extra after usm SEQ]", length - count); } /* * Decode SNMPv3 Message Header (SNMPv3) */ static void v3msg_print(netdissect_options *ndo, const u_char *np, u_int length) { struct be elem; int count = 0; u_char flags; int model; const u_char *xnp = np; int xlength = length; /* Sequence */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!message]"); asn1_print(ndo, &elem); return; } length = elem.asnlen; np = (const u_char *)elem.data.raw; if (ndo->ndo_vflag) { ND_PRINT("{ "); } /* msgID (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[msgID!=INT]"); asn1_print(ndo, &elem); return; } length -= count; np += count; /* msgMaxSize (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[msgMaxSize!=INT]"); asn1_print(ndo, &elem); return; } length -= count; np += count; /* msgFlags (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgFlags!=STR]"); asn1_print(ndo, &elem); return; } if (elem.asnlen != 1) { ND_PRINT("[msgFlags size %d]", elem.asnlen); return; } flags = GET_U_1(elem.data.str); if (flags != 0x00 && flags != 0x01 && flags != 0x03 && flags != 0x04 && flags != 0x05 && flags != 0x07) { ND_PRINT("[msgFlags=0x%02X]", flags); return; } length -= count; np += count; ND_PRINT("F=%s%s%s ", flags & 0x01 ? "a" : "", flags & 0x02 ? "p" : "", flags & 0x04 ? "r" : ""); /* msgSecurityModel (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[msgSecurityModel!=INT]"); asn1_print(ndo, &elem); return; } model = elem.data.integer; length -= count; np += count; if ((u_int)count < length) ND_PRINT("[%d extra after message SEQ]", length - count); if (ndo->ndo_vflag) { ND_PRINT("} "); } if (model == 3) { if (ndo->ndo_vflag) { ND_PRINT("{ USM "); } } else { ND_PRINT("[security model %d]", model); return; } np = xnp + (np - xnp); length = xlength - (np - xnp); /* msgSecurityParameters (OCTET STRING) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_STR) { ND_PRINT("[msgSecurityParameters!=STR]"); asn1_print(ndo, &elem); return; } length -= count; np += count; if (model == 3) { usm_print(ndo, elem.data.str, elem.asnlen); if (ndo->ndo_vflag) { ND_PRINT("} "); } } if (ndo->ndo_vflag) { ND_PRINT("{ ScopedPDU "); } scopedpdu_print(ndo, np, length, 3); if (ndo->ndo_vflag) { ND_PRINT("} "); } } /* * Decode SNMP header and pass on to PDU printing routines */ void snmp_print(netdissect_options *ndo, const u_char *np, u_int length) { struct be elem; int count = 0; int version = 0; ndo->ndo_protocol = "snmp"; ND_PRINT(" "); /* initial Sequence */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_SEQ) { ND_PRINT("[!init SEQ]"); asn1_print(ndo, &elem); return; } if ((u_int)count < length) ND_PRINT("[%d extra after iSEQ]", length - count); /* descend */ length = elem.asnlen; np = (const u_char *)elem.data.raw; /* Version (INTEGER) */ if ((count = asn1_parse(ndo, np, length, &elem)) < 0) return; if (elem.type != BE_INT) { ND_PRINT("[version!=INT]"); asn1_print(ndo, &elem); return; } switch (elem.data.integer) { case SNMP_VERSION_1: case SNMP_VERSION_2: case SNMP_VERSION_3: if (ndo->ndo_vflag) ND_PRINT("{ %s ", SnmpVersion[elem.data.integer]); break; default: ND_PRINT("SNMP [version = %d]", elem.data.integer); return; } version = elem.data.integer; length -= count; np += count; switch (version) { case SNMP_VERSION_1: case SNMP_VERSION_2: community_print(ndo, np, length, version); break; case SNMP_VERSION_3: v3msg_print(ndo, np, length); break; default: ND_PRINT("[version = %d]", elem.data.integer); break; } if (ndo->ndo_vflag) { ND_PRINT("} "); } }