diff options
author | 2019-09-18 09:48:14 +0000 | |
---|---|---|
committer | 2019-09-18 09:48:14 +0000 | |
commit | b89ba26fac84ada547bc29af5c17533ab27b2597 (patch) | |
tree | 9d2c8f4294f0423952e6b9d446fe60e881015a84 /usr.bin/snmp | |
parent | Move snmp packaging and unpackaging to their own function. (diff) | |
download | wireguard-openbsd-b89ba26fac84ada547bc29af5c17533ab27b2597.tar.xz wireguard-openbsd-b89ba26fac84ada547bc29af5c17533ab27b2597.zip |
Initial SNMPv3/USM support.
This commit only implements noAuthNoPriv.
Feedback and OK jmatthew@
Diffstat (limited to 'usr.bin/snmp')
-rw-r--r-- | usr.bin/snmp/Makefile | 4 | ||||
-rw-r--r-- | usr.bin/snmp/snmp.1 | 75 | ||||
-rw-r--r-- | usr.bin/snmp/snmp.c | 156 | ||||
-rw-r--r-- | usr.bin/snmp/snmp.h | 34 | ||||
-rw-r--r-- | usr.bin/snmp/snmpc.c | 173 | ||||
-rw-r--r-- | usr.bin/snmp/usm.c | 275 | ||||
-rw-r--r-- | usr.bin/snmp/usm.h | 23 |
7 files changed, 699 insertions, 41 deletions
diff --git a/usr.bin/snmp/Makefile b/usr.bin/snmp/Makefile index 62bb5565a30..7fef458a60d 100644 --- a/usr.bin/snmp/Makefile +++ b/usr.bin/snmp/Makefile @@ -1,7 +1,7 @@ -# $OpenBSD: Makefile,v 1.1 2019/08/09 06:17:59 martijn Exp $ +# $OpenBSD: Makefile,v 1.2 2019/09/18 09:48:14 martijn Exp $ PROG= snmp -SRCS= mib.c smi.c snmp.c snmpc.c +SRCS= mib.c smi.c snmp.c snmpc.c usm.c LDADD+= -lutil DPADD+= ${LIBUTIL} diff --git a/usr.bin/snmp/snmp.1 b/usr.bin/snmp/snmp.1 index e158ba00048..95eb26b7a45 100644 --- a/usr.bin/snmp/snmp.1 +++ b/usr.bin/snmp/snmp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: snmp.1,v 1.3 2019/08/14 14:40:23 deraadt Exp $ +.\" $OpenBSD: snmp.1,v 1.4 2019/09/18 09:48:14 martijn Exp $ .\" .\" Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: August 14 2019 $ +.Dd $Mdocdate: September 18 2019 $ .Dt SNMP 1 .Os .Sh NAME @@ -24,19 +24,29 @@ .Nm .Cm get | getnext .Op Fl c Ar community +.Op Fl E Ar ctxengineid +.Op Fl e Ar secengineid +.Op Fl n Ar ctxname +.Op Fl O Cm afnQqSvx .Op Fl r Ar retries .Op Fl t Ar timeout +.Op Fl u Ar user .Op Fl v Ar version -.Op Fl O Cm afnQqSvx +.Op Fl Z Ar boots , Ns Ar time .Ar agent .Ar oid ... .Nm .Cm walk .Op Fl c Ar community +.Op Fl E Ar ctxengineid +.Op Fl e Ar secengineid +.Op Fl n Ar ctxname +.Op Fl O Cm afnQqSvx .Op Fl r Ar retries .Op Fl t Ar timeout +.Op Fl u Ar user .Op Fl v Ar version -.Op Fl O Cm afnQqSvx +.Op Fl Z Ar boots , Ns Ar time .Op Fl C Cm cIipt .Op Fl C Cm E Ar endoid .Ar agent @@ -44,29 +54,44 @@ .Nm .Cm bulkget .Op Fl c Ar community +.Op Fl E Ar ctxengineid +.Op Fl e Ar secengineid +.Op Fl n Ar ctxname +.Op Fl O Cm afnQqSvx .Op Fl r Ar retries .Op Fl t Ar timeout +.Op Fl u Ar user .Op Fl v Ar version -.Op Fl O Cm afnQqSvx +.Op Fl Z Ar boots , Ns Ar time .Op Fl C Cm n Ns Ar nonrep Ns Cm r Ns Ar maxrep .Ar agent .Ar oid ... .Nm .Cm bulkwalk .Op Fl c Ar community +.Op Fl E Ar ctxengineid +.Op Fl e Ar secengineid +.Op Fl n Ar ctxname +.Op Fl O Cm afnQqSvx .Op Fl r Ar retries .Op Fl t Ar timeout +.Op Fl u Ar user .Op Fl v Ar version -.Op Fl O Cm afnQqSvx +.Op Fl Z Ar boots , Ns Ar time .Op Fl C Cm cipn Ns Ar nonrep Ns Cm r Ns Ar maxrep .Ar agent .Op Ar oid .Nm .Cm trap .Op Fl c Ar community +.Op Fl E Ar ctxengineid +.Op Fl e Ar secengineid +.Op Fl n Ar ctxname .Op Fl r Ar retries .Op Fl t Ar timeout +.Op Fl u Ar user .Op Fl v Ar version +.Op Fl Z Ar boots , Ns Ar time .Ar agent uptime trapoid .Oo Ar varoid type value Oc ... .Nm @@ -220,6 +245,26 @@ Set the string. Defaults to .Cm public . +This option is only used by +.Fl v Cm 1 +and +.Fl v Cm 2c . +.It Fl e Ar secengineid +The USM security engine id. +Under normal circumstances this value is discovered via snmpv3 discovery and +does not need to be specified. +This option is only used by +.Fl v Cm 3 . +.It Fl E Ar ctxengineid +The snmpv3 context engine id. +Most of the time this value can be safely ignored. +This option is only used by +.Fl v Cm 3 . +.It Fl n Ar ctxname +Sets the context name. +Defaults to an empty string. +This option is only used by +.Fl v Cm 3 . .It Fl O Ar output Set the .Ar output @@ -256,15 +301,29 @@ Set the .Ar timeout to wait for a reply, in seconds. Defaults to 1. +.It Fl u Ar user +Sets the username. +If +.Fl v Cm 3 +is used this option is required. +This option is only used by +.Fl v Cm 3 . .It Fl v Ar version Set the snmp protocol .Ar version to either -.Cm 1 +.Cm 1 , +.Cm 2c or -.Cm 2c . +.Cm 3 . Currently defaults to .Cm 2c . +.It Fl Z Ar boots , Ns Ar time +Set the engine boots and engine time. +Under normal circumstances this value is discovered via snmpv3 discovery and +does not need to be specified. +This option is only used by +.Fl v Cm 3 . .El .Pp The syntax for the diff --git a/usr.bin/snmp/snmp.c b/usr.bin/snmp/snmp.c index af2e66d82f7..1ccac891b5e 100644 --- a/usr.bin/snmp/snmp.c +++ b/usr.bin/snmp/snmp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmp.c,v 1.3 2019/09/18 09:44:38 martijn Exp $ */ +/* $OpenBSD: snmp.c,v 1.4 2019/09/18 09:48:14 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org> @@ -30,12 +30,55 @@ #include "smi.h" #include "snmp.h" +#define UDP_MAXPACKET 65535 + static struct ber_element * snmp_resolve(struct snmp_agent *, struct ber_element *, int); static char * snmp_package(struct snmp_agent *, struct ber_element *, size_t *); static struct ber_element * snmp_unpackage(struct snmp_agent *, char *, size_t); +static void snmp_v3_free(struct snmp_v3 *); + +struct snmp_v3 * +snmp_v3_init(int level, const char *ctxname, size_t ctxnamelen, + struct snmp_sec *sec) +{ + struct snmp_v3 *v3; + + if ((level & (SNMP_MSGFLAG_SECMASK | SNMP_MSGFLAG_REPORT)) != level || + sec == NULL) { + errno = EINVAL; + return NULL; + } + if ((v3 = calloc(1, sizeof(*v3))) == NULL) + return NULL; + + v3->level = level | SNMP_MSGFLAG_REPORT; + v3->ctxnamelen = ctxnamelen; + if (ctxnamelen != 0) { + if ((v3->ctxname = malloc(ctxnamelen)) == NULL) { + free(v3); + return NULL; + } + memcpy(v3->ctxname, ctxname, ctxnamelen); + } + v3->sec = sec; + return v3; +} + +int +snmp_v3_setengineid(struct snmp_v3 *v3, char *engineid, size_t engineidlen) +{ + if (v3->engineidset) + free(v3->engineid); + if ((v3->engineid = malloc(engineidlen)) == NULL) + return -1; + memcpy(v3->engineid, engineid, engineidlen); + v3->engineidlen = engineidlen; + v3->engineidset = 1; + return 0; +} struct snmp_agent * snmp_connect_v12(int fd, enum snmp_version version, const char *community) @@ -54,21 +97,54 @@ snmp_connect_v12(int fd, enum snmp_version version, const char *community) goto fail; agent->timeout = 1; agent->retries = 5; + agent->v3 = NULL; return agent; fail: - free(agent->community); free(agent); return NULL; } +struct snmp_agent * +snmp_connect_v3(int fd, struct snmp_v3 *v3) +{ + struct snmp_agent *agent; + + if ((agent = malloc(sizeof(*agent))) == NULL) + return NULL; + agent->fd = fd; + agent->version = SNMP_V3; + agent->v3 = v3; + agent->timeout = 1; + agent->retries = 5; + agent->community = NULL; + + if (v3->sec->init(agent) == -1) { + snmp_free_agent(agent); + return NULL; + } + return agent; +} + void snmp_free_agent(struct snmp_agent *agent) { free(agent->community); + if (agent->v3 != NULL) + snmp_v3_free(agent->v3); free(agent); } +static void +snmp_v3_free(struct snmp_v3 *v3) +{ + v3->sec->free(v3->sec->data); + free(v3->sec); + free(v3->ctxname); + free(v3->engineid); + free(v3); +} + struct ber_element * snmp_get(struct snmp_agent *agent, struct ber_oid *oid, size_t len) { @@ -253,7 +329,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply) tries--; continue; } - if (rreqid != reqid) { + if (rreqid != reqid && rreqid != 0) { errno = EPROTO; direction = POLLOUT; tries--; @@ -265,7 +341,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply) errno = EPROTO; direction = POLLOUT; tries--; - break; + continue; } } @@ -282,9 +358,10 @@ static char * snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len) { struct ber ber; - struct ber_element *message; - ssize_t ret; - char *packet = NULL; + struct ber_element *message, *scopedpdu = NULL; + ssize_t securitysize, ret; + char *securityparams = NULL, *packet = NULL; + long long msgid; bzero(&ber, sizeof(ber)); ber_set_application(&ber, smi_application); @@ -304,6 +381,29 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len) } break; case SNMP_V3: + msgid = arc4random_uniform(2147483647); + if ((scopedpdu = ber_add_sequence(NULL)) == NULL) { + ber_free_elements(pdu); + goto fail; + } + if (ber_printf_elements(scopedpdu, "xxe", + agent->v3->engineid, agent->v3->engineidlen, + agent->v3->ctxname, agent->v3->ctxnamelen, pdu) == NULL) { + ber_free_elements(pdu); + ber_free_elements(scopedpdu); + goto fail; + } + pdu = NULL; + if ((securityparams = agent->v3->sec->genparams(agent, + &securitysize)) == NULL) { + ber_free_elements(scopedpdu); + goto fail; + } + if (ber_printf_elements(message, "d{idxd}xe", + agent->version, msgid, UDP_MAXPACKET, &(agent->v3->level), + (size_t) 1, agent->v3->sec->model, securityparams, + securitysize, scopedpdu) == NULL) + goto fail; break; } @@ -316,6 +416,7 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len) fail: ber_free_elements(message); + free(securityparams); return packet; } @@ -326,7 +427,14 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen) enum snmp_version version; char *community; struct ber_element *pdu; - struct ber_element *message = NULL, *payload; + long long msgid, model; + int msgsz; + char *msgflags, *secparams; + size_t msgflagslen, secparamslen; + struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname; + off_t secparamsoffset; + char *engineid; + size_t engineidlen; bzero(&ber, sizeof(ber)); ber_set_application(&ber, smi_application); @@ -342,8 +450,7 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen) if (version != agent->version) goto fail; - switch (version) - { + switch (version) { case SNMP_V1: case SNMP_V2C: if (ber_scanf_elements(payload, "se", &community, &pdu) == -1) @@ -354,7 +461,34 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen) ber_free_elements(message); return pdu; case SNMP_V3: - break; + if (ber_scanf_elements(payload, "{idxi}pxe", &msgid, &msgsz, + &msgflags, &msgflagslen, &model, &secparamsoffset, + &secparams, &secparamslen, &scopedpdu) == -1) + goto fail; + if (msgflagslen != 1) + goto fail; + if (agent->v3->sec->parseparams(agent, buf, buflen, + secparamsoffset, secparams, secparamslen, + msgflags[0]) == -1) + goto fail; + if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid, + &engineidlen, &ctxname) == -1) + goto fail; + if (!agent->v3->engineidset) { + if (snmp_v3_setengineid(agent->v3, engineid, + engineidlen) == -1) + goto fail; + } + pdu = ber_unlink_elements(ctxname); + /* Accept reports, so we can continue if possible */ + if (pdu->be_type != SNMP_C_REPORT) { + if ((msgflags[0] & SNMP_MSGFLAG_SECMASK) != + (agent->v3->level & SNMP_MSGFLAG_SECMASK)) + goto fail; + } + + ber_free_elements(message); + return pdu; } /* NOTREACHED */ diff --git a/usr.bin/snmp/snmp.h b/usr.bin/snmp/snmp.h index 81034c95096..4895f4cd3cb 100644 --- a/usr.bin/snmp/snmp.h +++ b/usr.bin/snmp/snmp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmp.h,v 1.2 2019/09/18 09:44:38 martijn Exp $ */ +/* $OpenBSD: snmp.h,v 1.3 2019/09/18 09:48:14 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org> @@ -108,12 +108,37 @@ enum snmp_security_model { SNMP_SEC_TSM = 4 }; +struct snmp_agent; + +struct snmp_sec { + enum snmp_security_model model; + int (*init)(struct snmp_agent *); + char *(*genparams)(struct snmp_agent *, size_t *); + int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *, + size_t, uint8_t); + void (*free)(void *); + void *data; +}; + +struct snmp_v3 { + uint8_t level; + char *ctxname; + size_t ctxnamelen; + int engineidset; + char *engineid; + size_t engineidlen; + struct snmp_sec *sec; +}; + struct snmp_agent { int fd; - enum snmp_version version; - char *community; int timeout; int retries; + enum snmp_version version; +/* SNMP_V1 & SNMP_V2C */ + char *community; +/* SNMP_V3 */ + struct snmp_v3 *v3; }; #define SNMP_MSGFLAG_AUTH 0x01 @@ -123,7 +148,10 @@ struct snmp_agent { #define SNMP_MAX_TIMEWINDOW 150 /* RFC3414 */ +struct snmp_v3 *snmp_v3_init(int, const char *, size_t, struct snmp_sec *); +int snmp_v3_setengineid(struct snmp_v3 *, char *, size_t); struct snmp_agent *snmp_connect_v12(int, enum snmp_version, const char *); +struct snmp_agent *snmp_connect_v3(int, struct snmp_v3 *); void snmp_free_agent(struct snmp_agent *); struct ber_element * snmp_get(struct snmp_agent *agent, struct ber_oid *oid, size_t len); diff --git a/usr.bin/snmp/snmpc.c b/usr.bin/snmp/snmpc.c index d49c272d6bc..4150d9d11b1 100644 --- a/usr.bin/snmp/snmpc.c +++ b/usr.bin/snmp/snmpc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpc.c,v 1.8 2019/09/18 09:44:38 martijn Exp $ */ +/* $OpenBSD: snmpc.c,v 1.9 2019/09/18 09:48:14 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org> @@ -25,6 +25,7 @@ #include <arpa/inet.h> #include <ber.h> +#include <ctype.h> #include <err.h> #include <errno.h> #include <netdb.h> @@ -38,8 +39,9 @@ #include "smi.h" #include "snmp.h" +#include "usm.h" -#define GETOPT_COMMON "c:r:t:v:O:" +#define GETOPT_COMMON "c:E:e:n:O:r:t:u:v:Z:" int snmpc_get(int, char *[]); int snmpc_walk(int, char *[]); @@ -49,6 +51,7 @@ struct snmp_agent *snmpc_connect(char *, char *); int snmpc_parseagent(char *, char *); int snmpc_print(struct ber_element *); __dead void snmpc_printerror(enum snmp_error, char *); +char *snmpc_hex2bin(char *, size_t *); void usage(void); struct snmp_app { @@ -71,10 +74,11 @@ struct snmp_app snmp_apps[] = { struct snmp_app *snmp_app = NULL; char *community = "public"; +struct snmp_v3 *v3; char *mib = "mib_2"; int retries = 5; int timeout = 1; -int version = SNMP_V2C; +enum snmp_version version = SNMP_V2C; int print_equals = 1; int print_varbind_only = 0; int print_summary = 0; @@ -92,6 +96,14 @@ enum smi_output_string output_string = smi_os_default; int main(int argc, char *argv[]) { + struct snmp_sec *sec; + char *user = NULL; + int seclevel = SNMP_MSGFLAG_REPORT; + char *ctxname = NULL; + char *ctxengineid = NULL, *secengineid = NULL; + size_t ctxengineidlen, secengineidlen; + int zflag = 0; + long long boots, time; char optstr[BUFSIZ]; const char *errstr; char *strtolp; @@ -134,6 +146,29 @@ main(int argc, char *argv[]) case 'c': community = optarg; break; + case 'E': + ctxengineid = snmpc_hex2bin(optarg, + &ctxengineidlen); + if (ctxengineid == NULL) { + if (errno == EINVAL) + errx(1, "Bad engine ID value " + "after -3E flag."); + err(1, "-3E"); + } + break; + case 'e': + secengineid = snmpc_hex2bin(optarg, + &secengineidlen); + if (secengineid == NULL) { + if (errno == EINVAL) + errx(1, "Bad engine ID value " + "after -3e flag."); + err(1, "-3e"); + } + break; + case 'n': + ctxname = optarg; + break; case 'r': if ((retries = strtonum(optarg, 0, INT_MAX, &errstr)) == 0) { @@ -148,11 +183,16 @@ main(int argc, char *argv[]) errx(1, "-t: %s argument", errstr); } break; + case 'u': + user = optarg; + break; case 'v': if (strcmp(optarg, "1") == 0) version = SNMP_V1; else if (strcmp(optarg, "2c") == 0) version = SNMP_V2C; + else if (strcmp(optarg, "3") == 0) + version = SNMP_V3; else errc(1, EINVAL, "-v"); break; @@ -283,6 +323,18 @@ main(int argc, char *argv[]) } } break; + case 'Z': + boots = strtoll(optarg, &strtolp, 10); + if (boots < 0 || strtolp == optarg || strtolp[0] != ',') + usage(); + strtolp++; + while (strtolp[0] == ' ' && strtolp[0] == '\t') + strtolp++; + time = strtoll(strtolp, &strtolp, 10); + if (boots < 0 || strtolp == optarg) + usage(); + zflag = 1; + break; default: usage(); } @@ -290,6 +342,32 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; + if (version == SNMP_V3) { + /* Setup USM */ + if (user == NULL || user[0] == '\0') + errx(1, "No securityName specified"); + if ((sec = usm_init(user, strlen(user))) == NULL) + err(1, "usm_init"); + if (secengineid != NULL) { + if (usm_setengineid(sec, secengineid, + secengineidlen) == -1) + err(1, "Can't set secengineid"); + } + if (zflag) + if (usm_setbootstime(sec, boots, time) == -1) + err(1, "Can't set boots/time"); + v3 = snmp_v3_init(seclevel, ctxname, ctxname == NULL ? 0 : + strlen(ctxname), sec); + if (v3 == NULL) + err(1, "snmp_v3_init"); + if (ctxengineid != NULL) { + if (snmp_v3_setengineid(v3, ctxengineid, + ctxengineidlen) == -1) + err(1, "Can't set ctxengineid"); + } + } + + return snmp_app->exec(argc, argv); } @@ -301,6 +379,8 @@ snmpc_get(int argc, char *argv[]) struct snmp_agent *agent; int errorstatus, errorindex; int i; + int class; + unsigned type; if (argc < 2) usage(); @@ -338,12 +418,14 @@ snmpc_get(int argc, char *argv[]) err(1, "get"); } - (void) ber_scanf_elements(pdu, "{Sdd{e", &errorstatus, &errorindex, - &varbind); + (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, &errorstatus, + &errorindex, &varbind); if (errorstatus != 0) snmpc_printerror((enum snmp_error) errorstatus, argv[errorindex - 1]); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + printf("Received report:\n"); for (; varbind != NULL; varbind = varbind->be_next) { if (!snmpc_print(varbind)) err(1, "Can't print response"); @@ -364,6 +446,8 @@ snmpc_walk(int argc, char *argv[]) char oidstr[SNMP_MAX_OID_STRLEN]; int n = 0, prev_cmp; int errorstatus, errorindex; + int class; + unsigned type; if (strcmp(snmp_app->name, "bulkwalk") == 0 && version < SNMP_V2C) errx(1, "Cannot send V2 PDU on V1 session"); @@ -388,15 +472,19 @@ snmpc_walk(int argc, char *argv[]) if ((pdu = snmp_get(agent, &oid, 1)) == NULL) err(1, "%s", snmp_app->name); - (void) ber_scanf_elements(pdu, "{Sdd{e", &errorstatus, - &errorindex, &varbind); + (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, + &errorstatus, &errorindex, &varbind); if (errorstatus != 0) snmpc_printerror((enum snmp_error) errorstatus, argv[errorindex - 1]); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + printf("Received report:\n"); if (!snmpc_print(varbind)) err(1, "Can't print response"); ber_free_element(pdu); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + return 1; n++; } while (1) { @@ -410,14 +498,16 @@ snmpc_walk(int argc, char *argv[]) err(1, "walk"); } - (void) ber_scanf_elements(pdu, "{Sdd{e", &errorstatus, - &errorindex, &varbind); + (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, + &errorstatus, &errorindex, &varbind); if (errorstatus != 0) { smi_oid2string(&noid, oidstr, sizeof(oidstr), oid_lookup); snmpc_printerror((enum snmp_error) errorstatus, oidstr); } + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + printf("Received report:\n"); for (; varbind != NULL; varbind = varbind->be_next) { (void) ber_scanf_elements(varbind, "{oe}", &noid, &value); @@ -438,6 +528,8 @@ snmpc_walk(int argc, char *argv[]) n++; } ber_free_elements(pdu); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + return 1; if (varbind != NULL) break; } @@ -445,15 +537,19 @@ snmpc_walk(int argc, char *argv[]) if ((pdu = snmp_get(agent, &oid, 1)) == NULL) err(1, "%s", snmp_app->name); - (void) ber_scanf_elements(pdu, "{Sdd{e", &errorstatus, - &errorindex, &varbind); + (void) ber_scanf_elements(pdu, "t{Sdd{e", &class, &type, + &errorstatus, &errorindex, &varbind); if (errorstatus != 0) snmpc_printerror((enum snmp_error) errorstatus, argv[errorindex - 1]); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + printf("Received report:\n"); if (!snmpc_print(varbind)) err(1, "Can't print response"); ber_free_element(pdu); + if (class == BER_CLASS_CONTEXT && type == SNMP_C_REPORT) + return 1; n++; } if (print_time) @@ -697,6 +793,8 @@ snmpc_connect(char *host, char *port) case SNMP_V2C: return snmp_connect_v12(snmpc_parseagent(host, port), version, community); + case SNMP_V3: + return snmp_connect_v3(snmpc_parseagent(host, port), v3); } return NULL; } @@ -883,18 +981,60 @@ snmpc_parseagent(char *agent, char *defaultport) return s; } +char * +snmpc_hex2bin(char *hexstr, size_t *binlen) +{ + char *decstr; + + if (hexstr[0] == '0' && hexstr[1] == 'x') + hexstr += 2; + while (hexstr[0] == ' ' || hexstr[0] == '\t') + hexstr++; + + if ((decstr = malloc((strlen(hexstr) / 2) + 1)) == NULL) + return NULL; + + for (*binlen = 0; hexstr[0] != '\0'; (*binlen)++) { + hexstr[0] = toupper(hexstr[0]); + hexstr[1] = toupper(hexstr[1]); + if (hexstr[0] >= '0' && hexstr[0] <= '9') + decstr[*binlen] = (hexstr[0] - '0') << 4; + else if (hexstr[0] >= 'A' && hexstr[0] <= 'F') + decstr[*binlen] = ((hexstr[0] - 'A') + 10) << 4; + else + goto fail; + if (hexstr[1] >= '0' && hexstr[1] <= '9') + decstr[*binlen] |= (hexstr[1] - '0'); + else if (hexstr[1] >= 'A' && hexstr[1] <= 'F') + decstr[*binlen] |= (hexstr[1] - 'A') + 10; + else + goto fail; + + hexstr += 2; + while (hexstr[0] == ' ' || hexstr[0] == '\t') + hexstr++; + } + + return decstr; +fail: + errno = EINVAL; + free(decstr); + return NULL; +} + __dead void usage(void) { size_t i; if (snmp_app != NULL) { - fprintf(stderr, "usage: snmp %s%s%s%s\n", + fprintf(stderr, "usage: snmp %s%s%s\n", snmp_app->name, snmp_app->usecommonopt ? - " [-c community] [-r retries] [-t timeout] [-v version]\n" - " [-O afnqvxSQ]" : "", - snmp_app->usage == NULL ? "" : " ", + " [-c community] [-e secengineid] [-E ctxengineid] [-n ctxname]\n" + " [-O afnqvxSQ] [-r retries] [-t timeout] [-u user] [-v version]\n" + " [-Z boots,time]\n" + " " : "", snmp_app->usage == NULL ? "" : snmp_app->usage); exit(1); } @@ -906,8 +1046,7 @@ usage(void) fprintf(stderr, "snmp %s%s %s\n", snmp_apps[i].name, snmp_apps[i].usecommonopt ? - " [-c community] [-r retries] [-t timeout] [-v version]\n" - " [-O afnqvxSQ]" : "", + " [common options]" : "", snmp_apps[i].usage ? snmp_apps[i].usage : ""); } exit(1); diff --git a/usr.bin/snmp/usm.c b/usr.bin/snmp/usm.c new file mode 100644 index 00000000000..6fa960797b5 --- /dev/null +++ b/usr.bin/snmp/usm.c @@ -0,0 +1,275 @@ +/* $OpenBSD: usm.c,v 1.1 2019/09/18 09:48:14 martijn Exp $ */ + +/* + * Copyright (c) 2019 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/time.h> + +#include <openssl/evp.h> +#include <openssl/hmac.h> + +#include <ber.h> +#include <errno.h> +#include <string.h> +#include <time.h> + +#include "smi.h" +#include "snmp.h" +#include "usm.h" + +#define USM_MAX_DIGESTLEN 48 +#define USM_MAX_TIMEWINDOW 150 +#define USM_SALTOFFSET 8 + +struct usm_sec { + struct snmp_sec snmp; + char *user; + size_t userlen; + int engineidset; + char *engineid; + size_t engineidlen; + int bootsset; + uint32_t boots; + int timeset; + uint32_t time; + struct timespec timecheck; +}; + +static int usm_doinit(struct snmp_agent *); +static char *usm_genparams(struct snmp_agent *, size_t *); +static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *, + size_t, uint8_t); +static void usm_free(void *); + +struct snmp_sec * +usm_init(const char *user, size_t userlen) +{ + struct snmp_sec *sec; + struct usm_sec *usm; + + if (user == NULL || user[0] == '\0') { + errno = EINVAL; + return NULL; + } + + if ((sec = malloc(sizeof(*sec))) == NULL) + return NULL; + + if ((usm = calloc(1, sizeof(struct usm_sec))) == NULL) { + free(sec); + return NULL; + } + if ((usm->user = malloc(userlen)) == NULL) { + free(sec); + free(usm); + return NULL; + } + memcpy(usm->user, user, userlen); + usm->userlen = userlen; + + sec->model = SNMP_SEC_USM; + sec->init = usm_doinit; + sec->genparams = usm_genparams; + sec->parseparams = usm_parseparams; + sec->free = usm_free; + sec->data = usm; + return sec; +} + +static int +usm_doinit(struct snmp_agent *agent) +{ + struct ber_element *ber; + struct usm_sec *usm = agent->v3->sec->data; + int level; + size_t userlen; + + if (usm->engineidset && usm->bootsset && usm->timeset) + return 0; + + level = agent->v3->level; + agent->v3->level = SNMP_MSGFLAG_REPORT; + userlen = usm->userlen; + usm->userlen = 0; + + if ((ber = snmp_get(agent, NULL, 0)) == NULL) { + agent->v3->level = level; + usm->userlen = userlen; + return -1; + } + ber_free_element(ber); + + agent->v3->level = level; + usm->userlen = userlen; + + return 0; +} + +static char * +usm_genparams(struct snmp_agent *agent, size_t *len) +{ + struct ber ber; + struct ber_element *params; + struct usm_sec *usm = agent->v3->sec->data; + char *secparams = NULL; + ssize_t berlen = 0; + struct timespec now, timediff; + uint32_t boots, time; + + if (usm->timeset) { + if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) + return NULL; + timespecsub(&now, &(usm->timecheck), &timediff); + time = usm->time + timediff.tv_sec; + } else + time = 0; + boots = usm->boots; + + if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid, + usm->engineidlen, boots, time, usm->user, usm->userlen, NULL, + (size_t) 0, NULL, (size_t) 0)) == NULL) + return NULL; + + bzero(&ber, sizeof(ber)); + ber_set_application(&ber, smi_application); + if (ber_write_elements(&ber, params) != -1) + berlen = ber_copy_writebuf(&ber, (void **)&secparams); + + *len = berlen; + ber_free_element(params); + ber_free(&ber); + return secparams; +} + +static int +usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, + off_t secparamsoffset, char *buf, size_t buflen, uint8_t level) +{ + struct usm_sec *usm = agent->v3->sec->data; + struct ber ber; + struct ber_element *secparams; + char *engineid, *user; + size_t engineidlen, userlen; + struct timespec now, timediff; + uint32_t boots, time; + + bzero(&ber, sizeof(ber)); + + ber_set_application(&ber, smi_application); + ber_set_readbuf(&ber, buf, buflen); + if ((secparams = ber_read_elements(&ber, NULL)) == NULL) + return -1; + ber_free(&ber); + + if (ber_scanf_elements(secparams, "{xddxSS}", &engineid, &engineidlen, + &boots, &time, &user, &userlen) == -1) + goto fail; + + if (!usm->engineidset) { + if (usm_setengineid(agent->v3->sec, engineid, + engineidlen) == -1) + goto fail; + } else { + if (usm->engineidlen != engineidlen) + goto fail; + if (memcmp(usm->engineid, engineid, engineidlen) != 0) + goto fail; + } + + if (!usm->bootsset) { + usm->boots = boots; + usm->bootsset = 1; + } else { + if (boots < usm->boots) + goto fail; + if (boots > usm->boots) { + usm->bootsset = 0; + usm->timeset = 0; + usm_doinit(agent); + goto fail; + } + } + + if (!usm->timeset) { + usm->time = time; + if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1) + goto fail; + usm->timeset = 1; + } else { + if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) + goto fail; + timespecsub(&now, &(usm->timecheck), &timediff); + if (time < usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW || + time > usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) { + usm->bootsset = 0; + usm->timeset = 0; + usm_doinit(agent); + goto fail; + } + } + + if (userlen != usm->userlen || + memcmp(user, usm->user, userlen) != 0) + goto fail; + + ber_free_element(secparams); + return 0; + +fail: + ber_free_element(secparams); + return -1; +} + +static void +usm_free(void *data) +{ + struct usm_sec *usm = data; + + free(usm->user); + free(usm->engineid); + free(usm); +} + +int +usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen) +{ + struct usm_sec *usm = sec->data; + + if (usm->engineid != NULL) + free(usm->engineid); + if ((usm->engineid = malloc(engineidlen)) == NULL) + return -1; + memcpy(usm->engineid, engineid, engineidlen); + usm->engineidlen = engineidlen; + usm->engineidset = 1; + + return 0; +} + +int +usm_setbootstime(struct snmp_sec *sec, uint32_t boots, uint32_t time) +{ + struct usm_sec *usm = sec->data; + + if (clock_gettime(CLOCK_MONOTONIC, &(usm->timecheck)) == -1) + return -1; + + usm->boots = boots; + usm->bootsset = 1; + usm->time = time; + usm->timeset = 1; + return 0; +} diff --git a/usr.bin/snmp/usm.h b/usr.bin/snmp/usm.h new file mode 100644 index 00000000000..aa84290992a --- /dev/null +++ b/usr.bin/snmp/usm.h @@ -0,0 +1,23 @@ +/* $OpenBSD: usm.h,v 1.1 2019/09/18 09:48:14 martijn Exp $ */ + +/* + * Copyright (c) 2019 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 "snmp.h" + +struct snmp_sec *usm_init(const char *, size_t); +int usm_setengineid(struct snmp_sec *, char *, size_t); +int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t); |