snmp(1): Add SNMPv3/USM support [0/5]

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

snmp(1): Add SNMPv3/USM support [0/5]

Martijn van Duren-5
Hello tech@,

I've worked hard to get SNMPv3 support for snmp(1). Here's the end
result. This mail contains the full diff for people just wanting to
test, the follow up mails will contain the incremental diffs (still
massive beasts) so they're easier to review.

I've implemented Net-SNMP's -A, -a, -E, -e, -3K (as -K), -3k (as -k),
-l, -n, -u, -X, -x and -Z. I choose to leave out the -3 because the
way to handle this is really ugly and -K and -k are unused by
Net-SNMP.
This is also the reason there's no support for master keys, because -m
and -M are used by Net-SNMP and I don't know if I want to implement that
at some point and I haven't seen a device that needs the master key. The
scaffolding for adding master key support is there and if someone has
an actual usecase for it and comes up with a good suggestion for a flag
I'll be happy to implement it.

Tested with snmpd(8), netsnmpd and HP Laserjet 4730mfp by me and
netgear GS724Tv4 ProSafe by semarie@ on previous iteration of diff.

Tests, feedback, OKs welcome.

martijn@

diff --git a/Makefile b/Makefile
index 62bb556..102582b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 # $OpenBSD: Makefile,v 1.1 2019/08/09 06:17:59 martijn Exp $
 
 PROG= snmp
-SRCS= mib.c smi.c snmp.c snmpc.c
-LDADD+= -lutil
-DPADD+= ${LIBUTIL}
+SRCS= mib.c smi.c snmp.c snmpc.c usm.c
+LDADD+= -lcrypto -lutil
+DPADD+= ${LIBCRYPTO} ${LIBUTIL}
 
 MAN= snmp.1
 
diff --git a/snmp.1 b/snmp.1
index e158ba0..fe283a5 100644
--- a/snmp.1
+++ b/snmp.1
@@ -23,50 +23,110 @@
 .Sh SYNOPSIS
 .Nm
 .Cm get | getnext
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
+.Op Fl E Ar ctxengineid
+.Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
+.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 X Ar privpass
+.Op Fl x Ar cipher
+.Op Fl Z Ar boots , Ns Ar time
 .Ar agent
 .Ar oid ...
 .Nm
 .Cm walk
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
+.Op Fl E Ar ctxengineid
+.Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
+.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 X Ar privpass
+.Op Fl x Ar cipher
+.Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm cIipt
 .Op Fl C Cm E Ar endoid
 .Ar agent
 .Op Ar oid
 .Nm
 .Cm bulkget
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
+.Op Fl E Ar ctxengineid
+.Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
+.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 X Ar privpass
+.Op Fl x Ar cipher
+.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 A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
+.Op Fl E Ar ctxengineid
+.Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
+.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 X Ar privpass
+.Op Fl x Ar cipher
+.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 A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
+.Op Fl E Ar ctxengineid
+.Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
+.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 X Ar privpass
+.Op Fl x Ar cipher
+.Op Fl Z Ar boots , Ns Ar time
 .Ar agent uptime trapoid
 .Oo Ar varoid type value Oc ...
 .Nm
@@ -145,6 +205,28 @@ Dump the tree of compiled-in MIB objects.
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
+.It Fl A Ar authpass
+The authentication password for the user.
+This will be transformed to
+.Ar localauth .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl a Ar digest
+Set the digest
+.Pq authentication
+protocol.
+Options are
+.Cm MD5 ,
+.Cm SHA ,
+.Cm SHA-224 ,
+.Cm SHA-256 ,
+.Cm SHA-384
+or
+.Cm SHA-512 .
+This option defaults to
+.Cm MD5 .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl C Ar appopt
 Set the application specific
 .Ar appopt
@@ -220,6 +302,66 @@ 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 K Ar localpriv
+The localized privacy password for the user in hexadecimal format
+.Po
+optionally prefixed with a
+.Cm 0x
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl k Ar localauth
+The localized authentication password for the user in hexadecimal format
+.Po
+optionally prefixed with a
+.Cm 0x
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl l Ar seclevel
+The security level.
+Values can be
+.Cm noAuthNoPriv Pq default ,
+.Cm authNoPriv
+.Po
+requires either
+.Fl A
+or
+.Fl k
+.Pc
+or
+.Cm authPriv
+.Po
+requires either
+.Fl X
+or
+.Fl K
+in addition to the
+.Cm authNoPriv
+requirements
+.Pc .
+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 +398,45 @@ 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 X Ar privpass
+The privacy password for the user.
+This will be tansformed to
+.Ar localpriv .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl x Ar cipher
+Sets the cipher
+.Pq privacy
+protocol.
+Options are
+.Cm DES
+and
+.Cm AES .
+This option is only used by
+.Fl v Cm 3 .
+.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/snmp.c b/snmp.c
index 7fac777..ba4dc3c 100644
--- a/snmp.c
+++ b/snmp.c
@@ -32,6 +32,52 @@
 
 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 *);
+static void snmp_v3_secparamsoffset(void *, size_t);
+
+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)
@@ -50,21 +96,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)
 {
@@ -171,19 +250,16 @@ fail:
 static struct ber_element *
 snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
 {
- struct ber_element *message, *varbind;
+ struct ber_element *varbind;
  struct ber_oid oid;
  struct timespec start, now;
  struct pollfd pfd;
- struct ber ber;
+ char *message;
  ssize_t len;
  long long reqid, rreqid;
- long long version;
- char *community;
  short direction;
  int to, nfds, ret;
  int tries;
- void *ptr;
  char buf[READ_BUF_SIZE];
 
  if (ber_scanf_elements(pdu, "{i", &reqid) != 0) {
@@ -192,23 +268,8 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  return NULL;
  }
 
- if ((message = ber_add_sequence(NULL)) == NULL) {
- ber_free_elements(pdu);
+ if ((message = snmp_package(agent, pdu, &len)) == NULL)
  return NULL;
- }
- if (ber_printf_elements(message, "dse", agent->version,
-    agent->community, pdu) == NULL) {
- ber_free_elements(pdu);
- ber_free_elements(message);
- return NULL;
- }
- memset(&ber, 0, sizeof(ber));
- ber_set_application(&ber, smi_application);
- len = ber_write_elements(&ber, message);
- ber_free_elements(message);
- message = NULL;
- if (ber_get_writebuf(&ber, &ptr) < 1)
- goto fail;
 
  clock_gettime(CLOCK_MONOTONIC, &start);
  memcpy(&now, &start, sizeof(now));
@@ -236,7 +297,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  goto fail;
  }
  if (direction == POLLOUT) {
- ret = send(agent->fd, ptr, len, MSG_DONTWAIT);
+ ret = send(agent->fd, message, len, MSG_DONTWAIT);
  if (ret == -1)
  goto fail;
  if (ret < len) {
@@ -253,25 +314,10 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  errno = ECONNRESET;
  if (ret <= 0)
  goto fail;
- ber_set_readbuf(&ber, buf, ret);
- if ((message = ber_read_elements(&ber, NULL)) == NULL) {
- direction = POLLOUT;
+ if ((pdu = snmp_unpackage(agent, buf, ret)) == NULL) {
  tries--;
- continue;
- }
- if (ber_scanf_elements(message, "{ise", &version, &community,
-    &pdu) != 0) {
- errno = EPROTO;
  direction = POLLOUT;
- tries--;
- continue;
- }
- /* Skip invalid packets; should not happen */
- if (version != agent->version ||
-    strcmp(community, agent->community) != 0) {
  errno = EPROTO;
- direction = POLLOUT;
- tries--;
  continue;
  }
  /* Validate pdu format and check request id */
@@ -282,7 +328,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--;
@@ -294,20 +340,202 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  errno = EPROTO;
  direction = POLLOUT;
  tries--;
- break;
+ continue;
  }
  }
- if (varbind != NULL)
- continue;
 
- ber_unlink_elements(message->be_sub->be_next);
- ber_free_elements(message);
- ber_free(&ber);
+ free(message);
  return pdu;
  }
 
 fail:
+ free(message);
+ return NULL;
+}
+
+static char *
+snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
+{
+ struct ber ber;
+ struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
+ ssize_t securitysize, ret;
+ size_t secparamsoffset;
+ char *securityparams = NULL, *buf, *packet = NULL;
+ long long msgid;
+ void *cookie = NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+
+ if ((message = ber_add_sequence(NULL)) == NULL) {
+ ber_free_elements(pdu);
+ goto fail;
+ }
+
+ switch (agent->version) {
+ case SNMP_V1:
+ case SNMP_V2C:
+ if (ber_printf_elements(message, "dse", agent->version,
+    agent->community, pdu) == NULL) {
+ ber_free_elements(pdu);
+ goto fail;
+ }
+ 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, &cookie)) == NULL) {
+ ber_free_elements(scopedpdu);
+ goto fail;
+ }
+ if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
+ if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
+    cookie)) == NULL)
+ goto fail;
+ ber_free_elements(scopedpdu);
+ scopedpdu = encpdu;
+ }
+ if (ber_printf_elements(message, "d{idxd}xe",
+    agent->version, msgid, 1472, &(agent->v3->level),
+    (size_t) 1, agent->v3->sec->model, securityparams,
+    securitysize, scopedpdu) == NULL)
+ goto fail;
+ if (ber_scanf_elements(message, "{SSe", &secparams) == -1)
+ goto fail;
+ ber_set_writecallback(secparams, snmp_v3_secparamsoffset,
+    &secparamsoffset);
+ break;
+ }
+
+ if ((ret = ber_write_elements(&ber, message)) == -1)
+ goto fail;
+ *len = (size_t) ret;
+ if (ber_get_writebuf(&ber, (void **)&buf) != -1 &&
+    (packet = malloc(ret)) != NULL)
+ memcpy(packet, buf, ret);
+ ber_free(&ber);
+
+ if (agent->version == SNMP_V3 && packet != NULL) {
+ if (agent->v3->sec->finalparams(agent, packet,
+    ret, secparamsoffset, cookie) == -1) {
+ free(packet);
+ packet = NULL;
+ }
+ }
+
+fail:
+ if (agent->version == SNMP_V3)
+ agent->v3->sec->freecookie(cookie);
  ber_free_elements(message);
+ free(securityparams);
+ return packet;
+}
+
+static struct ber_element *
+snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
+{
+ struct ber ber;
+ enum snmp_version version;
+ char *community;
+ struct ber_element *pdu;
+ 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 *encpdu, *engineid;
+ size_t encpdulen, engineidlen;
+ void *cookie = NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+
+ ber_set_readbuf(&ber, buf, buflen);
+ if ((message = ber_read_elements(&ber, NULL)) == NULL)
+ return NULL;
  ber_free(&ber);
+
+ if (ber_scanf_elements(message, "{de", &version, &payload) != 0)
+ goto fail;
+
+ if (version != agent->version)
+ goto fail;
+
+ switch (version) {
+ case SNMP_V1:
+ case SNMP_V2C:
+ if (ber_scanf_elements(payload, "se", &community, &pdu) == -1)
+ goto fail;
+ ber_unlink_elements(payload);
+ ber_free_elements(message);
+ return pdu;
+ case SNMP_V3:
+ 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],
+    &cookie) == -1) {
+ cookie = NULL;
+ goto fail;
+ }
+ if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
+ if (ber_scanf_elements(scopedpdu, "x", &encpdu,
+    &encpdulen) == -1)
+ goto fail;
+ if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
+    encpdulen, cookie)) == NULL)
+ 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);
+ agent->v3->sec->freecookie(cookie);
+ return pdu;
+ }
+ /* NOTREACHED */
+
+fail:
+ if (version == SNMP_V3)
+ agent->v3->sec->freecookie(cookie);
+ ber_free_elements(message);
  return NULL;
 }
+
+static void
+snmp_v3_secparamsoffset(void *cookie, size_t offset)
+{
+ size_t *spoffset = cookie;
+
+ *spoffset = offset;
+}
diff --git a/snmp.h b/snmp.h
index 502aa75..ce62119 100644
--- a/snmp.h
+++ b/snmp.h
@@ -108,12 +108,43 @@ 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 *, void **);
+ struct ber_element *(*encpdu)(struct snmp_agent *,
+    struct ber_element *, void *);
+ int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
+ int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
+    size_t, uint8_t, void **);
+ struct ber_element *(*decpdu)(struct snmp_agent *, char *, size_t,
+    void *);
+ void (*free)(void *);
+ void (*freecookie)(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 +154,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/snmpc.c b/snmpc.c
index dc48b10..658ca82 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -23,8 +23,10 @@
 #include <sys/un.h>
 
 #include <arpa/inet.h>
+#include <openssl/evp.h>
 
 #include <ber.h>
+#include <ctype.h>
 #include <err.h>
 #include <errno.h>
 #include <netdb.h>
@@ -38,16 +40,19 @@
 
 #include "smi.h"
 #include "snmp.h"
+#include "usm.h"
 
-#define GETOPT_COMMON "c:r:t:v:O:"
+#define GETOPT_COMMON "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
 
 int snmpc_get(int, char *[]);
 int snmpc_walk(int, char *[]);
 int snmpc_trap(int, char *[]);
 int snmpc_mibtree(int, char *[]);
+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 {
@@ -70,10 +75,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;
@@ -91,6 +97,22 @@ enum smi_output_string output_string = smi_os_default;
 int
 main(int argc, char *argv[])
 {
+ const EVP_MD *md = NULL;
+ const EVP_CIPHER *cipher = NULL;
+ struct snmp_sec *sec;
+ char *user = NULL;
+ enum usm_key_level authkeylevel;
+ char *authkey = NULL;
+ size_t authkeylen = 0;
+ enum usm_key_level privkeylevel;
+ char *privkey = NULL;
+ size_t privkeylen = 0;
+ 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;
@@ -130,9 +152,86 @@ main(int argc, char *argv[])
 
  while ((ch = getopt(argc, argv, optstr)) != -1) {
  switch (ch) {
+ case 'A':
+ authkey = optarg;
+ authkeylen = strlen(authkey);
+ authkeylevel = USM_KEY_PASSWORD;
+ break;
+ case 'a':
+ if (strcasecmp(optarg, "MD5") == 0)
+ md = EVP_md5();
+ else if (strcasecmp(optarg, "SHA") == 0)
+ md = EVP_sha1();
+ else if (strcasecmp(optarg, "SHA-224") == 0)
+ md = EVP_sha224();
+ else if (strcasecmp(optarg, "SHA-256") == 0)
+ md = EVP_sha256();
+ else if (strcasecmp(optarg, "SHA-384") == 0)
+ md = EVP_sha384();
+ else if (strcasecmp(optarg, "SHA-512") == 0)
+ md = EVP_sha512();
+ else
+ errx(1, "Invalid authentication protocol "
+    "specified after -a flag: %s", optarg);
+ break;
  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 'K':
+ privkey = snmpc_hex2bin(optarg, &privkeylen);
+ if (privkey == NULL) {
+ if (errno == EINVAL)
+ errx(1, "Bad key value after "
+    "-3K flag.");
+ errx(1, "-3K");
+ }
+ privkeylevel = USM_KEY_LOCALIZED;
+ break;
+ case 'k':
+ authkey = snmpc_hex2bin(optarg, &authkeylen);
+ if (authkey == NULL) {
+ if (errno == EINVAL)
+ errx(1, "Bad key value after -k flag.");
+ err(1, "-k");
+ }
+ authkeylevel = USM_KEY_LOCALIZED;
+ break;
+ case 'l':
+ if (strcmp(optarg, "noAuthNoPriv") == 0)
+ seclevel = SNMP_MSGFLAG_REPORT;
+ else if (strcmp(optarg, "authNoPriv") == 0)
+ seclevel = SNMP_MSGFLAG_AUTH |
+    SNMP_MSGFLAG_REPORT;
+ else if (strcmp(optarg, "authPriv") == 0)
+ seclevel = SNMP_MSGFLAG_AUTH |
+    SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
+ else
+ errx(1, "Invalid security level specified "
+    "after -l flag: %s", optarg);
+ break;
+ case 'n':
+ ctxname = optarg;
+ break;
  case 'r':
  if ((retries = strtonum(optarg, 0, INT_MAX,
     &errstr)) == 0) {
@@ -147,11 +246,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;
@@ -282,6 +386,33 @@ main(int argc, char *argv[])
  }
  }
  break;
+ case 'X':
+ privkey = optarg;
+ privkeylen = strlen(privkey);
+ privkeylevel = USM_KEY_PASSWORD;
+ break;
+ case 'x':
+ if (strcasecmp(optarg, "DES") == 0)
+ cipher = EVP_des_cbc();
+ else if (strcasecmp(optarg, "AES") == 0)
+ cipher = EVP_aes_128_cfb128();
+ else
+ errx(1, "Invalid privacy protocol "
+    "specified after -3x flag: %s",
+    optarg);
+ 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();
  }
@@ -289,6 +420,50 @@ 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 (seclevel & SNMP_MSGFLAG_AUTH) {
+ if (md == NULL)
+ md = EVP_md5();
+ if (authkey == NULL)
+ errx(1, "No authKey or authPassword specified");
+ if (usm_setauth(sec, md, authkey, authkeylen,
+    authkeylevel) == -1)
+ err(1, "Can't set authkey");
+ }
+ if (seclevel & SNMP_MSGFLAG_PRIV) {
+ if (cipher == NULL)
+ cipher = EVP_des_cbc();
+ if (privkey == NULL)
+ errx(1, "No privKey or privPassword specified");
+ if (usm_setpriv(sec, cipher, privkey, privkeylen,
+    privkeylevel) == -1)
+ err(1, "Can't set authkey");
+ }
+ 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);
 }
 
@@ -300,13 +475,13 @@ snmpc_get(int argc, char *argv[])
  struct snmp_agent *agent;
  int errorstatus, errorindex;
  int i;
+ int class;
+ unsigned type;
 
  if (argc < 2)
  usage();
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version,
-    community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "161")) == NULL)
  err(1, "%s", snmp_app->name);
  agent->timeout = timeout;
  agent->retries = retries;
@@ -339,12 +514,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");
@@ -365,6 +542,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");
@@ -372,8 +551,7 @@ snmpc_walk(int argc, char *argv[])
  usage();
  oids = argc == 1 ? mib : argv[1];
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version, community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "161"))== NULL)
  err(1, "%s", snmp_app->name);
  agent->timeout = timeout;
  agent->retries = retries;
@@ -390,15 +568,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) {
@@ -412,14 +594,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);
@@ -440,6 +624,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;
  }
@@ -447,15 +633,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)
@@ -495,9 +685,7 @@ snmpc_trap(int argc, char *argv[])
  if (version == SNMP_V1)
  errx(1, "trap is not supported for snmp v1");
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "162"),
-    version, community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "162")) == NULL)
  err(1, "%s", snmp_app->name);
 
  if (pledge("stdio", NULL) == -1)
@@ -693,6 +881,20 @@ snmpc_mibtree(int argc, char *argv[])
  return 0;
 }
 
+struct snmp_agent *
+snmpc_connect(char *host, char *port)
+{
+ switch (version) {
+ case SNMP_V1:
+ 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;
+}
+
 int
 snmpc_print(struct ber_element *elm)
 {
@@ -875,18 +1077,61 @@ 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 ? "" : " ",
+    " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
+    "            [-E ctxengineid] [-K localpriv] [-k localauth] [-l seclevel]\n"
+    "            [-n ctxname] [-O afnqvxSQ] [-r retries] [-t timeout] [-u user]\n"
+    "            [-v version] [-X privpass] [-x cipher] [-Z boots,time]\n"
+    "            " : "",
     snmp_app->usage == NULL ? "" : snmp_app->usage);
  exit(1);
  }
@@ -898,8 +1143,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/usm.c b/usm.c
new file mode 100644
index 0000000..f34a6c9
--- /dev/null
+++ b/usm.c
@@ -0,0 +1,685 @@
+/* $OpenBSD: usm.c,v 1.16 2019/06/11 05:36:32 martijn Exp $ */
+
+/*
+ * Copyright (c) 2019 Martijn van Duren <[hidden email]>
+ *
+ * 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;
+ enum usm_key_level authlevel;
+ const EVP_MD *digest;
+ char *authkey;
+ enum usm_key_level privlevel;
+ const EVP_CIPHER *cipher;
+ char *privkey;
+ int bootsset;
+ uint32_t boots;
+ int timeset;
+ uint32_t time;
+ struct timespec timecheck;
+};
+
+struct usm_cookie {
+ size_t digestoffset;
+ long long salt;
+ uint32_t boots;
+ uint32_t time;
+};
+
+static int usm_doinit(struct snmp_agent *);
+static char *usm_genparams(struct snmp_agent *, size_t *, void **);
+static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
+static struct ber_element *usm_encpdu(struct snmp_agent *agent,
+    struct ber_element *pdu, void *cookie);
+static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *,
+    char *, size_t, size_t *);
+static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
+    size_t, uint8_t, void **);
+struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *);
+static void usm_digest_pos(void *, size_t);
+static void usm_free(void *);
+static char *usm_passwd2mkey(const EVP_MD *, const char *);
+static char *usm_mkey2lkey(struct usm_sec *, const EVP_MD *, const char *);
+static size_t usm_digestlen(const EVP_MD *);
+
+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->encpdu = usm_encpdu;
+ sec->parseparams = usm_parseparams;
+ sec->decpdu = usm_decpdu;
+ sec->finalparams = usm_finalparams;
+ sec->free = usm_free;
+ sec->freecookie = 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;
+
+ /* Ugly hack for HP Laserjet */
+ if (!usm->engineidset || !usm->bootsset || !usm->timeset) {
+ if ((ber = snmp_get(agent, NULL, 0)) == NULL)
+ return -1;
+ ber_free_element(ber);
+ }
+ return 0;
+}
+
+static char *
+usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
+{
+ struct ber ber;
+ struct ber_element *params, *digestelm;
+ struct usm_sec *usm = agent->v3->sec->data;
+ char digest[USM_MAX_DIGESTLEN];
+ char *secparams = NULL, *buf;
+ ssize_t berlen;
+ struct usm_cookie *usmcookie;
+ struct timespec now, timediff;
+
+ bzero(digest, sizeof(digest));
+
+ if ((usmcookie = calloc(1, sizeof(*usmcookie))) == NULL)
+ return NULL;
+ *cookie = usmcookie;
+
+ arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt));
+ if (usm->timeset) {
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
+ free(usmcookie);
+ return NULL;
+ }
+ timespecsub(&now, &(usm->timecheck), &timediff);
+ usmcookie->time = usm->time + timediff.tv_sec;
+ } else
+ usmcookie->time = 0;
+ usmcookie->boots = usm->boots;
+
+ if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
+    usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
+    usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
+    usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
+    agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
+    (size_t) 0)) == NULL) {
+ free(usmcookie);
+ return NULL;
+ }
+
+ if (ber_scanf_elements(params, "{SSSSe",  &digestelm) == -1) {
+ ber_free_element(params);
+ free(usmcookie);
+ return NULL;
+ }
+
+ ber_set_writecallback(digestelm, usm_digest_pos, usmcookie);
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+ if ((berlen = ber_write_elements(&ber, params)) != -1 &&
+    ber_get_writebuf(&ber, (void **)&buf) != -1 &&
+    (secparams = malloc(berlen)) != NULL)
+ memcpy(secparams, buf, berlen);
+ *len = berlen;
+ ber_free_element(params);
+ ber_free(&ber);
+ return secparams;
+}
+
+static struct ber_element *
+usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ struct ber ber;
+ struct ber_element *retpdu;
+ char *serialpdu, *encpdu;
+ ssize_t pdulen;
+ size_t encpdulen;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+ pdulen = ber_write_elements(&ber, pdu);
+ if (pdulen == -1)
+ return NULL;
+
+ ber_get_writebuf(&ber, (void **)&serialpdu);
+
+ encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu,
+    pdulen, &encpdulen);
+ ber_free(&ber);
+ if (encpdu == NULL)
+ return NULL;
+
+ retpdu = ber_add_nstring(NULL, encpdu, encpdulen);
+ free(encpdu);
+ return retpdu;
+}
+
+static char *
+usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key,
+    struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen)
+{
+ EVP_CIPHER_CTX ctx;
+ size_t i;
+ char iv[EVP_MAX_IV_LENGTH];
+ char *salt = (char *)&(cookie->salt);
+ char *outtext;
+ int len, len2;
+ uint32_t ivv;
+
+ switch (EVP_CIPHER_type(cipher)) {
+ case NID_des_cbc:
+ /* RFC3414, chap 8.1.1.1. */
+ for (i = 0; i < 8; i++)
+ iv[i] = salt[i] ^ key[USM_SALTOFFSET + i];
+ break;
+ case NID_aes_128_cfb128:
+ /* RFC3826, chap 3.1.2.1. */
+ ivv = htobe32(cookie->boots);
+ memcpy(iv, &ivv, sizeof(ivv));
+ ivv = htobe32(cookie->time);
+ memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
+ memcpy(iv + 2 * sizeof(ivv), &(cookie->salt),
+    sizeof(cookie->salt));
+ break;
+ default:
+ return NULL;
+ }
+
+ bzero(&ctx, sizeof(ctx));
+ if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc))
+ return NULL;
+
+ EVP_CIPHER_CTX_set_padding(&ctx, do_enc);
+
+ *outlen = EVP_CIPHER_block_size(cipher);
+ /* Maximum output size */
+ *outlen = pdulen + (*outlen - (pdulen % *outlen));
+
+ if ((outtext = malloc(*outlen)) == NULL)
+ return NULL;
+
+ if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) &&
+    EVP_CipherFinal_ex(&ctx, outtext + len, &len2))
+ *outlen = len + len2;
+ else {
+ free(outtext);
+ outtext = NULL;
+ }
+
+ EVP_CIPHER_CTX_cleanup(&ctx);
+
+ return outtext;
+}
+
+static int
+usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
+    size_t secparamsoffset, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ u_char digest[EVP_MAX_MD_SIZE];
+
+ if ((agent->v3->level & SNMP_MSGFLAG_AUTH) == 0)
+ return 0;
+
+ if (usm->authlevel != USM_KEY_LOCALIZED)
+ return -1;
+
+ if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), buf,
+    buflen, digest, NULL) == NULL)
+ return -1;
+
+ memcpy(buf + secparamsoffset + usmcookie->digestoffset, digest,
+    usm_digestlen(usm->digest));
+ return 0;
+}
+
+static int
+usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
+    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level,
+    void **cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct ber ber;
+ struct ber_element *secparams;
+ char *engineid, *user, *digest, *salt;
+ size_t engineidlen, userlen, digestlen, saltlen;
+ struct timespec now, timediff;
+ off_t digestoffset;
+ char exp_digest[EVP_MAX_MD_SIZE];
+ struct usm_cookie *usmcookie;
+
+ bzero(&ber, sizeof(ber));
+ bzero(exp_digest, sizeof(exp_digest));
+
+ 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 ((usmcookie = malloc(sizeof(*usmcookie))) == NULL)
+ goto fail;
+ *cookie = usmcookie;
+
+ if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen,
+    &(usmcookie->boots), &(usmcookie->time), &user, &userlen,
+    &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1)
+ goto fail;
+ if (saltlen != sizeof(usmcookie->salt) && saltlen != 0)
+ goto fail;
+ memcpy(&(usmcookie->salt), salt, saltlen);
+
+ 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 = usmcookie->boots;
+ usm->bootsset = 1;
+ } else {
+ if (usmcookie->boots < usm->boots)
+ goto fail;
+ if (usmcookie->boots > usm->boots) {
+ usm->bootsset = 0;
+ usm->timeset = 0;
+ usm_doinit(agent);
+ goto fail;
+ }
+ }
+
+ if (!usm->timeset) {
+ usm->time = usmcookie->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 (usmcookie->time <
+    usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
+    usmcookie->time >
+    usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
+ usm->bootsset = 0;
+ usm->timeset = 0;
+ usm_doinit(agent);
+ goto fail;
+ }
+ }
+ /*
+ * Don't assume these are set if both are zero.
+ * Ugly hack for HP Laserjet
+ */
+ if (usm->boots == 0 && usm->time == 0) {
+ usm->bootsset = 0;
+ usm->timeset = 0;
+ }
+
+ if (userlen != usm->userlen ||
+    memcmp(user, usm->user, userlen) != 0)
+ goto fail;
+
+ if (level & SNMP_MSGFLAG_AUTH) {
+ if (digestlen != usm_digestlen(usm->digest))
+ goto fail;
+ }
+ if ((agent->v3->level & SNMP_MSGFLAG_AUTH)) {
+ bzero(packet + secparamsoffset + digestoffset, digestlen);
+ if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), packet,
+    packetlen, exp_digest, NULL) == NULL)
+ goto fail;
+
+ if (memcmp(exp_digest, digest, digestlen) != 0)
+ goto fail;
+ } else
+ if (digestlen != 0)
+ goto fail;
+
+ ber_free_element(secparams);
+ return 0;
+
+fail:
+ free(usmcookie);
+ ber_free_element(secparams);
+ return -1;
+}
+
+struct ber_element *
+usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ struct ber ber;
+ struct ber_element *scopedpdu;
+ char *rawpdu;
+ size_t rawpdulen;
+
+ if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie,
+    encpdu, encpdulen, &rawpdulen)) == NULL)
+ return NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+ ber_set_readbuf(&ber, rawpdu, rawpdulen);
+ scopedpdu = ber_read_elements(&ber, NULL);
+ ber_free(&ber);
+ free(rawpdu);
+
+ return scopedpdu;
+}
+
+static void
+usm_digest_pos(void *data, size_t offset)
+{
+ struct usm_cookie *usmcookie = data;
+
+ usmcookie->digestoffset = offset;
+}
+
+static void
+usm_free(void *data)
+{
+ struct usm_sec *usm = data;
+
+ free(usm->user);
+ free(usm->authkey);
+ free(usm->privkey);
+ free(usm->engineid);
+ free(usm);
+}
+
+int
+usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
+    size_t keylen, enum usm_key_level level)
+{
+ struct usm_sec *usm = sec->data;
+ char *lkey;
+
+ /*
+ * We could transform a master key to a local key here if we already
+ * have usm_setengineid called. Sine snmpc.c is the only caller at
+ * the moment there's no need, since it always calls this function
+ * first.
+ */
+ if (level == USM_KEY_PASSWORD) {
+ if ((usm->authkey = usm_passwd2mkey(digest, key)) == NULL)
+ return -1;
+ level = USM_KEY_MASTER;
+ keylen = EVP_MD_size(digest);
+ } else {
+ if (keylen != (size_t)EVP_MD_size(digest)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((lkey = malloc(keylen)) == NULL)
+ return -1;
+ memcpy(lkey, key, keylen);
+ usm->authkey = lkey;
+ }
+ usm->digest = digest;
+ usm->authlevel = level;
+ return 0;
+}
+
+int
+usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key,
+    size_t keylen, enum usm_key_level level)
+{
+ struct usm_sec *usm = sec->data;
+ char *lkey;
+
+ if (usm->digest == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * We could transform a master key to a local key here if we already
+ * have usm_setengineid called. Sine snmpc.c is the only caller at
+ * the moment there's no need, since it always calls us first.
+ */
+ if (level == USM_KEY_PASSWORD) {
+ if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL)
+ return -1;
+ level = USM_KEY_MASTER;
+ keylen = EVP_MD_size(usm->digest);
+ } else {
+ if (keylen != (size_t)EVP_MD_size(usm->digest)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((lkey = malloc(keylen)) == NULL)
+ return -1;
+ memcpy(lkey, key, keylen);
+ usm->privkey = lkey;
+ }
+ usm->cipher = cipher;
+ usm->privlevel = level;
+ return 0;
+}
+
+int
+usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
+{
+ struct usm_sec *usm = sec->data;
+ char *mkey;
+
+ 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;
+
+ if (usm->authlevel == USM_KEY_MASTER) {
+ mkey = usm->authkey;
+ if ((usm->authkey = usm_mkey2lkey(usm, usm->digest,
+    mkey)) == NULL) {
+ usm->authkey = mkey;
+ return -1;
+ }
+ free(mkey);
+ usm->authlevel = USM_KEY_LOCALIZED;
+ }
+ if (usm->privlevel == USM_KEY_MASTER) {
+ mkey = usm->privkey;
+ if ((usm->privkey = usm_mkey2lkey(usm, usm->digest,
+    mkey)) == NULL) {
+ usm->privkey = mkey;
+ return -1;
+ }
+ free(mkey);
+ usm->privlevel = USM_KEY_LOCALIZED;
+ }
+
+ 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;
+}
+
+static char *
+usm_passwd2mkey(const EVP_MD *md, const char *passwd)
+{
+ EVP_MD_CTX ctx;
+ int i, count;
+ const u_char *pw;
+ u_char *c;
+ u_char keybuf[EVP_MAX_MD_SIZE > 64 ? EVP_MAX_MD_SIZE : 64];
+ unsigned dlen;
+ char *key;
+
+ bzero(&ctx, sizeof(ctx));
+ EVP_DigestInit_ex(&ctx, md, NULL);
+ pw = (const u_char *)passwd;
+ for (count = 0; count < 1048576; count += 64) {
+ c = keybuf;
+ for (i = 0; i < 64; i++) {
+ if (*pw == '\0')
+ pw = (const u_char *)passwd;
+ *c++ = *pw++;
+ }
+ EVP_DigestUpdate(&ctx, keybuf, 64);
+ }
+ EVP_DigestFinal_ex(&ctx, keybuf, &dlen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ if ((key = malloc(dlen)) == NULL)
+ return NULL;
+ memcpy(key, keybuf, dlen);
+ return key;
+}
+
+static char *
+usm_mkey2lkey(struct usm_sec *usm, const EVP_MD *md, const char *mkey)
+{
+ EVP_MD_CTX ctx;
+ u_char buf[EVP_MAX_MD_SIZE];
+ u_char *lkey;
+ unsigned lklen;
+
+
+ bzero(&ctx, sizeof(ctx));
+ EVP_DigestInit_ex(&ctx, md, NULL);
+
+ EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
+ EVP_DigestUpdate(&ctx, usm->engineid, usm->engineidlen);
+ EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
+
+ EVP_DigestFinal_ex(&ctx, buf, &lklen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ if ((lkey = malloc(lklen)) == NULL)
+ return NULL;
+ memcpy(lkey, buf, lklen);
+ return lkey;
+}
+
+static size_t
+usm_digestlen(const EVP_MD *md)
+{
+ switch (EVP_MD_type(md)) {
+ case NID_md5:
+ case NID_sha1:
+                return 12;
+        case NID_sha224:
+                return 16;
+        case NID_sha256:
+                return 24;
+        case NID_sha384:
+                return 32;
+        case NID_sha512:
+                return 48;
+        default:
+                return 0;
+
+ }
+}
diff --git a/usm.h b/usm.h
new file mode 100644
index 0000000..b1aea8b
--- /dev/null
+++ b/usm.h
@@ -0,0 +1,34 @@
+/* $OpenBSD: snmp.h,v 1.1 2019/08/09 06:17:59 martijn Exp $ */
+
+/*
+ * Copyright (c) 2019 Martijn van Duren <[hidden email]>
+ *
+ * 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"
+
+enum usm_key_level {
+ USM_KEY_UNSET = 0,
+ USM_KEY_PASSWORD,
+ USM_KEY_MASTER,
+ USM_KEY_LOCALIZED
+};
+
+struct snmp_sec *usm_init(const char *, size_t);
+int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
+    enum usm_key_level);
+int usm_setpriv(struct snmp_sec *, const EVP_CIPHER *, const char *, size_t,
+    enum usm_key_level);
+int usm_setengineid(struct snmp_sec *, char *, size_t);
+int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [1/5]

Martijn van Duren-5
Here's a diff that restructures packet handling to allow easier addition
of SNMPv3.

diff --git a/snmp.c b/snmp.c
index 7fac777..b2d5cfc 100644
--- a/snmp.c
+++ b/snmp.c
@@ -32,6 +32,10 @@
 
 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);
 
 struct snmp_agent *
 snmp_connect_v12(int fd, enum snmp_version version, const char *community)
@@ -171,19 +175,16 @@ fail:
 static struct ber_element *
 snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
 {
- struct ber_element *message, *varbind;
+ struct ber_element *varbind;
  struct ber_oid oid;
  struct timespec start, now;
  struct pollfd pfd;
- struct ber ber;
+ char *message;
  ssize_t len;
  long long reqid, rreqid;
- long long version;
- char *community;
  short direction;
  int to, nfds, ret;
  int tries;
- void *ptr;
  char buf[READ_BUF_SIZE];
 
  if (ber_scanf_elements(pdu, "{i", &reqid) != 0) {
@@ -192,23 +193,8 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  return NULL;
  }
 
- if ((message = ber_add_sequence(NULL)) == NULL) {
- ber_free_elements(pdu);
- return NULL;
- }
- if (ber_printf_elements(message, "dse", agent->version,
-    agent->community, pdu) == NULL) {
- ber_free_elements(pdu);
- ber_free_elements(message);
+ if ((message = snmp_package(agent, pdu, &len)) == NULL)
  return NULL;
- }
- memset(&ber, 0, sizeof(ber));
- ber_set_application(&ber, smi_application);
- len = ber_write_elements(&ber, message);
- ber_free_elements(message);
- message = NULL;
- if (ber_get_writebuf(&ber, &ptr) < 1)
- goto fail;
 
  clock_gettime(CLOCK_MONOTONIC, &start);
  memcpy(&now, &start, sizeof(now));
@@ -236,7 +222,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  goto fail;
  }
  if (direction == POLLOUT) {
- ret = send(agent->fd, ptr, len, MSG_DONTWAIT);
+ ret = send(agent->fd, message, len, MSG_DONTWAIT);
  if (ret == -1)
  goto fail;
  if (ret < len) {
@@ -253,25 +239,10 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  errno = ECONNRESET;
  if (ret <= 0)
  goto fail;
- ber_set_readbuf(&ber, buf, ret);
- if ((message = ber_read_elements(&ber, NULL)) == NULL) {
- direction = POLLOUT;
+ if ((pdu = snmp_unpackage(agent, buf, ret)) == NULL) {
  tries--;
- continue;
- }
- if (ber_scanf_elements(message, "{ise", &version, &community,
-    &pdu) != 0) {
- errno = EPROTO;
  direction = POLLOUT;
- tries--;
- continue;
- }
- /* Skip invalid packets; should not happen */
- if (version != agent->version ||
-    strcmp(community, agent->community) != 0) {
  errno = EPROTO;
- direction = POLLOUT;
- tries--;
  continue;
  }
  /* Validate pdu format and check request id */
@@ -297,17 +268,96 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  break;
  }
  }
- if (varbind != NULL)
- continue;
 
- ber_unlink_elements(message->be_sub->be_next);
- ber_free_elements(message);
- ber_free(&ber);
+ free(message);
  return pdu;
  }
 
+fail:
+ free(message);
+ return NULL;
+}
+
+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 *buf, *packet = NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+
+ if ((message = ber_add_sequence(NULL)) == NULL) {
+ ber_free_elements(pdu);
+ goto fail;
+ }
+
+ switch (agent->version) {
+ case SNMP_V1:
+ case SNMP_V2C:
+ if (ber_printf_elements(message, "dse", agent->version,
+    agent->community, pdu) == NULL) {
+ ber_free_elements(pdu);
+ goto fail;
+ }
+ break;
+ case SNMP_V3:
+ break;
+ }
+
+ if ((ret = ber_write_elements(&ber, message)) == -1)
+ goto fail;
+ *len = (size_t) ret;
+ if (ber_get_writebuf(&ber, (void **)&buf) != -1 &&
+    (packet = malloc(ret)) != NULL)
+ memcpy(packet, buf, ret);
+ ber_free(&ber);
+
 fail:
  ber_free_elements(message);
+ return packet;
+}
+
+static struct ber_element *
+snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
+{
+ struct ber ber;
+ enum snmp_version version;
+ char *community;
+ struct ber_element *pdu;
+ struct ber_element *message = NULL, *payload;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+
+ ber_set_readbuf(&ber, buf, buflen);
+ if ((message = ber_read_elements(&ber, NULL)) == NULL)
+ return NULL;
  ber_free(&ber);
+
+ if (ber_scanf_elements(message, "{de", &version, &payload) != 0)
+ goto fail;
+
+ if (version != agent->version)
+ goto fail;
+
+ switch (version)
+ {
+ case SNMP_V1:
+ case SNMP_V2C:
+ if (ber_scanf_elements(payload, "se", &community, &pdu) == -1)
+ goto fail;
+ ber_unlink_elements(payload);
+ ber_free_elements(message);
+ return pdu;
+ case SNMP_V3:
+ break;
+ }
+ /* NOTREACHED */
+
+fail:
+ ber_free_elements(message);
  return NULL;
 }
diff --git a/snmpc.c b/snmpc.c
index dc48b10..104bb7c 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -45,6 +45,7 @@ int snmpc_get(int, char *[]);
 int snmpc_walk(int, char *[]);
 int snmpc_trap(int, char *[]);
 int snmpc_mibtree(int, char *[]);
+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 *);
@@ -304,9 +305,7 @@ snmpc_get(int argc, char *argv[])
  if (argc < 2)
  usage();
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version,
-    community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "161")) == NULL)
  err(1, "%s", snmp_app->name);
  agent->timeout = timeout;
  agent->retries = retries;
@@ -372,8 +371,7 @@ snmpc_walk(int argc, char *argv[])
  usage();
  oids = argc == 1 ? mib : argv[1];
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version, community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "161"))== NULL)
  err(1, "%s", snmp_app->name);
  agent->timeout = timeout;
  agent->retries = retries;
@@ -495,9 +493,7 @@ snmpc_trap(int argc, char *argv[])
  if (version == SNMP_V1)
  errx(1, "trap is not supported for snmp v1");
 
- agent = snmp_connect_v12(snmpc_parseagent(argv[0], "162"),
-    version, community);
- if (agent == NULL)
+ if ((agent = snmpc_connect(argv[0], "162")) == NULL)
  err(1, "%s", snmp_app->name);
 
  if (pledge("stdio", NULL) == -1)
@@ -693,6 +689,18 @@ snmpc_mibtree(int argc, char *argv[])
  return 0;
 }
 
+struct snmp_agent *
+snmpc_connect(char *host, char *port)
+{
+ switch (version) {
+ case SNMP_V1:
+ case SNMP_V2C:
+ return snmp_connect_v12(snmpc_parseagent(host, port), version,
+    community);
+ }
+ return NULL;
+}
+
 int
 snmpc_print(struct ber_element *elm)
 {

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [2/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
Here's the diff that adds initial SNMPv3 support with USM noAuthNoPriv
support.

diff --git a/Makefile b/Makefile
index 62bb556..9eb684b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 # $OpenBSD: Makefile,v 1.1 2019/08/09 06:17:59 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/snmp.1 b/snmp.1
index e158ba0..523fd16 100644
--- a/snmp.1
+++ b/snmp.1
@@ -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/snmp.c b/snmp.c
index b2d5cfc..7165976 100644
--- a/snmp.c
+++ b/snmp.c
@@ -36,6 +36,47 @@ 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 +95,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 +327,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 +339,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
  errno = EPROTO;
  direction = POLLOUT;
  tries--;
- break;
+ continue;
  }
  }
 
@@ -282,9 +356,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 *buf, *packet = NULL;
+ struct ber_element *message, *scopedpdu = NULL;
+ ssize_t securitysize, ret;
+ char *securityparams = NULL, *buf, *packet = NULL;
+ long long msgid;
 
  bzero(&ber, sizeof(ber));
  ber_set_application(&ber, smi_application);
@@ -304,6 +379,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, 1472, &(agent->v3->level),
+    (size_t) 1, agent->v3->sec->model, securityparams,
+    securitysize, scopedpdu) == NULL)
+ goto fail;
  break;
  }
 
@@ -317,6 +415,7 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
 
 fail:
  ber_free_elements(message);
+ free(securityparams);
  return packet;
 }
 
@@ -327,7 +426,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);
@@ -343,8 +449,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)
@@ -353,7 +458,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/snmp.h b/snmp.h
index 502aa75..1cfd8aa 100644
--- a/snmp.h
+++ b/snmp.h
@@ -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/snmpc.c b/snmpc.c
index 104bb7c..4c09405 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -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/usm.c b/usm.c
new file mode 100644
index 0000000..c51cf7d
--- /dev/null
+++ b/usm.c
@@ -0,0 +1,276 @@
+/* $OpenBSD: usm.c,v 1.16 2019/06/11 05:36:32 martijn Exp $ */
+
+/*
+ * Copyright (c) 2019 Martijn van Duren <[hidden email]>
+ *
+ * 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, *buf;
+ ssize_t berlen;
+ 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 ((berlen = ber_write_elements(&ber, params)) != -1 &&
+    ber_get_writebuf(&ber, (void **)&buf) != -1 &&
+    (secparams = malloc(berlen)) != NULL)
+ memcpy(secparams, buf, berlen);
+ *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/usm.h b/usm.h
new file mode 100644
index 0000000..1def439
--- /dev/null
+++ b/usm.h
@@ -0,0 +1,23 @@
+/* $OpenBSD: snmp.h,v 1.1 2019/08/09 06:17:59 martijn Exp $ */
+
+/*
+ * Copyright (c) 2019 Martijn van Duren <[hidden email]>
+ *
+ * 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);

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [3/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
This diff adds support for authNoPriv.

diff --git a/Makefile b/Makefile
index 9eb684b..102582b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,8 @@
 
 PROG= snmp
 SRCS= mib.c smi.c snmp.c snmpc.c usm.c
-LDADD+= -lutil
-DPADD+= ${LIBUTIL}
+LDADD+= -lcrypto -lutil
+DPADD+= ${LIBCRYPTO} ${LIBUTIL}
 
 MAN= snmp.1
 
diff --git a/snmp.1 b/snmp.1
index 523fd16..e810560 100644
--- a/snmp.1
+++ b/snmp.1
@@ -23,9 +23,13 @@
 .Sh SYNOPSIS
 .Nm
 .Cm get | getnext
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
 .Op Fl O Cm afnQqSvx
 .Op Fl r Ar retries
@@ -37,9 +41,13 @@
 .Ar oid ...
 .Nm
 .Cm walk
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
 .Op Fl O Cm afnQqSvx
 .Op Fl r Ar retries
@@ -53,9 +61,13 @@
 .Op Ar oid
 .Nm
 .Cm bulkget
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
 .Op Fl O Cm afnQqSvx
 .Op Fl r Ar retries
@@ -68,9 +80,13 @@
 .Ar oid ...
 .Nm
 .Cm bulkwalk
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
 .Op Fl O Cm afnQqSvx
 .Op Fl r Ar retries
@@ -83,9 +99,13 @@
 .Op Ar oid
 .Nm
 .Cm trap
+.Op Fl A Ar authpass
+.Op Fl a Ar digest
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl k Ar localauth
+.Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
 .Op Fl r Ar retries
 .Op Fl t Ar timeout
@@ -170,6 +190,28 @@ Dump the tree of compiled-in MIB objects.
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
+.It Fl A Ar authpass
+The authentication password for the user.
+This will be transformed to
+.Ar localauth .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl a Ar digest
+Set the digest
+.Pq authentication
+protocol.
+Options are
+.Cm MD5 ,
+.Cm SHA ,
+.Cm SHA-224 ,
+.Cm SHA-256 ,
+.Cm SHA-384
+or
+.Cm SHA-512 .
+This option defaults to
+.Cm MD5 .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl C Ar appopt
 Set the application specific
 .Ar appopt
@@ -260,6 +302,28 @@ 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 k Ar localauth
+The localized authentication password for the user in hexadecimal format
+.Po
+optionally prefixed with a
+.Cm 0x
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl l Ar seclevel
+The security level.
+Values can be
+.Cm noAuthNoPriv Pq default
+or
+.Cm authNoPriv
+.Po
+requires either
+.Fl A
+or
+.Fl k
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl n Ar ctxname
 Sets the context name.
 Defaults to an empty string.
diff --git a/snmp.c b/snmp.c
index 7165976..57b40e3 100644
--- a/snmp.c
+++ b/snmp.c
@@ -37,6 +37,7 @@ static char *
 static struct ber_element *
     snmp_unpackage(struct snmp_agent *, char *, size_t);
 static void snmp_v3_free(struct snmp_v3 *);
+static void snmp_v3_secparamsoffset(void *, size_t);
 
 struct snmp_v3 *
 snmp_v3_init(int level, const char *ctxname, size_t ctxnamelen,
@@ -356,10 +357,12 @@ static char *
 snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
 {
  struct ber ber;
- struct ber_element *message, *scopedpdu = NULL;
+ struct ber_element *message, *scopedpdu = NULL, *secparams;
  ssize_t securitysize, ret;
+ size_t secparamsoffset;
  char *securityparams = NULL, *buf, *packet = NULL;
  long long msgid;
+ void *cookie = NULL;
 
  bzero(&ber, sizeof(ber));
  ber_set_application(&ber, smi_application);
@@ -393,7 +396,7 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
  }
  pdu = NULL;
  if ((securityparams = agent->v3->sec->genparams(agent,
-    &securitysize)) == NULL) {
+    &securitysize, &cookie)) == NULL) {
  ber_free_elements(scopedpdu);
  goto fail;
  }
@@ -402,6 +405,10 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
     (size_t) 1, agent->v3->sec->model, securityparams,
     securitysize, scopedpdu) == NULL)
  goto fail;
+ if (ber_scanf_elements(message, "{SSe", &secparams) == -1)
+ goto fail;
+ ber_set_writecallback(secparams, snmp_v3_secparamsoffset,
+    &secparamsoffset);
  break;
  }
 
@@ -413,7 +420,17 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
  memcpy(packet, buf, ret);
  ber_free(&ber);
 
+ if (agent->version == SNMP_V3 && packet != NULL) {
+ if (agent->v3->sec->finalparams(agent, packet,
+    ret, secparamsoffset, cookie) == -1) {
+ free(packet);
+ packet = NULL;
+ }
+ }
+
 fail:
+ if (agent->version == SNMP_V3)
+ agent->v3->sec->freecookie(cookie);
  ber_free_elements(message);
  free(securityparams);
  return packet;
@@ -493,3 +510,11 @@ fail:
  ber_free_elements(message);
  return NULL;
 }
+
+static void
+snmp_v3_secparamsoffset(void *cookie, size_t offset)
+{
+ size_t *spoffset = cookie;
+
+ *spoffset = offset;
+}
diff --git a/snmp.h b/snmp.h
index 1cfd8aa..df20f0d 100644
--- a/snmp.h
+++ b/snmp.h
@@ -113,10 +113,12 @@ struct snmp_agent;
 struct snmp_sec {
  enum snmp_security_model model;
  int (*init)(struct snmp_agent *);
- char *(*genparams)(struct snmp_agent *, size_t *);
+ char *(*genparams)(struct snmp_agent *, size_t *, void **);
+ int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
  int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
     size_t, uint8_t);
  void (*free)(void *);
+ void (*freecookie)(void *);
  void *data;
 };
 
diff --git a/snmpc.c b/snmpc.c
index 4c09405..edaa50c 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -23,6 +23,7 @@
 #include <sys/un.h>
 
 #include <arpa/inet.h>
+#include <openssl/evp.h>
 
 #include <ber.h>
 #include <ctype.h>
@@ -41,7 +42,7 @@
 #include "snmp.h"
 #include "usm.h"
 
-#define GETOPT_COMMON "c:E:e:n:O:r:t:u:v:Z:"
+#define GETOPT_COMMON "A:a:c:E:e:k:l:n:O:r:t:u:v:Z:"
 
 int snmpc_get(int, char *[]);
 int snmpc_walk(int, char *[]);
@@ -96,8 +97,12 @@ enum smi_output_string output_string = smi_os_default;
 int
 main(int argc, char *argv[])
 {
+ const EVP_MD *md = NULL;
  struct snmp_sec *sec;
  char *user = NULL;
+ enum usm_key_level authkeylevel;
+ char *authkey = NULL;
+ size_t authkeylen = 0;
  int seclevel = SNMP_MSGFLAG_REPORT;
  char *ctxname = NULL;
  char *ctxengineid = NULL, *secengineid = NULL;
@@ -143,6 +148,28 @@ main(int argc, char *argv[])
 
  while ((ch = getopt(argc, argv, optstr)) != -1) {
  switch (ch) {
+ case 'A':
+ authkey = optarg;
+ authkeylen = strlen(authkey);
+ authkeylevel = USM_KEY_PASSWORD;
+ break;
+ case 'a':
+ if (strcasecmp(optarg, "MD5") == 0)
+ md = EVP_md5();
+ else if (strcasecmp(optarg, "SHA") == 0)
+ md = EVP_sha1();
+ else if (strcasecmp(optarg, "SHA-224") == 0)
+ md = EVP_sha224();
+ else if (strcasecmp(optarg, "SHA-256") == 0)
+ md = EVP_sha256();
+ else if (strcasecmp(optarg, "SHA-384") == 0)
+ md = EVP_sha384();
+ else if (strcasecmp(optarg, "SHA-512") == 0)
+ md = EVP_sha512();
+ else
+ errx(1, "Invalid authentication protocol "
+    "specified after -a flag: %s", optarg);
+ break;
  case 'c':
  community = optarg;
  break;
@@ -166,6 +193,25 @@ main(int argc, char *argv[])
  err(1, "-3e");
  }
  break;
+ case 'k':
+ authkey = snmpc_hex2bin(optarg, &authkeylen);
+ if (authkey == NULL) {
+ if (errno == EINVAL)
+ errx(1, "Bad key value after -k flag.");
+ err(1, "-k");
+ }
+ authkeylevel = USM_KEY_LOCALIZED;
+ break;
+ case 'l':
+ if (strcmp(optarg, "noAuthNoPriv") == 0)
+ seclevel = SNMP_MSGFLAG_REPORT;
+ else if (strcmp(optarg, "authNoPriv") == 0)
+ seclevel = SNMP_MSGFLAG_AUTH |
+    SNMP_MSGFLAG_REPORT;
+ else
+ errx(1, "Invalid security level specified "
+    "after -l flag: %s", optarg);
+ break;
  case 'n':
  ctxname = optarg;
  break;
@@ -348,6 +394,15 @@ main(int argc, char *argv[])
  errx(1, "No securityName specified");
  if ((sec = usm_init(user, strlen(user))) == NULL)
  err(1, "usm_init");
+ if (seclevel & SNMP_MSGFLAG_AUTH) {
+ if (md == NULL)
+ md = EVP_md5();
+ if (authkey == NULL)
+ errx(1, "No authKey or authPassword specified");
+ if (usm_setauth(sec, md, authkey, authkeylen,
+    authkeylevel) == -1)
+ err(1, "Can't set authkey");
+ }
  if (secengineid != NULL) {
  if (usm_setengineid(sec, secengineid,
     secengineidlen) == -1)
@@ -1031,7 +1086,8 @@ usage(void)
  fprintf(stderr, "usage: snmp %s%s%s\n",
     snmp_app->name,
     snmp_app->usecommonopt ?
-    " [-c community] [-e secengineid] [-E ctxengineid] [-n ctxname]\n"
+    " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
+    "            [-E ctxengineid] [-k localauth] [-l seclevel] [-n ctxname]\n"
     "            [-O afnqvxSQ] [-r retries] [-t timeout] [-u user] [-v version]\n"
     "            [-Z boots,time]\n"
     "            " : "",
diff --git a/usm.c b/usm.c
index c51cf7d..df9a53d 100644
--- a/usm.c
+++ b/usm.c
@@ -41,6 +41,9 @@ struct usm_sec {
  int engineidset;
  char *engineid;
  size_t engineidlen;
+ enum usm_key_level authlevel;
+ const EVP_MD *digest;
+ char *authkey;
  int bootsset;
  uint32_t boots;
  int timeset;
@@ -48,11 +51,20 @@ struct usm_sec {
  struct timespec timecheck;
 };
 
+struct usm_cookie {
+ size_t digestoffset;
+};
+
 static int usm_doinit(struct snmp_agent *);
-static char *usm_genparams(struct snmp_agent *, size_t *);
+static char *usm_genparams(struct snmp_agent *, size_t *, void **);
+static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
 static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
     size_t, uint8_t);
+static void usm_digest_pos(void *, size_t);
 static void usm_free(void *);
+static char *usm_passwd2mkey(const EVP_MD *, const char *);
+static char *usm_mkey2lkey(struct usm_sec *, const EVP_MD *, const char *);
+static size_t usm_digestlen(const EVP_MD *);
 
 struct snmp_sec *
 usm_init(const char *user, size_t userlen)
@@ -84,7 +96,9 @@ usm_init(const char *user, size_t userlen)
  sec->init = usm_doinit;
  sec->genparams = usm_genparams;
  sec->parseparams = usm_parseparams;
+ sec->finalparams = usm_finalparams;
  sec->free = usm_free;
+ sec->freecookie = free;
  sec->data = usm;
  return sec;
 }
@@ -119,19 +133,29 @@ usm_doinit(struct snmp_agent *agent)
 }
 
 static char *
-usm_genparams(struct snmp_agent *agent, size_t *len)
+usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
 {
  struct ber ber;
- struct ber_element *params;
+ struct ber_element *params, *digestelm;
  struct usm_sec *usm = agent->v3->sec->data;
+ char digest[USM_MAX_DIGESTLEN];
  char *secparams = NULL, *buf;
  ssize_t berlen;
+ struct usm_cookie *usmcookie;
  struct timespec now, timediff;
  uint32_t boots, time;
 
+ bzero(digest, sizeof(digest));
+
+ if ((usmcookie = calloc(1, sizeof(*usmcookie))) == NULL)
+ return NULL;
+ *cookie = usmcookie;
+
  if (usm->timeset) {
- if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
+ free(usmcookie);
  return NULL;
+ }
  timespecsub(&now, &(usm->timecheck), &timediff);
  time = usm->time + timediff.tv_sec;
  } else
@@ -139,9 +163,20 @@ usm_genparams(struct snmp_agent *agent, size_t *len)
  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)
+    usm->engineidlen, boots, time, usm->user, usm->userlen, digest,
+    agent->v3->level & SNMP_MSGFLAG_AUTH ? usm_digestlen(usm->digest) :
+    (size_t) 0, NULL, (size_t) 0)) == NULL) {
+ free(usmcookie);
  return NULL;
+ }
+
+ if (ber_scanf_elements(params, "{SSSSe",  &digestelm) == -1) {
+ ber_free_element(params);
+ free(usmcookie);
+ return NULL;
+ }
+
+ ber_set_writecallback(digestelm, usm_digest_pos, usmcookie);
 
  bzero(&ber, sizeof(ber));
  ber_set_application(&ber, smi_application);
@@ -155,6 +190,29 @@ usm_genparams(struct snmp_agent *agent, size_t *len)
  return secparams;
 }
 
+static int
+usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
+    size_t secparamsoffset, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ u_char digest[EVP_MAX_MD_SIZE];
+
+ if ((agent->v3->level & SNMP_MSGFLAG_AUTH) == 0)
+ return 0;
+
+ if (usm->authlevel != USM_KEY_LOCALIZED)
+ return -1;
+
+ if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), buf,
+    buflen, digest, NULL) == NULL)
+ return -1;
+
+ memcpy(buf + secparamsoffset + usmcookie->digestoffset, digest,
+    usm_digestlen(usm->digest));
+ return 0;
+}
+
 static int
 usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
     off_t secparamsoffset, char *buf, size_t buflen, uint8_t level)
@@ -162,12 +220,15 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  struct usm_sec *usm = agent->v3->sec->data;
  struct ber ber;
  struct ber_element *secparams;
- char *engineid, *user;
- size_t engineidlen, userlen;
+ char *engineid, *user, *digest;
+ size_t engineidlen, userlen, digestlen;
  struct timespec now, timediff;
+ off_t digestoffset;
+ char exp_digest[EVP_MAX_MD_SIZE];
  uint32_t boots, time;
 
  bzero(&ber, sizeof(ber));
+ bzero(exp_digest, sizeof(exp_digest));
 
  ber_set_application(&ber, smi_application);
  ber_set_readbuf(&ber, buf, buflen);
@@ -175,8 +236,9 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  return -1;
  ber_free(&ber);
 
- if (ber_scanf_elements(secparams, "{xddxSS}", &engineid, &engineidlen,
-    &boots, &time, &user, &userlen) == -1)
+ if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen,
+    &boots, &time, &user, &userlen, &digestoffset, &digest,
+    &digestlen) == -1)
  goto fail;
 
  if (!usm->engineidset) {
@@ -226,6 +288,22 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
     memcmp(user, usm->user, userlen) != 0)
  goto fail;
 
+ if (level & SNMP_MSGFLAG_AUTH) {
+ if (digestlen != usm_digestlen(usm->digest))
+ goto fail;
+ }
+ if ((agent->v3->level & SNMP_MSGFLAG_AUTH)) {
+ bzero(packet + secparamsoffset + digestoffset, digestlen);
+ if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), packet,
+    packetlen, exp_digest, NULL) == NULL)
+ goto fail;
+
+ if (memcmp(exp_digest, digest, digestlen) != 0)
+ goto fail;
+ } else
+ if (digestlen != 0)
+ goto fail;
+
  ber_free_element(secparams);
  return 0;
 
@@ -234,20 +312,63 @@ fail:
  return -1;
 }
 
+static void
+usm_digest_pos(void *data, size_t offset)
+{
+ struct usm_cookie *usmcookie = data;
+
+ usmcookie->digestoffset = offset;
+}
+
 static void
 usm_free(void *data)
 {
  struct usm_sec *usm = data;
 
  free(usm->user);
+ free(usm->authkey);
  free(usm->engineid);
  free(usm);
 }
 
+int
+usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
+    size_t keylen, enum usm_key_level level)
+{
+ struct usm_sec *usm = sec->data;
+ char *lkey;
+
+ /*
+ * We could transform a master key to a local key here if we already
+ * have usm_setengineid called. Sine snmpc.c is the only caller at
+ * the moment there's no need, since it always calls this function
+ * first.
+ */
+ if (level == USM_KEY_PASSWORD) {
+ if ((usm->authkey = usm_passwd2mkey(digest, key)) == NULL)
+ return -1;
+ level = USM_KEY_MASTER;
+ keylen = EVP_MD_size(digest);
+ } else {
+ if (keylen != (size_t)EVP_MD_size(digest)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((lkey = malloc(keylen)) == NULL)
+ return -1;
+ memcpy(lkey, key, keylen);
+ usm->authkey = lkey;
+ }
+ usm->digest = digest;
+ usm->authlevel = level;
+ return 0;
+}
+
 int
 usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
 {
  struct usm_sec *usm = sec->data;
+ char *mkey;
 
  if (usm->engineid != NULL)
  free(usm->engineid);
@@ -257,6 +378,17 @@ usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
  usm->engineidlen = engineidlen;
  usm->engineidset = 1;
 
+ if (usm->authlevel == USM_KEY_MASTER) {
+ mkey = usm->authkey;
+ if ((usm->authkey = usm_mkey2lkey(usm, usm->digest,
+    mkey)) == NULL) {
+ usm->authkey = mkey;
+ return -1;
+ }
+ free(mkey);
+ usm->authlevel = USM_KEY_LOCALIZED;
+ }
+
  return 0;
 }
 
@@ -274,3 +406,81 @@ usm_setbootstime(struct snmp_sec *sec, uint32_t boots, uint32_t time)
  usm->timeset = 1;
  return 0;
 }
+
+static char *
+usm_passwd2mkey(const EVP_MD *md, const char *passwd)
+{
+ EVP_MD_CTX ctx;
+ int i, count;
+ const u_char *pw;
+ u_char *c;
+ u_char keybuf[EVP_MAX_MD_SIZE > 64 ? EVP_MAX_MD_SIZE : 64];
+ unsigned dlen;
+ char *key;
+
+ bzero(&ctx, sizeof(ctx));
+ EVP_DigestInit_ex(&ctx, md, NULL);
+ pw = (const u_char *)passwd;
+ for (count = 0; count < 1048576; count += 64) {
+ c = keybuf;
+ for (i = 0; i < 64; i++) {
+ if (*pw == '\0')
+ pw = (const u_char *)passwd;
+ *c++ = *pw++;
+ }
+ EVP_DigestUpdate(&ctx, keybuf, 64);
+ }
+ EVP_DigestFinal_ex(&ctx, keybuf, &dlen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ if ((key = malloc(dlen)) == NULL)
+ return NULL;
+ memcpy(key, keybuf, dlen);
+ return key;
+}
+
+static char *
+usm_mkey2lkey(struct usm_sec *usm, const EVP_MD *md, const char *mkey)
+{
+ EVP_MD_CTX ctx;
+ u_char buf[EVP_MAX_MD_SIZE];
+ u_char *lkey;
+ unsigned lklen;
+
+
+ bzero(&ctx, sizeof(ctx));
+ EVP_DigestInit_ex(&ctx, md, NULL);
+
+ EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
+ EVP_DigestUpdate(&ctx, usm->engineid, usm->engineidlen);
+ EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
+
+ EVP_DigestFinal_ex(&ctx, buf, &lklen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ if ((lkey = malloc(lklen)) == NULL)
+ return NULL;
+ memcpy(lkey, buf, lklen);
+ return lkey;
+}
+
+static size_t
+usm_digestlen(const EVP_MD *md)
+{
+ switch (EVP_MD_type(md)) {
+ case NID_md5:
+ case NID_sha1:
+                return 12;
+        case NID_sha224:
+                return 16;
+        case NID_sha256:
+                return 24;
+        case NID_sha384:
+                return 32;
+        case NID_sha512:
+                return 48;
+        default:
+                return 0;
+
+ }
+}
diff --git a/usm.h b/usm.h
index 1def439..636cd0e 100644
--- a/usm.h
+++ b/usm.h
@@ -18,6 +18,15 @@
 
 #include "snmp.h"
 
+enum usm_key_level {
+ USM_KEY_UNSET = 0,
+ USM_KEY_PASSWORD,
+ USM_KEY_MASTER,
+ USM_KEY_LOCALIZED
+};
+
 struct snmp_sec *usm_init(const char *, size_t);
+int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
+    enum usm_key_level);
 int usm_setengineid(struct snmp_sec *, char *, size_t);
 int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [4/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
This diff adds support for authPriv.

diff --git a/snmp.1 b/snmp.1
index e810560..fe283a5 100644
--- a/snmp.1
+++ b/snmp.1
@@ -28,6 +28,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -36,6 +37,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Ar agent
 .Ar oid ...
@@ -46,6 +49,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -54,6 +58,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm cIipt
 .Op Fl C Cm E Ar endoid
@@ -66,6 +72,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -74,6 +81,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm n Ns Ar nonrep Ns Cm r Ns Ar maxrep
 .Ar agent
@@ -85,6 +94,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -93,6 +103,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm cipn Ns Ar nonrep Ns Cm r Ns Ar maxrep
 .Ar agent
@@ -104,6 +116,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -111,6 +124,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Ar agent uptime trapoid
 .Oo Ar varoid type value Oc ...
@@ -302,6 +317,14 @@ 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 K Ar localpriv
+The localized privacy password for the user in hexadecimal format
+.Po
+optionally prefixed with a
+.Cm 0x
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl k Ar localauth
 The localized authentication password for the user in hexadecimal format
 .Po
@@ -313,14 +336,24 @@ This option is only used by
 .It Fl l Ar seclevel
 The security level.
 Values can be
-.Cm noAuthNoPriv Pq default
-or
+.Cm noAuthNoPriv Pq default ,
 .Cm authNoPriv
 .Po
 requires either
 .Fl A
 or
 .Fl k
+.Pc
+or
+.Cm authPriv
+.Po
+requires either
+.Fl X
+or
+.Fl K
+in addition to the
+.Cm authNoPriv
+requirements
 .Pc .
 This option is only used by
 .Fl v Cm 3 .
@@ -382,6 +415,22 @@ or
 .Cm 3 .
 Currently defaults to
 .Cm 2c .
+.It Fl X Ar privpass
+The privacy password for the user.
+This will be tansformed to
+.Ar localpriv .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl x Ar cipher
+Sets the cipher
+.Pq privacy
+protocol.
+Options are
+.Cm DES
+and
+.Cm AES .
+This option is only used by
+.Fl v Cm 3 .
 .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
diff --git a/snmp.c b/snmp.c
index 57b40e3..ba4dc3c 100644
--- a/snmp.c
+++ b/snmp.c
@@ -357,7 +357,7 @@ static char *
 snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
 {
  struct ber ber;
- struct ber_element *message, *scopedpdu = NULL, *secparams;
+ struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
  ssize_t securitysize, ret;
  size_t secparamsoffset;
  char *securityparams = NULL, *buf, *packet = NULL;
@@ -400,6 +400,13 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
  ber_free_elements(scopedpdu);
  goto fail;
  }
+ if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
+ if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
+    cookie)) == NULL)
+ goto fail;
+ ber_free_elements(scopedpdu);
+ scopedpdu = encpdu;
+ }
  if (ber_printf_elements(message, "d{idxd}xe",
     agent->version, msgid, 1472, &(agent->v3->level),
     (size_t) 1, agent->v3->sec->model, securityparams,
@@ -449,8 +456,9 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
  size_t msgflagslen, secparamslen;
  struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname;
  off_t secparamsoffset;
- char *engineid;
- size_t engineidlen;
+ char *encpdu, *engineid;
+ size_t encpdulen, engineidlen;
+ void *cookie = NULL;
 
  bzero(&ber, sizeof(ber));
  ber_set_application(&ber, smi_application);
@@ -482,9 +490,19 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
  if (msgflagslen != 1)
  goto fail;
  if (agent->v3->sec->parseparams(agent, buf, buflen,
-    secparamsoffset, secparams, secparamslen,
-    msgflags[0]) == -1)
+    secparamsoffset, secparams, secparamslen, msgflags[0],
+    &cookie) == -1) {
+ cookie = NULL;
  goto fail;
+ }
+ if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
+ if (ber_scanf_elements(scopedpdu, "x", &encpdu,
+    &encpdulen) == -1)
+ goto fail;
+ if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
+    encpdulen, cookie)) == NULL)
+ goto fail;
+ }
  if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid,
     &engineidlen, &ctxname) == -1)
  goto fail;
@@ -502,11 +520,14 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
  }
 
  ber_free_elements(message);
+ agent->v3->sec->freecookie(cookie);
  return pdu;
  }
  /* NOTREACHED */
 
 fail:
+ if (version == SNMP_V3)
+ agent->v3->sec->freecookie(cookie);
  ber_free_elements(message);
  return NULL;
 }
diff --git a/snmp.h b/snmp.h
index df20f0d..ce62119 100644
--- a/snmp.h
+++ b/snmp.h
@@ -114,9 +114,13 @@ struct snmp_sec {
  enum snmp_security_model model;
  int (*init)(struct snmp_agent *);
  char *(*genparams)(struct snmp_agent *, size_t *, void **);
+ struct ber_element *(*encpdu)(struct snmp_agent *,
+    struct ber_element *, void *);
  int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
  int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
-    size_t, uint8_t);
+    size_t, uint8_t, void **);
+ struct ber_element *(*decpdu)(struct snmp_agent *, char *, size_t,
+    void *);
  void (*free)(void *);
  void (*freecookie)(void *);
  void *data;
diff --git a/snmpc.c b/snmpc.c
index edaa50c..658ca82 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -42,7 +42,7 @@
 #include "snmp.h"
 #include "usm.h"
 
-#define GETOPT_COMMON "A:a:c:E:e:k:l:n:O:r:t:u:v:Z:"
+#define GETOPT_COMMON "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
 
 int snmpc_get(int, char *[]);
 int snmpc_walk(int, char *[]);
@@ -98,11 +98,15 @@ int
 main(int argc, char *argv[])
 {
  const EVP_MD *md = NULL;
+ const EVP_CIPHER *cipher = NULL;
  struct snmp_sec *sec;
  char *user = NULL;
  enum usm_key_level authkeylevel;
  char *authkey = NULL;
  size_t authkeylen = 0;
+ enum usm_key_level privkeylevel;
+ char *privkey = NULL;
+ size_t privkeylen = 0;
  int seclevel = SNMP_MSGFLAG_REPORT;
  char *ctxname = NULL;
  char *ctxengineid = NULL, *secengineid = NULL;
@@ -193,6 +197,16 @@ main(int argc, char *argv[])
  err(1, "-3e");
  }
  break;
+ case 'K':
+ privkey = snmpc_hex2bin(optarg, &privkeylen);
+ if (privkey == NULL) {
+ if (errno == EINVAL)
+ errx(1, "Bad key value after "
+    "-3K flag.");
+ errx(1, "-3K");
+ }
+ privkeylevel = USM_KEY_LOCALIZED;
+ break;
  case 'k':
  authkey = snmpc_hex2bin(optarg, &authkeylen);
  if (authkey == NULL) {
@@ -208,6 +222,9 @@ main(int argc, char *argv[])
  else if (strcmp(optarg, "authNoPriv") == 0)
  seclevel = SNMP_MSGFLAG_AUTH |
     SNMP_MSGFLAG_REPORT;
+ else if (strcmp(optarg, "authPriv") == 0)
+ seclevel = SNMP_MSGFLAG_AUTH |
+    SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
  else
  errx(1, "Invalid security level specified "
     "after -l flag: %s", optarg);
@@ -369,6 +386,21 @@ main(int argc, char *argv[])
  }
  }
  break;
+ case 'X':
+ privkey = optarg;
+ privkeylen = strlen(privkey);
+ privkeylevel = USM_KEY_PASSWORD;
+ break;
+ case 'x':
+ if (strcasecmp(optarg, "DES") == 0)
+ cipher = EVP_des_cbc();
+ else if (strcasecmp(optarg, "AES") == 0)
+ cipher = EVP_aes_128_cfb128();
+ else
+ errx(1, "Invalid privacy protocol "
+    "specified after -3x flag: %s",
+    optarg);
+ break;
  case 'Z':
  boots = strtoll(optarg, &strtolp, 10);
  if (boots < 0 || strtolp == optarg || strtolp[0] != ',')
@@ -403,6 +435,15 @@ main(int argc, char *argv[])
     authkeylevel) == -1)
  err(1, "Can't set authkey");
  }
+ if (seclevel & SNMP_MSGFLAG_PRIV) {
+ if (cipher == NULL)
+ cipher = EVP_des_cbc();
+ if (privkey == NULL)
+ errx(1, "No privKey or privPassword specified");
+ if (usm_setpriv(sec, cipher, privkey, privkeylen,
+    privkeylevel) == -1)
+ err(1, "Can't set authkey");
+ }
  if (secengineid != NULL) {
  if (usm_setengineid(sec, secengineid,
     secengineidlen) == -1)
@@ -1087,9 +1128,9 @@ usage(void)
     snmp_app->name,
     snmp_app->usecommonopt ?
     " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
-    "            [-E ctxengineid] [-k localauth] [-l seclevel] [-n ctxname]\n"
-    "            [-O afnqvxSQ] [-r retries] [-t timeout] [-u user] [-v version]\n"
-    "            [-Z boots,time]\n"
+    "            [-E ctxengineid] [-K localpriv] [-k localauth] [-l seclevel]\n"
+    "            [-n ctxname] [-O afnqvxSQ] [-r retries] [-t timeout] [-u user]\n"
+    "            [-v version] [-X privpass] [-x cipher] [-Z boots,time]\n"
     "            " : "",
     snmp_app->usage == NULL ? "" : snmp_app->usage);
  exit(1);
diff --git a/usm.c b/usm.c
index df9a53d..ba2020c 100644
--- a/usm.c
+++ b/usm.c
@@ -44,6 +44,9 @@ struct usm_sec {
  enum usm_key_level authlevel;
  const EVP_MD *digest;
  char *authkey;
+ enum usm_key_level privlevel;
+ const EVP_CIPHER *cipher;
+ char *privkey;
  int bootsset;
  uint32_t boots;
  int timeset;
@@ -53,13 +56,21 @@ struct usm_sec {
 
 struct usm_cookie {
  size_t digestoffset;
+ long long salt;
+ uint32_t boots;
+ uint32_t time;
 };
 
 static int usm_doinit(struct snmp_agent *);
 static char *usm_genparams(struct snmp_agent *, size_t *, void **);
 static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
+static struct ber_element *usm_encpdu(struct snmp_agent *agent,
+    struct ber_element *pdu, void *cookie);
+static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *,
+    char *, size_t, size_t *);
 static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
-    size_t, uint8_t);
+    size_t, uint8_t, void **);
+struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *);
 static void usm_digest_pos(void *, size_t);
 static void usm_free(void *);
 static char *usm_passwd2mkey(const EVP_MD *, const char *);
@@ -95,7 +106,9 @@ usm_init(const char *user, size_t userlen)
  sec->model = SNMP_SEC_USM;
  sec->init = usm_doinit;
  sec->genparams = usm_genparams;
+ sec->encpdu = usm_encpdu;
  sec->parseparams = usm_parseparams;
+ sec->decpdu = usm_decpdu;
  sec->finalparams = usm_finalparams;
  sec->free = usm_free;
  sec->freecookie = free;
@@ -143,7 +156,6 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
  ssize_t berlen;
  struct usm_cookie *usmcookie;
  struct timespec now, timediff;
- uint32_t boots, time;
 
  bzero(digest, sizeof(digest));
 
@@ -151,21 +163,24 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
  return NULL;
  *cookie = usmcookie;
 
+ arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt));
  if (usm->timeset) {
  if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
  free(usmcookie);
  return NULL;
  }
  timespecsub(&now, &(usm->timecheck), &timediff);
- time = usm->time + timediff.tv_sec;
+ usmcookie->time = usm->time + timediff.tv_sec;
  } else
- time = 0;
- boots = usm->boots;
+ usmcookie->time = 0;
+ usmcookie->boots = usm->boots;
 
  if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
-    usm->engineidlen, boots, time, usm->user, usm->userlen, digest,
-    agent->v3->level & SNMP_MSGFLAG_AUTH ? usm_digestlen(usm->digest) :
-    (size_t) 0, NULL, (size_t) 0)) == NULL) {
+    usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
+    usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
+    usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
+    agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
+    (size_t) 0)) == NULL) {
  free(usmcookie);
  return NULL;
  }
@@ -190,6 +205,93 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
  return secparams;
 }
 
+static struct ber_element *
+usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ struct ber ber;
+ struct ber_element *retpdu;
+ char *serialpdu, *encpdu;
+ ssize_t pdulen;
+ size_t encpdulen;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+ pdulen = ber_write_elements(&ber, pdu);
+ if (pdulen == -1)
+ return NULL;
+
+ ber_get_writebuf(&ber, (void **)&serialpdu);
+
+ encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu,
+    pdulen, &encpdulen);
+ ber_free(&ber);
+ if (encpdu == NULL)
+ return NULL;
+
+ retpdu = ber_add_nstring(NULL, encpdu, encpdulen);
+ free(encpdu);
+ return retpdu;
+}
+
+static char *
+usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key,
+    struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen)
+{
+ EVP_CIPHER_CTX ctx;
+ size_t i;
+ char iv[EVP_MAX_IV_LENGTH];
+ char *salt = (char *)&(cookie->salt);
+ char *outtext;
+ int len, len2;
+ uint32_t ivv;
+
+ switch (EVP_CIPHER_type(cipher)) {
+ case NID_des_cbc:
+ /* RFC3414, chap 8.1.1.1. */
+ for (i = 0; i < 8; i++)
+ iv[i] = salt[i] ^ key[USM_SALTOFFSET + i];
+ break;
+ case NID_aes_128_cfb128:
+ /* RFC3826, chap 3.1.2.1. */
+ ivv = htobe32(cookie->boots);
+ memcpy(iv, &ivv, sizeof(ivv));
+ ivv = htobe32(cookie->time);
+ memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
+ memcpy(iv + 2 * sizeof(ivv), &(cookie->salt),
+    sizeof(cookie->salt));
+ break;
+ default:
+ return NULL;
+ }
+
+ bzero(&ctx, sizeof(ctx));
+ if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc))
+ return NULL;
+
+ EVP_CIPHER_CTX_set_padding(&ctx, do_enc);
+
+ *outlen = EVP_CIPHER_block_size(cipher);
+ /* Maximum output size */
+ *outlen = pdulen + (*outlen - (pdulen % *outlen));
+
+ if ((outtext = malloc(*outlen)) == NULL)
+ return NULL;
+
+ if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) &&
+    EVP_CipherFinal_ex(&ctx, outtext + len, &len2))
+ *outlen = len + len2;
+ else {
+ free(outtext);
+ outtext = NULL;
+ }
+
+ EVP_CIPHER_CTX_cleanup(&ctx);
+
+ return outtext;
+}
+
 static int
 usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
     size_t secparamsoffset, void *cookie)
@@ -215,17 +317,18 @@ usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
 
 static int
 usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
-    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level)
+    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level,
+    void **cookie)
 {
  struct usm_sec *usm = agent->v3->sec->data;
  struct ber ber;
  struct ber_element *secparams;
- char *engineid, *user, *digest;
- size_t engineidlen, userlen, digestlen;
+ char *engineid, *user, *digest, *salt;
+ size_t engineidlen, userlen, digestlen, saltlen;
  struct timespec now, timediff;
  off_t digestoffset;
  char exp_digest[EVP_MAX_MD_SIZE];
- uint32_t boots, time;
+ struct usm_cookie *usmcookie;
 
  bzero(&ber, sizeof(ber));
  bzero(exp_digest, sizeof(exp_digest));
@@ -236,10 +339,17 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  return -1;
  ber_free(&ber);
 
- if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen,
-    &boots, &time, &user, &userlen, &digestoffset, &digest,
-    &digestlen) == -1)
+ if ((usmcookie = malloc(sizeof(*usmcookie))) == NULL)
+ goto fail;
+ *cookie = usmcookie;
+
+ if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen,
+    &(usmcookie->boots), &(usmcookie->time), &user, &userlen,
+    &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1)
  goto fail;
+ if (saltlen != sizeof(usmcookie->salt) && saltlen != 0)
+ goto fail;
+ memcpy(&(usmcookie->salt), salt, saltlen);
 
  if (!usm->engineidset) {
  if (usm_setengineid(agent->v3->sec, engineid,
@@ -253,12 +363,12 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  }
 
  if (!usm->bootsset) {
- usm->boots = boots;
+ usm->boots = usmcookie->boots;
  usm->bootsset = 1;
  } else {
- if (boots < usm->boots)
+ if (usmcookie->boots < usm->boots)
  goto fail;
- if (boots > usm->boots) {
+ if (usmcookie->boots > usm->boots) {
  usm->bootsset = 0;
  usm->timeset = 0;
  usm_doinit(agent);
@@ -267,7 +377,7 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  }
 
  if (!usm->timeset) {
- usm->time = time;
+ usm->time = usmcookie->time;
  if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1)
  goto fail;
  usm->timeset = 1;
@@ -275,8 +385,10 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  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) {
+ if (usmcookie->time <
+    usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
+    usmcookie->time >
+    usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
  usm->bootsset = 0;
  usm->timeset = 0;
  usm_doinit(agent);
@@ -308,10 +420,35 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  return 0;
 
 fail:
+ free(usmcookie);
  ber_free_element(secparams);
  return -1;
 }
 
+struct ber_element *
+usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void *cookie)
+{
+ struct usm_sec *usm = agent->v3->sec->data;
+ struct usm_cookie *usmcookie = cookie;
+ struct ber ber;
+ struct ber_element *scopedpdu;
+ char *rawpdu;
+ size_t rawpdulen;
+
+ if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie,
+    encpdu, encpdulen, &rawpdulen)) == NULL)
+ return NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber_set_application(&ber, smi_application);
+ ber_set_readbuf(&ber, rawpdu, rawpdulen);
+ scopedpdu = ber_read_elements(&ber, NULL);
+ ber_free(&ber);
+ free(rawpdu);
+
+ return scopedpdu;
+}
+
 static void
 usm_digest_pos(void *data, size_t offset)
 {
@@ -327,6 +464,7 @@ usm_free(void *data)
 
  free(usm->user);
  free(usm->authkey);
+ free(usm->privkey);
  free(usm->engineid);
  free(usm);
 }
@@ -364,6 +502,43 @@ usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
  return 0;
 }
 
+int
+usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key,
+    size_t keylen, enum usm_key_level level)
+{
+ struct usm_sec *usm = sec->data;
+ char *lkey;
+
+ if (usm->digest == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * We could transform a master key to a local key here if we already
+ * have usm_setengineid called. Sine snmpc.c is the only caller at
+ * the moment there's no need, since it always calls us first.
+ */
+ if (level == USM_KEY_PASSWORD) {
+ if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL)
+ return -1;
+ level = USM_KEY_MASTER;
+ keylen = EVP_MD_size(usm->digest);
+ } else {
+ if (keylen != (size_t)EVP_MD_size(usm->digest)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((lkey = malloc(keylen)) == NULL)
+ return -1;
+ memcpy(lkey, key, keylen);
+ usm->privkey = lkey;
+ }
+ usm->cipher = cipher;
+ usm->privlevel = level;
+ return 0;
+}
+
 int
 usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
 {
@@ -388,6 +563,16 @@ usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
  free(mkey);
  usm->authlevel = USM_KEY_LOCALIZED;
  }
+ if (usm->privlevel == USM_KEY_MASTER) {
+ mkey = usm->privkey;
+ if ((usm->privkey = usm_mkey2lkey(usm, usm->digest,
+    mkey)) == NULL) {
+ usm->privkey = mkey;
+ return -1;
+ }
+ free(mkey);
+ usm->privlevel = USM_KEY_LOCALIZED;
+ }
 
  return 0;
 }
diff --git a/usm.h b/usm.h
index 636cd0e..b1aea8b 100644
--- a/usm.h
+++ b/usm.h
@@ -28,5 +28,7 @@ enum usm_key_level {
 struct snmp_sec *usm_init(const char *, size_t);
 int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
     enum usm_key_level);
+int usm_setpriv(struct snmp_sec *, const EVP_CIPHER *, const char *, size_t,
+    enum usm_key_level);
 int usm_setengineid(struct snmp_sec *, char *, size_t);
 int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [5/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
This diff adds support for the HP laserjet. The problem with this device
is that it only returns the engineid on probing, but only returns boots
and time after a packet has been send with full auth/enc.

It's not an intrusive diff and allows us to easily walk this device
without resorting to tcpdump for the information and setting it via -e
and -Z, so I think it's worth having it.

diff --git a/usm.c b/usm.c
index ba2020c..f34a6c9 100644
--- a/usm.c
+++ b/usm.c
@@ -142,6 +142,12 @@ usm_doinit(struct snmp_agent *agent)
  agent->v3->level = level;
  usm->userlen = userlen;
 
+ /* Ugly hack for HP Laserjet */
+ if (!usm->engineidset || !usm->bootsset || !usm->timeset) {
+ if ((ber = snmp_get(agent, NULL, 0)) == NULL)
+ return -1;
+ ber_free_element(ber);
+ }
  return 0;
 }
 
@@ -395,6 +401,14 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
  goto fail;
  }
  }
+ /*
+ * Don't assume these are set if both are zero.
+ * Ugly hack for HP Laserjet
+ */
+ if (usm->boots == 0 && usm->time == 0) {
+ usm->bootsset = 0;
+ usm->timeset = 0;
+ }
 
  if (userlen != usm->userlen ||
     memcmp(user, usm->user, userlen) != 0)

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [0/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
I would like to move forward with this.
Is anyone willing to look at this?

On 9/2/19 9:06 AM, Martijn van Duren wrote:

> Hello tech@,
>
> I've worked hard to get SNMPv3 support for snmp(1). Here's the end
> result. This mail contains the full diff for people just wanting to
> test, the follow up mails will contain the incremental diffs (still
> massive beasts) so they're easier to review.
>
> I've implemented Net-SNMP's -A, -a, -E, -e, -3K (as -K), -3k (as -k),
> -l, -n, -u, -X, -x and -Z. I choose to leave out the -3 because the
> way to handle this is really ugly and -K and -k are unused by
> Net-SNMP.
> This is also the reason there's no support for master keys, because -m
> and -M are used by Net-SNMP and I don't know if I want to implement that
> at some point and I haven't seen a device that needs the master key. The
> scaffolding for adding master key support is there and if someone has
> an actual usecase for it and comes up with a good suggestion for a flag
> I'll be happy to implement it.
>
> Tested with snmpd(8), netsnmpd and HP Laserjet 4730mfp by me and
> netgear GS724Tv4 ProSafe by semarie@ on previous iteration of diff.
>
> Tests, feedback, OKs welcome.
>
> martijn@
>
> diff --git a/Makefile b/Makefile
> index 62bb556..102582b 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1,9 +1,9 @@
>  # $OpenBSD: Makefile,v 1.1 2019/08/09 06:17:59 martijn Exp $
>  
>  PROG= snmp
> -SRCS= mib.c smi.c snmp.c snmpc.c
> -LDADD+= -lutil
> -DPADD+= ${LIBUTIL}
> +SRCS= mib.c smi.c snmp.c snmpc.c usm.c
> +LDADD+= -lcrypto -lutil
> +DPADD+= ${LIBCRYPTO} ${LIBUTIL}
>  
>  MAN= snmp.1
>  
> diff --git a/snmp.1 b/snmp.1
> index e158ba0..fe283a5 100644
> --- a/snmp.1
> +++ b/snmp.1
> @@ -23,50 +23,110 @@
>  .Sh SYNOPSIS
>  .Nm
>  .Cm get | getnext
> +.Op Fl A Ar authpass
> +.Op Fl a Ar digest
>  .Op Fl c Ar community
> +.Op Fl E Ar ctxengineid
> +.Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
> +.Op Fl k Ar localauth
> +.Op Fl l Ar seclevel
> +.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 X Ar privpass
> +.Op Fl x Ar cipher
> +.Op Fl Z Ar boots , Ns Ar time
>  .Ar agent
>  .Ar oid ...
>  .Nm
>  .Cm walk
> +.Op Fl A Ar authpass
> +.Op Fl a Ar digest
>  .Op Fl c Ar community
> +.Op Fl E Ar ctxengineid
> +.Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
> +.Op Fl k Ar localauth
> +.Op Fl l Ar seclevel
> +.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 X Ar privpass
> +.Op Fl x Ar cipher
> +.Op Fl Z Ar boots , Ns Ar time
>  .Op Fl C Cm cIipt
>  .Op Fl C Cm E Ar endoid
>  .Ar agent
>  .Op Ar oid
>  .Nm
>  .Cm bulkget
> +.Op Fl A Ar authpass
> +.Op Fl a Ar digest
>  .Op Fl c Ar community
> +.Op Fl E Ar ctxengineid
> +.Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
> +.Op Fl k Ar localauth
> +.Op Fl l Ar seclevel
> +.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 X Ar privpass
> +.Op Fl x Ar cipher
> +.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 A Ar authpass
> +.Op Fl a Ar digest
>  .Op Fl c Ar community
> +.Op Fl E Ar ctxengineid
> +.Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
> +.Op Fl k Ar localauth
> +.Op Fl l Ar seclevel
> +.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 X Ar privpass
> +.Op Fl x Ar cipher
> +.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 A Ar authpass
> +.Op Fl a Ar digest
>  .Op Fl c Ar community
> +.Op Fl E Ar ctxengineid
> +.Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
> +.Op Fl k Ar localauth
> +.Op Fl l Ar seclevel
> +.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 X Ar privpass
> +.Op Fl x Ar cipher
> +.Op Fl Z Ar boots , Ns Ar time
>  .Ar agent uptime trapoid
>  .Oo Ar varoid type value Oc ...
>  .Nm
> @@ -145,6 +205,28 @@ Dump the tree of compiled-in MIB objects.
>  .Pp
>  The options are as follows:
>  .Bl -tag -width Ds
> +.It Fl A Ar authpass
> +The authentication password for the user.
> +This will be transformed to
> +.Ar localauth .
> +This option is only used by
> +.Fl v Cm 3 .
> +.It Fl a Ar digest
> +Set the digest
> +.Pq authentication
> +protocol.
> +Options are
> +.Cm MD5 ,
> +.Cm SHA ,
> +.Cm SHA-224 ,
> +.Cm SHA-256 ,
> +.Cm SHA-384
> +or
> +.Cm SHA-512 .
> +This option defaults to
> +.Cm MD5 .
> +This option is only used by
> +.Fl v Cm 3 .
>  .It Fl C Ar appopt
>  Set the application specific
>  .Ar appopt
> @@ -220,6 +302,66 @@ 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 K Ar localpriv
> +The localized privacy password for the user in hexadecimal format
> +.Po
> +optionally prefixed with a
> +.Cm 0x
> +.Pc .
> +This option is only used by
> +.Fl v Cm 3 .
> +.It Fl k Ar localauth
> +The localized authentication password for the user in hexadecimal format
> +.Po
> +optionally prefixed with a
> +.Cm 0x
> +.Pc .
> +This option is only used by
> +.Fl v Cm 3 .
> +.It Fl l Ar seclevel
> +The security level.
> +Values can be
> +.Cm noAuthNoPriv Pq default ,
> +.Cm authNoPriv
> +.Po
> +requires either
> +.Fl A
> +or
> +.Fl k
> +.Pc
> +or
> +.Cm authPriv
> +.Po
> +requires either
> +.Fl X
> +or
> +.Fl K
> +in addition to the
> +.Cm authNoPriv
> +requirements
> +.Pc .
> +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 +398,45 @@ 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 X Ar privpass
> +The privacy password for the user.
> +This will be tansformed to
> +.Ar localpriv .
> +This option is only used by
> +.Fl v Cm 3 .
> +.It Fl x Ar cipher
> +Sets the cipher
> +.Pq privacy
> +protocol.
> +Options are
> +.Cm DES
> +and
> +.Cm AES .
> +This option is only used by
> +.Fl v Cm 3 .
> +.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/snmp.c b/snmp.c
> index 7fac777..ba4dc3c 100644
> --- a/snmp.c
> +++ b/snmp.c
> @@ -32,6 +32,52 @@
>  
>  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 *);
> +static void snmp_v3_secparamsoffset(void *, size_t);
> +
> +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)
> @@ -50,21 +96,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)
>  {
> @@ -171,19 +250,16 @@ fail:
>  static struct ber_element *
>  snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
>  {
> - struct ber_element *message, *varbind;
> + struct ber_element *varbind;
>   struct ber_oid oid;
>   struct timespec start, now;
>   struct pollfd pfd;
> - struct ber ber;
> + char *message;
>   ssize_t len;
>   long long reqid, rreqid;
> - long long version;
> - char *community;
>   short direction;
>   int to, nfds, ret;
>   int tries;
> - void *ptr;
>   char buf[READ_BUF_SIZE];
>  
>   if (ber_scanf_elements(pdu, "{i", &reqid) != 0) {
> @@ -192,23 +268,8 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
>   return NULL;
>   }
>  
> - if ((message = ber_add_sequence(NULL)) == NULL) {
> - ber_free_elements(pdu);
> + if ((message = snmp_package(agent, pdu, &len)) == NULL)
>   return NULL;
> - }
> - if (ber_printf_elements(message, "dse", agent->version,
> -    agent->community, pdu) == NULL) {
> - ber_free_elements(pdu);
> - ber_free_elements(message);
> - return NULL;
> - }
> - memset(&ber, 0, sizeof(ber));
> - ber_set_application(&ber, smi_application);
> - len = ber_write_elements(&ber, message);
> - ber_free_elements(message);
> - message = NULL;
> - if (ber_get_writebuf(&ber, &ptr) < 1)
> - goto fail;
>  
>   clock_gettime(CLOCK_MONOTONIC, &start);
>   memcpy(&now, &start, sizeof(now));
> @@ -236,7 +297,7 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
>   goto fail;
>   }
>   if (direction == POLLOUT) {
> - ret = send(agent->fd, ptr, len, MSG_DONTWAIT);
> + ret = send(agent->fd, message, len, MSG_DONTWAIT);
>   if (ret == -1)
>   goto fail;
>   if (ret < len) {
> @@ -253,25 +314,10 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
>   errno = ECONNRESET;
>   if (ret <= 0)
>   goto fail;
> - ber_set_readbuf(&ber, buf, ret);
> - if ((message = ber_read_elements(&ber, NULL)) == NULL) {
> - direction = POLLOUT;
> + if ((pdu = snmp_unpackage(agent, buf, ret)) == NULL) {
>   tries--;
> - continue;
> - }
> - if (ber_scanf_elements(message, "{ise", &version, &community,
> -    &pdu) != 0) {
> - errno = EPROTO;
>   direction = POLLOUT;
> - tries--;
> - continue;
> - }
> - /* Skip invalid packets; should not happen */
> - if (version != agent->version ||
> -    strcmp(community, agent->community) != 0) {
>   errno = EPROTO;
> - direction = POLLOUT;
> - tries--;
>   continue;
>   }
>   /* Validate pdu format and check request id */
> @@ -282,7 +328,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--;
> @@ -294,20 +340,202 @@ snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
>   errno = EPROTO;
>   direction = POLLOUT;
>   tries--;
> - break;
> + continue;
>   }
>   }
> - if (varbind != NULL)
> - continue;
>  
> - ber_unlink_elements(message->be_sub->be_next);
> - ber_free_elements(message);
> - ber_free(&ber);
> + free(message);
>   return pdu;
>   }
>  
>  fail:
> + free(message);
> + return NULL;
> +}
> +
> +static char *
> +snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
> +{
> + struct ber ber;
> + struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
> + ssize_t securitysize, ret;
> + size_t secparamsoffset;
> + char *securityparams = NULL, *buf, *packet = NULL;
> + long long msgid;
> + void *cookie = NULL;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> +
> + if ((message = ber_add_sequence(NULL)) == NULL) {
> + ber_free_elements(pdu);
> + goto fail;
> + }
> +
> + switch (agent->version) {
> + case SNMP_V1:
> + case SNMP_V2C:
> + if (ber_printf_elements(message, "dse", agent->version,
> +    agent->community, pdu) == NULL) {
> + ber_free_elements(pdu);
> + goto fail;
> + }
> + 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, &cookie)) == NULL) {
> + ber_free_elements(scopedpdu);
> + goto fail;
> + }
> + if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
> + if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
> +    cookie)) == NULL)
> + goto fail;
> + ber_free_elements(scopedpdu);
> + scopedpdu = encpdu;
> + }
> + if (ber_printf_elements(message, "d{idxd}xe",
> +    agent->version, msgid, 1472, &(agent->v3->level),
> +    (size_t) 1, agent->v3->sec->model, securityparams,
> +    securitysize, scopedpdu) == NULL)
> + goto fail;
> + if (ber_scanf_elements(message, "{SSe", &secparams) == -1)
> + goto fail;
> + ber_set_writecallback(secparams, snmp_v3_secparamsoffset,
> +    &secparamsoffset);
> + break;
> + }
> +
> + if ((ret = ber_write_elements(&ber, message)) == -1)
> + goto fail;
> + *len = (size_t) ret;
> + if (ber_get_writebuf(&ber, (void **)&buf) != -1 &&
> +    (packet = malloc(ret)) != NULL)
> + memcpy(packet, buf, ret);
> + ber_free(&ber);
> +
> + if (agent->version == SNMP_V3 && packet != NULL) {
> + if (agent->v3->sec->finalparams(agent, packet,
> +    ret, secparamsoffset, cookie) == -1) {
> + free(packet);
> + packet = NULL;
> + }
> + }
> +
> +fail:
> + if (agent->version == SNMP_V3)
> + agent->v3->sec->freecookie(cookie);
>   ber_free_elements(message);
> + free(securityparams);
> + return packet;
> +}
> +
> +static struct ber_element *
> +snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
> +{
> + struct ber ber;
> + enum snmp_version version;
> + char *community;
> + struct ber_element *pdu;
> + 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 *encpdu, *engineid;
> + size_t encpdulen, engineidlen;
> + void *cookie = NULL;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> +
> + ber_set_readbuf(&ber, buf, buflen);
> + if ((message = ber_read_elements(&ber, NULL)) == NULL)
> + return NULL;
>   ber_free(&ber);
> +
> + if (ber_scanf_elements(message, "{de", &version, &payload) != 0)
> + goto fail;
> +
> + if (version != agent->version)
> + goto fail;
> +
> + switch (version) {
> + case SNMP_V1:
> + case SNMP_V2C:
> + if (ber_scanf_elements(payload, "se", &community, &pdu) == -1)
> + goto fail;
> + ber_unlink_elements(payload);
> + ber_free_elements(message);
> + return pdu;
> + case SNMP_V3:
> + 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],
> +    &cookie) == -1) {
> + cookie = NULL;
> + goto fail;
> + }
> + if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
> + if (ber_scanf_elements(scopedpdu, "x", &encpdu,
> +    &encpdulen) == -1)
> + goto fail;
> + if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
> +    encpdulen, cookie)) == NULL)
> + 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);
> + agent->v3->sec->freecookie(cookie);
> + return pdu;
> + }
> + /* NOTREACHED */
> +
> +fail:
> + if (version == SNMP_V3)
> + agent->v3->sec->freecookie(cookie);
> + ber_free_elements(message);
>   return NULL;
>  }
> +
> +static void
> +snmp_v3_secparamsoffset(void *cookie, size_t offset)
> +{
> + size_t *spoffset = cookie;
> +
> + *spoffset = offset;
> +}
> diff --git a/snmp.h b/snmp.h
> index 502aa75..ce62119 100644
> --- a/snmp.h
> +++ b/snmp.h
> @@ -108,12 +108,43 @@ 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 *, void **);
> + struct ber_element *(*encpdu)(struct snmp_agent *,
> +    struct ber_element *, void *);
> + int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
> + int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
> +    size_t, uint8_t, void **);
> + struct ber_element *(*decpdu)(struct snmp_agent *, char *, size_t,
> +    void *);
> + void (*free)(void *);
> + void (*freecookie)(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 +154,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/snmpc.c b/snmpc.c
> index dc48b10..658ca82 100644
> --- a/snmpc.c
> +++ b/snmpc.c
> @@ -23,8 +23,10 @@
>  #include <sys/un.h>
>  
>  #include <arpa/inet.h>
> +#include <openssl/evp.h>
>  
>  #include <ber.h>
> +#include <ctype.h>
>  #include <err.h>
>  #include <errno.h>
>  #include <netdb.h>
> @@ -38,16 +40,19 @@
>  
>  #include "smi.h"
>  #include "snmp.h"
> +#include "usm.h"
>  
> -#define GETOPT_COMMON "c:r:t:v:O:"
> +#define GETOPT_COMMON "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
>  
>  int snmpc_get(int, char *[]);
>  int snmpc_walk(int, char *[]);
>  int snmpc_trap(int, char *[]);
>  int snmpc_mibtree(int, char *[]);
> +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 {
> @@ -70,10 +75,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;
> @@ -91,6 +97,22 @@ enum smi_output_string output_string = smi_os_default;
>  int
>  main(int argc, char *argv[])
>  {
> + const EVP_MD *md = NULL;
> + const EVP_CIPHER *cipher = NULL;
> + struct snmp_sec *sec;
> + char *user = NULL;
> + enum usm_key_level authkeylevel;
> + char *authkey = NULL;
> + size_t authkeylen = 0;
> + enum usm_key_level privkeylevel;
> + char *privkey = NULL;
> + size_t privkeylen = 0;
> + 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;
> @@ -130,9 +152,86 @@ main(int argc, char *argv[])
>  
>   while ((ch = getopt(argc, argv, optstr)) != -1) {
>   switch (ch) {
> + case 'A':
> + authkey = optarg;
> + authkeylen = strlen(authkey);
> + authkeylevel = USM_KEY_PASSWORD;
> + break;
> + case 'a':
> + if (strcasecmp(optarg, "MD5") == 0)
> + md = EVP_md5();
> + else if (strcasecmp(optarg, "SHA") == 0)
> + md = EVP_sha1();
> + else if (strcasecmp(optarg, "SHA-224") == 0)
> + md = EVP_sha224();
> + else if (strcasecmp(optarg, "SHA-256") == 0)
> + md = EVP_sha256();
> + else if (strcasecmp(optarg, "SHA-384") == 0)
> + md = EVP_sha384();
> + else if (strcasecmp(optarg, "SHA-512") == 0)
> + md = EVP_sha512();
> + else
> + errx(1, "Invalid authentication protocol "
> +    "specified after -a flag: %s", optarg);
> + break;
>   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 'K':
> + privkey = snmpc_hex2bin(optarg, &privkeylen);
> + if (privkey == NULL) {
> + if (errno == EINVAL)
> + errx(1, "Bad key value after "
> +    "-3K flag.");
> + errx(1, "-3K");
> + }
> + privkeylevel = USM_KEY_LOCALIZED;
> + break;
> + case 'k':
> + authkey = snmpc_hex2bin(optarg, &authkeylen);
> + if (authkey == NULL) {
> + if (errno == EINVAL)
> + errx(1, "Bad key value after -k flag.");
> + err(1, "-k");
> + }
> + authkeylevel = USM_KEY_LOCALIZED;
> + break;
> + case 'l':
> + if (strcmp(optarg, "noAuthNoPriv") == 0)
> + seclevel = SNMP_MSGFLAG_REPORT;
> + else if (strcmp(optarg, "authNoPriv") == 0)
> + seclevel = SNMP_MSGFLAG_AUTH |
> +    SNMP_MSGFLAG_REPORT;
> + else if (strcmp(optarg, "authPriv") == 0)
> + seclevel = SNMP_MSGFLAG_AUTH |
> +    SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
> + else
> + errx(1, "Invalid security level specified "
> +    "after -l flag: %s", optarg);
> + break;
> + case 'n':
> + ctxname = optarg;
> + break;
>   case 'r':
>   if ((retries = strtonum(optarg, 0, INT_MAX,
>      &errstr)) == 0) {
> @@ -147,11 +246,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;
> @@ -282,6 +386,33 @@ main(int argc, char *argv[])
>   }
>   }
>   break;
> + case 'X':
> + privkey = optarg;
> + privkeylen = strlen(privkey);
> + privkeylevel = USM_KEY_PASSWORD;
> + break;
> + case 'x':
> + if (strcasecmp(optarg, "DES") == 0)
> + cipher = EVP_des_cbc();
> + else if (strcasecmp(optarg, "AES") == 0)
> + cipher = EVP_aes_128_cfb128();
> + else
> + errx(1, "Invalid privacy protocol "
> +    "specified after -3x flag: %s",
> +    optarg);
> + 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();
>   }
> @@ -289,6 +420,50 @@ 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 (seclevel & SNMP_MSGFLAG_AUTH) {
> + if (md == NULL)
> + md = EVP_md5();
> + if (authkey == NULL)
> + errx(1, "No authKey or authPassword specified");
> + if (usm_setauth(sec, md, authkey, authkeylen,
> +    authkeylevel) == -1)
> + err(1, "Can't set authkey");
> + }
> + if (seclevel & SNMP_MSGFLAG_PRIV) {
> + if (cipher == NULL)
> + cipher = EVP_des_cbc();
> + if (privkey == NULL)
> + errx(1, "No privKey or privPassword specified");
> + if (usm_setpriv(sec, cipher, privkey, privkeylen,
> +    privkeylevel) == -1)
> + err(1, "Can't set authkey");
> + }
> + 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);
>  }
>  
> @@ -300,13 +475,13 @@ snmpc_get(int argc, char *argv[])
>   struct snmp_agent *agent;
>   int errorstatus, errorindex;
>   int i;
> + int class;
> + unsigned type;
>  
>   if (argc < 2)
>   usage();
>  
> - agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version,
> -    community);
> - if (agent == NULL)
> + if ((agent = snmpc_connect(argv[0], "161")) == NULL)
>   err(1, "%s", snmp_app->name);
>   agent->timeout = timeout;
>   agent->retries = retries;
> @@ -339,12 +514,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");
> @@ -365,6 +542,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");
> @@ -372,8 +551,7 @@ snmpc_walk(int argc, char *argv[])
>   usage();
>   oids = argc == 1 ? mib : argv[1];
>  
> - agent = snmp_connect_v12(snmpc_parseagent(argv[0], "161"), version, community);
> - if (agent == NULL)
> + if ((agent = snmpc_connect(argv[0], "161"))== NULL)
>   err(1, "%s", snmp_app->name);
>   agent->timeout = timeout;
>   agent->retries = retries;
> @@ -390,15 +568,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) {
> @@ -412,14 +594,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);
> @@ -440,6 +624,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;
>   }
> @@ -447,15 +633,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)
> @@ -495,9 +685,7 @@ snmpc_trap(int argc, char *argv[])
>   if (version == SNMP_V1)
>   errx(1, "trap is not supported for snmp v1");
>  
> - agent = snmp_connect_v12(snmpc_parseagent(argv[0], "162"),
> -    version, community);
> - if (agent == NULL)
> + if ((agent = snmpc_connect(argv[0], "162")) == NULL)
>   err(1, "%s", snmp_app->name);
>  
>   if (pledge("stdio", NULL) == -1)
> @@ -693,6 +881,20 @@ snmpc_mibtree(int argc, char *argv[])
>   return 0;
>  }
>  
> +struct snmp_agent *
> +snmpc_connect(char *host, char *port)
> +{
> + switch (version) {
> + case SNMP_V1:
> + 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;
> +}
> +
>  int
>  snmpc_print(struct ber_element *elm)
>  {
> @@ -875,18 +1077,61 @@ 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 ? "" : " ",
> +    " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
> +    "            [-E ctxengineid] [-K localpriv] [-k localauth] [-l seclevel]\n"
> +    "            [-n ctxname] [-O afnqvxSQ] [-r retries] [-t timeout] [-u user]\n"
> +    "            [-v version] [-X privpass] [-x cipher] [-Z boots,time]\n"
> +    "            " : "",
>      snmp_app->usage == NULL ? "" : snmp_app->usage);
>   exit(1);
>   }
> @@ -898,8 +1143,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/usm.c b/usm.c
> new file mode 100644
> index 0000000..f34a6c9
> --- /dev/null
> +++ b/usm.c
> @@ -0,0 +1,685 @@
> +/* $OpenBSD: usm.c,v 1.16 2019/06/11 05:36:32 martijn Exp $ */
> +
> +/*
> + * Copyright (c) 2019 Martijn van Duren <[hidden email]>
> + *
> + * 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;
> + enum usm_key_level authlevel;
> + const EVP_MD *digest;
> + char *authkey;
> + enum usm_key_level privlevel;
> + const EVP_CIPHER *cipher;
> + char *privkey;
> + int bootsset;
> + uint32_t boots;
> + int timeset;
> + uint32_t time;
> + struct timespec timecheck;
> +};
> +
> +struct usm_cookie {
> + size_t digestoffset;
> + long long salt;
> + uint32_t boots;
> + uint32_t time;
> +};
> +
> +static int usm_doinit(struct snmp_agent *);
> +static char *usm_genparams(struct snmp_agent *, size_t *, void **);
> +static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
> +static struct ber_element *usm_encpdu(struct snmp_agent *agent,
> +    struct ber_element *pdu, void *cookie);
> +static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *,
> +    char *, size_t, size_t *);
> +static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
> +    size_t, uint8_t, void **);
> +struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *);
> +static void usm_digest_pos(void *, size_t);
> +static void usm_free(void *);
> +static char *usm_passwd2mkey(const EVP_MD *, const char *);
> +static char *usm_mkey2lkey(struct usm_sec *, const EVP_MD *, const char *);
> +static size_t usm_digestlen(const EVP_MD *);
> +
> +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->encpdu = usm_encpdu;
> + sec->parseparams = usm_parseparams;
> + sec->decpdu = usm_decpdu;
> + sec->finalparams = usm_finalparams;
> + sec->free = usm_free;
> + sec->freecookie = 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;
> +
> + /* Ugly hack for HP Laserjet */
> + if (!usm->engineidset || !usm->bootsset || !usm->timeset) {
> + if ((ber = snmp_get(agent, NULL, 0)) == NULL)
> + return -1;
> + ber_free_element(ber);
> + }
> + return 0;
> +}
> +
> +static char *
> +usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
> +{
> + struct ber ber;
> + struct ber_element *params, *digestelm;
> + struct usm_sec *usm = agent->v3->sec->data;
> + char digest[USM_MAX_DIGESTLEN];
> + char *secparams = NULL, *buf;
> + ssize_t berlen;
> + struct usm_cookie *usmcookie;
> + struct timespec now, timediff;
> +
> + bzero(digest, sizeof(digest));
> +
> + if ((usmcookie = calloc(1, sizeof(*usmcookie))) == NULL)
> + return NULL;
> + *cookie = usmcookie;
> +
> + arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt));
> + if (usm->timeset) {
> + if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
> + free(usmcookie);
> + return NULL;
> + }
> + timespecsub(&now, &(usm->timecheck), &timediff);
> + usmcookie->time = usm->time + timediff.tv_sec;
> + } else
> + usmcookie->time = 0;
> + usmcookie->boots = usm->boots;
> +
> + if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
> +    usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
> +    usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
> +    usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
> +    agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
> +    (size_t) 0)) == NULL) {
> + free(usmcookie);
> + return NULL;
> + }
> +
> + if (ber_scanf_elements(params, "{SSSSe",  &digestelm) == -1) {
> + ber_free_element(params);
> + free(usmcookie);
> + return NULL;
> + }
> +
> + ber_set_writecallback(digestelm, usm_digest_pos, usmcookie);
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> + if ((berlen = ber_write_elements(&ber, params)) != -1 &&
> +    ber_get_writebuf(&ber, (void **)&buf) != -1 &&
> +    (secparams = malloc(berlen)) != NULL)
> + memcpy(secparams, buf, berlen);
> + *len = berlen;
> + ber_free_element(params);
> + ber_free(&ber);
> + return secparams;
> +}
> +
> +static struct ber_element *
> +usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct usm_cookie *usmcookie = cookie;
> + struct ber ber;
> + struct ber_element *retpdu;
> + char *serialpdu, *encpdu;
> + ssize_t pdulen;
> + size_t encpdulen;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> + pdulen = ber_write_elements(&ber, pdu);
> + if (pdulen == -1)
> + return NULL;
> +
> + ber_get_writebuf(&ber, (void **)&serialpdu);
> +
> + encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu,
> +    pdulen, &encpdulen);
> + ber_free(&ber);
> + if (encpdu == NULL)
> + return NULL;
> +
> + retpdu = ber_add_nstring(NULL, encpdu, encpdulen);
> + free(encpdu);
> + return retpdu;
> +}
> +
> +static char *
> +usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key,
> +    struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen)
> +{
> + EVP_CIPHER_CTX ctx;
> + size_t i;
> + char iv[EVP_MAX_IV_LENGTH];
> + char *salt = (char *)&(cookie->salt);
> + char *outtext;
> + int len, len2;
> + uint32_t ivv;
> +
> + switch (EVP_CIPHER_type(cipher)) {
> + case NID_des_cbc:
> + /* RFC3414, chap 8.1.1.1. */
> + for (i = 0; i < 8; i++)
> + iv[i] = salt[i] ^ key[USM_SALTOFFSET + i];
> + break;
> + case NID_aes_128_cfb128:
> + /* RFC3826, chap 3.1.2.1. */
> + ivv = htobe32(cookie->boots);
> + memcpy(iv, &ivv, sizeof(ivv));
> + ivv = htobe32(cookie->time);
> + memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
> + memcpy(iv + 2 * sizeof(ivv), &(cookie->salt),
> +    sizeof(cookie->salt));
> + break;
> + default:
> + return NULL;
> + }
> +
> + bzero(&ctx, sizeof(ctx));
> + if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc))
> + return NULL;
> +
> + EVP_CIPHER_CTX_set_padding(&ctx, do_enc);
> +
> + *outlen = EVP_CIPHER_block_size(cipher);
> + /* Maximum output size */
> + *outlen = pdulen + (*outlen - (pdulen % *outlen));
> +
> + if ((outtext = malloc(*outlen)) == NULL)
> + return NULL;
> +
> + if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) &&
> +    EVP_CipherFinal_ex(&ctx, outtext + len, &len2))
> + *outlen = len + len2;
> + else {
> + free(outtext);
> + outtext = NULL;
> + }
> +
> + EVP_CIPHER_CTX_cleanup(&ctx);
> +
> + return outtext;
> +}
> +
> +static int
> +usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
> +    size_t secparamsoffset, void *cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct usm_cookie *usmcookie = cookie;
> + u_char digest[EVP_MAX_MD_SIZE];
> +
> + if ((agent->v3->level & SNMP_MSGFLAG_AUTH) == 0)
> + return 0;
> +
> + if (usm->authlevel != USM_KEY_LOCALIZED)
> + return -1;
> +
> + if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), buf,
> +    buflen, digest, NULL) == NULL)
> + return -1;
> +
> + memcpy(buf + secparamsoffset + usmcookie->digestoffset, digest,
> +    usm_digestlen(usm->digest));
> + return 0;
> +}
> +
> +static int
> +usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
> +    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level,
> +    void **cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct ber ber;
> + struct ber_element *secparams;
> + char *engineid, *user, *digest, *salt;
> + size_t engineidlen, userlen, digestlen, saltlen;
> + struct timespec now, timediff;
> + off_t digestoffset;
> + char exp_digest[EVP_MAX_MD_SIZE];
> + struct usm_cookie *usmcookie;
> +
> + bzero(&ber, sizeof(ber));
> + bzero(exp_digest, sizeof(exp_digest));
> +
> + 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 ((usmcookie = malloc(sizeof(*usmcookie))) == NULL)
> + goto fail;
> + *cookie = usmcookie;
> +
> + if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen,
> +    &(usmcookie->boots), &(usmcookie->time), &user, &userlen,
> +    &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1)
> + goto fail;
> + if (saltlen != sizeof(usmcookie->salt) && saltlen != 0)
> + goto fail;
> + memcpy(&(usmcookie->salt), salt, saltlen);
> +
> + 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 = usmcookie->boots;
> + usm->bootsset = 1;
> + } else {
> + if (usmcookie->boots < usm->boots)
> + goto fail;
> + if (usmcookie->boots > usm->boots) {
> + usm->bootsset = 0;
> + usm->timeset = 0;
> + usm_doinit(agent);
> + goto fail;
> + }
> + }
> +
> + if (!usm->timeset) {
> + usm->time = usmcookie->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 (usmcookie->time <
> +    usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
> +    usmcookie->time >
> +    usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
> + usm->bootsset = 0;
> + usm->timeset = 0;
> + usm_doinit(agent);
> + goto fail;
> + }
> + }
> + /*
> + * Don't assume these are set if both are zero.
> + * Ugly hack for HP Laserjet
> + */
> + if (usm->boots == 0 && usm->time == 0) {
> + usm->bootsset = 0;
> + usm->timeset = 0;
> + }
> +
> + if (userlen != usm->userlen ||
> +    memcmp(user, usm->user, userlen) != 0)
> + goto fail;
> +
> + if (level & SNMP_MSGFLAG_AUTH) {
> + if (digestlen != usm_digestlen(usm->digest))
> + goto fail;
> + }
> + if ((agent->v3->level & SNMP_MSGFLAG_AUTH)) {
> + bzero(packet + secparamsoffset + digestoffset, digestlen);
> + if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), packet,
> +    packetlen, exp_digest, NULL) == NULL)
> + goto fail;
> +
> + if (memcmp(exp_digest, digest, digestlen) != 0)
> + goto fail;
> + } else
> + if (digestlen != 0)
> + goto fail;
> +
> + ber_free_element(secparams);
> + return 0;
> +
> +fail:
> + free(usmcookie);
> + ber_free_element(secparams);
> + return -1;
> +}
> +
> +struct ber_element *
> +usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void *cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct usm_cookie *usmcookie = cookie;
> + struct ber ber;
> + struct ber_element *scopedpdu;
> + char *rawpdu;
> + size_t rawpdulen;
> +
> + if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie,
> +    encpdu, encpdulen, &rawpdulen)) == NULL)
> + return NULL;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> + ber_set_readbuf(&ber, rawpdu, rawpdulen);
> + scopedpdu = ber_read_elements(&ber, NULL);
> + ber_free(&ber);
> + free(rawpdu);
> +
> + return scopedpdu;
> +}
> +
> +static void
> +usm_digest_pos(void *data, size_t offset)
> +{
> + struct usm_cookie *usmcookie = data;
> +
> + usmcookie->digestoffset = offset;
> +}
> +
> +static void
> +usm_free(void *data)
> +{
> + struct usm_sec *usm = data;
> +
> + free(usm->user);
> + free(usm->authkey);
> + free(usm->privkey);
> + free(usm->engineid);
> + free(usm);
> +}
> +
> +int
> +usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
> +    size_t keylen, enum usm_key_level level)
> +{
> + struct usm_sec *usm = sec->data;
> + char *lkey;
> +
> + /*
> + * We could transform a master key to a local key here if we already
> + * have usm_setengineid called. Sine snmpc.c is the only caller at
> + * the moment there's no need, since it always calls this function
> + * first.
> + */
> + if (level == USM_KEY_PASSWORD) {
> + if ((usm->authkey = usm_passwd2mkey(digest, key)) == NULL)
> + return -1;
> + level = USM_KEY_MASTER;
> + keylen = EVP_MD_size(digest);
> + } else {
> + if (keylen != (size_t)EVP_MD_size(digest)) {
> + errno = EINVAL;
> + return -1;
> + }
> + if ((lkey = malloc(keylen)) == NULL)
> + return -1;
> + memcpy(lkey, key, keylen);
> + usm->authkey = lkey;
> + }
> + usm->digest = digest;
> + usm->authlevel = level;
> + return 0;
> +}
> +
> +int
> +usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key,
> +    size_t keylen, enum usm_key_level level)
> +{
> + struct usm_sec *usm = sec->data;
> + char *lkey;
> +
> + if (usm->digest == NULL) {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + /*
> + * We could transform a master key to a local key here if we already
> + * have usm_setengineid called. Sine snmpc.c is the only caller at
> + * the moment there's no need, since it always calls us first.
> + */
> + if (level == USM_KEY_PASSWORD) {
> + if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL)
> + return -1;
> + level = USM_KEY_MASTER;
> + keylen = EVP_MD_size(usm->digest);
> + } else {
> + if (keylen != (size_t)EVP_MD_size(usm->digest)) {
> + errno = EINVAL;
> + return -1;
> + }
> + if ((lkey = malloc(keylen)) == NULL)
> + return -1;
> + memcpy(lkey, key, keylen);
> + usm->privkey = lkey;
> + }
> + usm->cipher = cipher;
> + usm->privlevel = level;
> + return 0;
> +}
> +
> +int
> +usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
> +{
> + struct usm_sec *usm = sec->data;
> + char *mkey;
> +
> + 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;
> +
> + if (usm->authlevel == USM_KEY_MASTER) {
> + mkey = usm->authkey;
> + if ((usm->authkey = usm_mkey2lkey(usm, usm->digest,
> +    mkey)) == NULL) {
> + usm->authkey = mkey;
> + return -1;
> + }
> + free(mkey);
> + usm->authlevel = USM_KEY_LOCALIZED;
> + }
> + if (usm->privlevel == USM_KEY_MASTER) {
> + mkey = usm->privkey;
> + if ((usm->privkey = usm_mkey2lkey(usm, usm->digest,
> +    mkey)) == NULL) {
> + usm->privkey = mkey;
> + return -1;
> + }
> + free(mkey);
> + usm->privlevel = USM_KEY_LOCALIZED;
> + }
> +
> + 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;
> +}
> +
> +static char *
> +usm_passwd2mkey(const EVP_MD *md, const char *passwd)
> +{
> + EVP_MD_CTX ctx;
> + int i, count;
> + const u_char *pw;
> + u_char *c;
> + u_char keybuf[EVP_MAX_MD_SIZE > 64 ? EVP_MAX_MD_SIZE : 64];
> + unsigned dlen;
> + char *key;
> +
> + bzero(&ctx, sizeof(ctx));
> + EVP_DigestInit_ex(&ctx, md, NULL);
> + pw = (const u_char *)passwd;
> + for (count = 0; count < 1048576; count += 64) {
> + c = keybuf;
> + for (i = 0; i < 64; i++) {
> + if (*pw == '\0')
> + pw = (const u_char *)passwd;
> + *c++ = *pw++;
> + }
> + EVP_DigestUpdate(&ctx, keybuf, 64);
> + }
> + EVP_DigestFinal_ex(&ctx, keybuf, &dlen);
> + EVP_MD_CTX_cleanup(&ctx);
> +
> + if ((key = malloc(dlen)) == NULL)
> + return NULL;
> + memcpy(key, keybuf, dlen);
> + return key;
> +}
> +
> +static char *
> +usm_mkey2lkey(struct usm_sec *usm, const EVP_MD *md, const char *mkey)
> +{
> + EVP_MD_CTX ctx;
> + u_char buf[EVP_MAX_MD_SIZE];
> + u_char *lkey;
> + unsigned lklen;
> +
> +
> + bzero(&ctx, sizeof(ctx));
> + EVP_DigestInit_ex(&ctx, md, NULL);
> +
> + EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
> + EVP_DigestUpdate(&ctx, usm->engineid, usm->engineidlen);
> + EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
> +
> + EVP_DigestFinal_ex(&ctx, buf, &lklen);
> + EVP_MD_CTX_cleanup(&ctx);
> +
> + if ((lkey = malloc(lklen)) == NULL)
> + return NULL;
> + memcpy(lkey, buf, lklen);
> + return lkey;
> +}
> +
> +static size_t
> +usm_digestlen(const EVP_MD *md)
> +{
> + switch (EVP_MD_type(md)) {
> + case NID_md5:
> + case NID_sha1:
> +                return 12;
> +        case NID_sha224:
> +                return 16;
> +        case NID_sha256:
> +                return 24;
> +        case NID_sha384:
> +                return 32;
> +        case NID_sha512:
> +                return 48;
> +        default:
> +                return 0;
> +
> + }
> +}
> diff --git a/usm.h b/usm.h
> new file mode 100644
> index 0000000..b1aea8b
> --- /dev/null
> +++ b/usm.h
> @@ -0,0 +1,34 @@
> +/* $OpenBSD: snmp.h,v 1.1 2019/08/09 06:17:59 martijn Exp $ */
> +
> +/*
> + * Copyright (c) 2019 Martijn van Duren <[hidden email]>
> + *
> + * 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"
> +
> +enum usm_key_level {
> + USM_KEY_UNSET = 0,
> + USM_KEY_PASSWORD,
> + USM_KEY_MASTER,
> + USM_KEY_LOCALIZED
> +};
> +
> +struct snmp_sec *usm_init(const char *, size_t);
> +int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
> +    enum usm_key_level);
> +int usm_setpriv(struct snmp_sec *, const EVP_CIPHER *, const char *, size_t,
> +    enum usm_key_level);
> +int usm_setengineid(struct snmp_sec *, char *, size_t);
> +int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);
>

Reply | Threaded
Open this post in threaded view
|

Re: snmp(1): Add SNMPv3/USM support [4/5]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
This diff needs the following change on top to preserve authNoPriv:

--- usm.c.old Thu Sep 12 21:41:14 2019
+++ usm.c Thu Sep 12 21:41:39 2019
@@ -185,7 +185,7 @@
     usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
     usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
     usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
-    agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
+    agent->v3->level & SNMP_MSGFLAG_PRIV ? sizeof(usmcookie->salt) :
     (size_t) 0)) == NULL) {
  free(usmcookie);
  return NULL;


On 9/2/19 9:13 AM, Martijn van Duren wrote:

> This diff adds support for authPriv.
>
> diff --git a/snmp.1 b/snmp.1
> index e810560..fe283a5 100644
> --- a/snmp.1
> +++ b/snmp.1
> @@ -28,6 +28,7 @@
>  .Op Fl c Ar community
>  .Op Fl E Ar ctxengineid
>  .Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
>  .Op Fl k Ar localauth
>  .Op Fl l Ar seclevel
>  .Op Fl n Ar ctxname
> @@ -36,6 +37,8 @@
>  .Op Fl t Ar timeout
>  .Op Fl u Ar user
>  .Op Fl v Ar version
> +.Op Fl X Ar privpass
> +.Op Fl x Ar cipher
>  .Op Fl Z Ar boots , Ns Ar time
>  .Ar agent
>  .Ar oid ...
> @@ -46,6 +49,7 @@
>  .Op Fl c Ar community
>  .Op Fl E Ar ctxengineid
>  .Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
>  .Op Fl k Ar localauth
>  .Op Fl l Ar seclevel
>  .Op Fl n Ar ctxname
> @@ -54,6 +58,8 @@
>  .Op Fl t Ar timeout
>  .Op Fl u Ar user
>  .Op Fl v Ar version
> +.Op Fl X Ar privpass
> +.Op Fl x Ar cipher
>  .Op Fl Z Ar boots , Ns Ar time
>  .Op Fl C Cm cIipt
>  .Op Fl C Cm E Ar endoid
> @@ -66,6 +72,7 @@
>  .Op Fl c Ar community
>  .Op Fl E Ar ctxengineid
>  .Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
>  .Op Fl k Ar localauth
>  .Op Fl l Ar seclevel
>  .Op Fl n Ar ctxname
> @@ -74,6 +81,8 @@
>  .Op Fl t Ar timeout
>  .Op Fl u Ar user
>  .Op Fl v Ar version
> +.Op Fl X Ar privpass
> +.Op Fl x Ar cipher
>  .Op Fl Z Ar boots , Ns Ar time
>  .Op Fl C Cm n Ns Ar nonrep Ns Cm r Ns Ar maxrep
>  .Ar agent
> @@ -85,6 +94,7 @@
>  .Op Fl c Ar community
>  .Op Fl E Ar ctxengineid
>  .Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
>  .Op Fl k Ar localauth
>  .Op Fl l Ar seclevel
>  .Op Fl n Ar ctxname
> @@ -93,6 +103,8 @@
>  .Op Fl t Ar timeout
>  .Op Fl u Ar user
>  .Op Fl v Ar version
> +.Op Fl X Ar privpass
> +.Op Fl x Ar cipher
>  .Op Fl Z Ar boots , Ns Ar time
>  .Op Fl C Cm cipn Ns Ar nonrep Ns Cm r Ns Ar maxrep
>  .Ar agent
> @@ -104,6 +116,7 @@
>  .Op Fl c Ar community
>  .Op Fl E Ar ctxengineid
>  .Op Fl e Ar secengineid
> +.Op Fl K Ar localpriv
>  .Op Fl k Ar localauth
>  .Op Fl l Ar seclevel
>  .Op Fl n Ar ctxname
> @@ -111,6 +124,8 @@
>  .Op Fl t Ar timeout
>  .Op Fl u Ar user
>  .Op Fl v Ar version
> +.Op Fl X Ar privpass
> +.Op Fl x Ar cipher
>  .Op Fl Z Ar boots , Ns Ar time
>  .Ar agent uptime trapoid
>  .Oo Ar varoid type value Oc ...
> @@ -302,6 +317,14 @@ 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 K Ar localpriv
> +The localized privacy password for the user in hexadecimal format
> +.Po
> +optionally prefixed with a
> +.Cm 0x
> +.Pc .
> +This option is only used by
> +.Fl v Cm 3 .
>  .It Fl k Ar localauth
>  The localized authentication password for the user in hexadecimal format
>  .Po
> @@ -313,14 +336,24 @@ This option is only used by
>  .It Fl l Ar seclevel
>  The security level.
>  Values can be
> -.Cm noAuthNoPriv Pq default
> -or
> +.Cm noAuthNoPriv Pq default ,
>  .Cm authNoPriv
>  .Po
>  requires either
>  .Fl A
>  or
>  .Fl k
> +.Pc
> +or
> +.Cm authPriv
> +.Po
> +requires either
> +.Fl X
> +or
> +.Fl K
> +in addition to the
> +.Cm authNoPriv
> +requirements
>  .Pc .
>  This option is only used by
>  .Fl v Cm 3 .
> @@ -382,6 +415,22 @@ or
>  .Cm 3 .
>  Currently defaults to
>  .Cm 2c .
> +.It Fl X Ar privpass
> +The privacy password for the user.
> +This will be tansformed to
> +.Ar localpriv .
> +This option is only used by
> +.Fl v Cm 3 .
> +.It Fl x Ar cipher
> +Sets the cipher
> +.Pq privacy
> +protocol.
> +Options are
> +.Cm DES
> +and
> +.Cm AES .
> +This option is only used by
> +.Fl v Cm 3 .
>  .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
> diff --git a/snmp.c b/snmp.c
> index 57b40e3..ba4dc3c 100644
> --- a/snmp.c
> +++ b/snmp.c
> @@ -357,7 +357,7 @@ static char *
>  snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
>  {
>   struct ber ber;
> - struct ber_element *message, *scopedpdu = NULL, *secparams;
> + struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
>   ssize_t securitysize, ret;
>   size_t secparamsoffset;
>   char *securityparams = NULL, *buf, *packet = NULL;
> @@ -400,6 +400,13 @@ snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
>   ber_free_elements(scopedpdu);
>   goto fail;
>   }
> + if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
> + if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
> +    cookie)) == NULL)
> + goto fail;
> + ber_free_elements(scopedpdu);
> + scopedpdu = encpdu;
> + }
>   if (ber_printf_elements(message, "d{idxd}xe",
>      agent->version, msgid, 1472, &(agent->v3->level),
>      (size_t) 1, agent->v3->sec->model, securityparams,
> @@ -449,8 +456,9 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
>   size_t msgflagslen, secparamslen;
>   struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname;
>   off_t secparamsoffset;
> - char *engineid;
> - size_t engineidlen;
> + char *encpdu, *engineid;
> + size_t encpdulen, engineidlen;
> + void *cookie = NULL;
>  
>   bzero(&ber, sizeof(ber));
>   ber_set_application(&ber, smi_application);
> @@ -482,9 +490,19 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
>   if (msgflagslen != 1)
>   goto fail;
>   if (agent->v3->sec->parseparams(agent, buf, buflen,
> -    secparamsoffset, secparams, secparamslen,
> -    msgflags[0]) == -1)
> +    secparamsoffset, secparams, secparamslen, msgflags[0],
> +    &cookie) == -1) {
> + cookie = NULL;
>   goto fail;
> + }
> + if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
> + if (ber_scanf_elements(scopedpdu, "x", &encpdu,
> +    &encpdulen) == -1)
> + goto fail;
> + if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
> +    encpdulen, cookie)) == NULL)
> + goto fail;
> + }
>   if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid,
>      &engineidlen, &ctxname) == -1)
>   goto fail;
> @@ -502,11 +520,14 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
>   }
>  
>   ber_free_elements(message);
> + agent->v3->sec->freecookie(cookie);
>   return pdu;
>   }
>   /* NOTREACHED */
>  
>  fail:
> + if (version == SNMP_V3)
> + agent->v3->sec->freecookie(cookie);
>   ber_free_elements(message);
>   return NULL;
>  }
> diff --git a/snmp.h b/snmp.h
> index df20f0d..ce62119 100644
> --- a/snmp.h
> +++ b/snmp.h
> @@ -114,9 +114,13 @@ struct snmp_sec {
>   enum snmp_security_model model;
>   int (*init)(struct snmp_agent *);
>   char *(*genparams)(struct snmp_agent *, size_t *, void **);
> + struct ber_element *(*encpdu)(struct snmp_agent *,
> +    struct ber_element *, void *);
>   int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
>   int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
> -    size_t, uint8_t);
> +    size_t, uint8_t, void **);
> + struct ber_element *(*decpdu)(struct snmp_agent *, char *, size_t,
> +    void *);
>   void (*free)(void *);
>   void (*freecookie)(void *);
>   void *data;
> diff --git a/snmpc.c b/snmpc.c
> index edaa50c..658ca82 100644
> --- a/snmpc.c
> +++ b/snmpc.c
> @@ -42,7 +42,7 @@
>  #include "snmp.h"
>  #include "usm.h"
>  
> -#define GETOPT_COMMON "A:a:c:E:e:k:l:n:O:r:t:u:v:Z:"
> +#define GETOPT_COMMON "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
>  
>  int snmpc_get(int, char *[]);
>  int snmpc_walk(int, char *[]);
> @@ -98,11 +98,15 @@ int
>  main(int argc, char *argv[])
>  {
>   const EVP_MD *md = NULL;
> + const EVP_CIPHER *cipher = NULL;
>   struct snmp_sec *sec;
>   char *user = NULL;
>   enum usm_key_level authkeylevel;
>   char *authkey = NULL;
>   size_t authkeylen = 0;
> + enum usm_key_level privkeylevel;
> + char *privkey = NULL;
> + size_t privkeylen = 0;
>   int seclevel = SNMP_MSGFLAG_REPORT;
>   char *ctxname = NULL;
>   char *ctxengineid = NULL, *secengineid = NULL;
> @@ -193,6 +197,16 @@ main(int argc, char *argv[])
>   err(1, "-3e");
>   }
>   break;
> + case 'K':
> + privkey = snmpc_hex2bin(optarg, &privkeylen);
> + if (privkey == NULL) {
> + if (errno == EINVAL)
> + errx(1, "Bad key value after "
> +    "-3K flag.");
> + errx(1, "-3K");
> + }
> + privkeylevel = USM_KEY_LOCALIZED;
> + break;
>   case 'k':
>   authkey = snmpc_hex2bin(optarg, &authkeylen);
>   if (authkey == NULL) {
> @@ -208,6 +222,9 @@ main(int argc, char *argv[])
>   else if (strcmp(optarg, "authNoPriv") == 0)
>   seclevel = SNMP_MSGFLAG_AUTH |
>      SNMP_MSGFLAG_REPORT;
> + else if (strcmp(optarg, "authPriv") == 0)
> + seclevel = SNMP_MSGFLAG_AUTH |
> +    SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
>   else
>   errx(1, "Invalid security level specified "
>      "after -l flag: %s", optarg);
> @@ -369,6 +386,21 @@ main(int argc, char *argv[])
>   }
>   }
>   break;
> + case 'X':
> + privkey = optarg;
> + privkeylen = strlen(privkey);
> + privkeylevel = USM_KEY_PASSWORD;
> + break;
> + case 'x':
> + if (strcasecmp(optarg, "DES") == 0)
> + cipher = EVP_des_cbc();
> + else if (strcasecmp(optarg, "AES") == 0)
> + cipher = EVP_aes_128_cfb128();
> + else
> + errx(1, "Invalid privacy protocol "
> +    "specified after -3x flag: %s",
> +    optarg);
> + break;
>   case 'Z':
>   boots = strtoll(optarg, &strtolp, 10);
>   if (boots < 0 || strtolp == optarg || strtolp[0] != ',')
> @@ -403,6 +435,15 @@ main(int argc, char *argv[])
>      authkeylevel) == -1)
>   err(1, "Can't set authkey");
>   }
> + if (seclevel & SNMP_MSGFLAG_PRIV) {
> + if (cipher == NULL)
> + cipher = EVP_des_cbc();
> + if (privkey == NULL)
> + errx(1, "No privKey or privPassword specified");
> + if (usm_setpriv(sec, cipher, privkey, privkeylen,
> +    privkeylevel) == -1)
> + err(1, "Can't set authkey");
> + }
>   if (secengineid != NULL) {
>   if (usm_setengineid(sec, secengineid,
>      secengineidlen) == -1)
> @@ -1087,9 +1128,9 @@ usage(void)
>      snmp_app->name,
>      snmp_app->usecommonopt ?
>      " [-A authpass] [-a digest] [-c community] [-e secengineid]\n"
> -    "            [-E ctxengineid] [-k localauth] [-l seclevel] [-n ctxname]\n"
> -    "            [-O afnqvxSQ] [-r retries] [-t timeout] [-u user] [-v version]\n"
> -    "            [-Z boots,time]\n"
> +    "            [-E ctxengineid] [-K localpriv] [-k localauth] [-l seclevel]\n"
> +    "            [-n ctxname] [-O afnqvxSQ] [-r retries] [-t timeout] [-u user]\n"
> +    "            [-v version] [-X privpass] [-x cipher] [-Z boots,time]\n"
>      "            " : "",
>      snmp_app->usage == NULL ? "" : snmp_app->usage);
>   exit(1);
> diff --git a/usm.c b/usm.c
> index df9a53d..ba2020c 100644
> --- a/usm.c
> +++ b/usm.c
> @@ -44,6 +44,9 @@ struct usm_sec {
>   enum usm_key_level authlevel;
>   const EVP_MD *digest;
>   char *authkey;
> + enum usm_key_level privlevel;
> + const EVP_CIPHER *cipher;
> + char *privkey;
>   int bootsset;
>   uint32_t boots;
>   int timeset;
> @@ -53,13 +56,21 @@ struct usm_sec {
>  
>  struct usm_cookie {
>   size_t digestoffset;
> + long long salt;
> + uint32_t boots;
> + uint32_t time;
>  };
>  
>  static int usm_doinit(struct snmp_agent *);
>  static char *usm_genparams(struct snmp_agent *, size_t *, void **);
>  static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
> +static struct ber_element *usm_encpdu(struct snmp_agent *agent,
> +    struct ber_element *pdu, void *cookie);
> +static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *,
> +    char *, size_t, size_t *);
>  static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
> -    size_t, uint8_t);
> +    size_t, uint8_t, void **);
> +struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *);
>  static void usm_digest_pos(void *, size_t);
>  static void usm_free(void *);
>  static char *usm_passwd2mkey(const EVP_MD *, const char *);
> @@ -95,7 +106,9 @@ usm_init(const char *user, size_t userlen)
>   sec->model = SNMP_SEC_USM;
>   sec->init = usm_doinit;
>   sec->genparams = usm_genparams;
> + sec->encpdu = usm_encpdu;
>   sec->parseparams = usm_parseparams;
> + sec->decpdu = usm_decpdu;
>   sec->finalparams = usm_finalparams;
>   sec->free = usm_free;
>   sec->freecookie = free;
> @@ -143,7 +156,6 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
>   ssize_t berlen;
>   struct usm_cookie *usmcookie;
>   struct timespec now, timediff;
> - uint32_t boots, time;
>  
>   bzero(digest, sizeof(digest));
>  
> @@ -151,21 +163,24 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
>   return NULL;
>   *cookie = usmcookie;
>  
> + arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt));
>   if (usm->timeset) {
>   if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
>   free(usmcookie);
>   return NULL;
>   }
>   timespecsub(&now, &(usm->timecheck), &timediff);
> - time = usm->time + timediff.tv_sec;
> + usmcookie->time = usm->time + timediff.tv_sec;
>   } else
> - time = 0;
> - boots = usm->boots;
> + usmcookie->time = 0;
> + usmcookie->boots = usm->boots;
>  
>   if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
> -    usm->engineidlen, boots, time, usm->user, usm->userlen, digest,
> -    agent->v3->level & SNMP_MSGFLAG_AUTH ? usm_digestlen(usm->digest) :
> -    (size_t) 0, NULL, (size_t) 0)) == NULL) {
> +    usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
> +    usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
> +    usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
> +    agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
> +    (size_t) 0)) == NULL) {
>   free(usmcookie);
>   return NULL;
>   }
> @@ -190,6 +205,93 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
>   return secparams;
>  }
>  
> +static struct ber_element *
> +usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct usm_cookie *usmcookie = cookie;
> + struct ber ber;
> + struct ber_element *retpdu;
> + char *serialpdu, *encpdu;
> + ssize_t pdulen;
> + size_t encpdulen;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> + pdulen = ber_write_elements(&ber, pdu);
> + if (pdulen == -1)
> + return NULL;
> +
> + ber_get_writebuf(&ber, (void **)&serialpdu);
> +
> + encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu,
> +    pdulen, &encpdulen);
> + ber_free(&ber);
> + if (encpdu == NULL)
> + return NULL;
> +
> + retpdu = ber_add_nstring(NULL, encpdu, encpdulen);
> + free(encpdu);
> + return retpdu;
> +}
> +
> +static char *
> +usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key,
> +    struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen)
> +{
> + EVP_CIPHER_CTX ctx;
> + size_t i;
> + char iv[EVP_MAX_IV_LENGTH];
> + char *salt = (char *)&(cookie->salt);
> + char *outtext;
> + int len, len2;
> + uint32_t ivv;
> +
> + switch (EVP_CIPHER_type(cipher)) {
> + case NID_des_cbc:
> + /* RFC3414, chap 8.1.1.1. */
> + for (i = 0; i < 8; i++)
> + iv[i] = salt[i] ^ key[USM_SALTOFFSET + i];
> + break;
> + case NID_aes_128_cfb128:
> + /* RFC3826, chap 3.1.2.1. */
> + ivv = htobe32(cookie->boots);
> + memcpy(iv, &ivv, sizeof(ivv));
> + ivv = htobe32(cookie->time);
> + memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
> + memcpy(iv + 2 * sizeof(ivv), &(cookie->salt),
> +    sizeof(cookie->salt));
> + break;
> + default:
> + return NULL;
> + }
> +
> + bzero(&ctx, sizeof(ctx));
> + if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc))
> + return NULL;
> +
> + EVP_CIPHER_CTX_set_padding(&ctx, do_enc);
> +
> + *outlen = EVP_CIPHER_block_size(cipher);
> + /* Maximum output size */
> + *outlen = pdulen + (*outlen - (pdulen % *outlen));
> +
> + if ((outtext = malloc(*outlen)) == NULL)
> + return NULL;
> +
> + if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) &&
> +    EVP_CipherFinal_ex(&ctx, outtext + len, &len2))
> + *outlen = len + len2;
> + else {
> + free(outtext);
> + outtext = NULL;
> + }
> +
> + EVP_CIPHER_CTX_cleanup(&ctx);
> +
> + return outtext;
> +}
> +
>  static int
>  usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
>      size_t secparamsoffset, void *cookie)
> @@ -215,17 +317,18 @@ usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
>  
>  static int
>  usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
> -    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level)
> +    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level,
> +    void **cookie)
>  {
>   struct usm_sec *usm = agent->v3->sec->data;
>   struct ber ber;
>   struct ber_element *secparams;
> - char *engineid, *user, *digest;
> - size_t engineidlen, userlen, digestlen;
> + char *engineid, *user, *digest, *salt;
> + size_t engineidlen, userlen, digestlen, saltlen;
>   struct timespec now, timediff;
>   off_t digestoffset;
>   char exp_digest[EVP_MAX_MD_SIZE];
> - uint32_t boots, time;
> + struct usm_cookie *usmcookie;
>  
>   bzero(&ber, sizeof(ber));
>   bzero(exp_digest, sizeof(exp_digest));
> @@ -236,10 +339,17 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
>   return -1;
>   ber_free(&ber);
>  
> - if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen,
> -    &boots, &time, &user, &userlen, &digestoffset, &digest,
> -    &digestlen) == -1)
> + if ((usmcookie = malloc(sizeof(*usmcookie))) == NULL)
> + goto fail;
> + *cookie = usmcookie;
> +
> + if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen,
> +    &(usmcookie->boots), &(usmcookie->time), &user, &userlen,
> +    &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1)
>   goto fail;
> + if (saltlen != sizeof(usmcookie->salt) && saltlen != 0)
> + goto fail;
> + memcpy(&(usmcookie->salt), salt, saltlen);
>  
>   if (!usm->engineidset) {
>   if (usm_setengineid(agent->v3->sec, engineid,
> @@ -253,12 +363,12 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
>   }
>  
>   if (!usm->bootsset) {
> - usm->boots = boots;
> + usm->boots = usmcookie->boots;
>   usm->bootsset = 1;
>   } else {
> - if (boots < usm->boots)
> + if (usmcookie->boots < usm->boots)
>   goto fail;
> - if (boots > usm->boots) {
> + if (usmcookie->boots > usm->boots) {
>   usm->bootsset = 0;
>   usm->timeset = 0;
>   usm_doinit(agent);
> @@ -267,7 +377,7 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
>   }
>  
>   if (!usm->timeset) {
> - usm->time = time;
> + usm->time = usmcookie->time;
>   if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1)
>   goto fail;
>   usm->timeset = 1;
> @@ -275,8 +385,10 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
>   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) {
> + if (usmcookie->time <
> +    usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
> +    usmcookie->time >
> +    usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
>   usm->bootsset = 0;
>   usm->timeset = 0;
>   usm_doinit(agent);
> @@ -308,10 +420,35 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
>   return 0;
>  
>  fail:
> + free(usmcookie);
>   ber_free_element(secparams);
>   return -1;
>  }
>  
> +struct ber_element *
> +usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void *cookie)
> +{
> + struct usm_sec *usm = agent->v3->sec->data;
> + struct usm_cookie *usmcookie = cookie;
> + struct ber ber;
> + struct ber_element *scopedpdu;
> + char *rawpdu;
> + size_t rawpdulen;
> +
> + if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie,
> +    encpdu, encpdulen, &rawpdulen)) == NULL)
> + return NULL;
> +
> + bzero(&ber, sizeof(ber));
> + ber_set_application(&ber, smi_application);
> + ber_set_readbuf(&ber, rawpdu, rawpdulen);
> + scopedpdu = ber_read_elements(&ber, NULL);
> + ber_free(&ber);
> + free(rawpdu);
> +
> + return scopedpdu;
> +}
> +
>  static void
>  usm_digest_pos(void *data, size_t offset)
>  {
> @@ -327,6 +464,7 @@ usm_free(void *data)
>  
>   free(usm->user);
>   free(usm->authkey);
> + free(usm->privkey);
>   free(usm->engineid);
>   free(usm);
>  }
> @@ -364,6 +502,43 @@ usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
>   return 0;
>  }
>  
> +int
> +usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key,
> +    size_t keylen, enum usm_key_level level)
> +{
> + struct usm_sec *usm = sec->data;
> + char *lkey;
> +
> + if (usm->digest == NULL) {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + /*
> + * We could transform a master key to a local key here if we already
> + * have usm_setengineid called. Sine snmpc.c is the only caller at
> + * the moment there's no need, since it always calls us first.
> + */
> + if (level == USM_KEY_PASSWORD) {
> + if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL)
> + return -1;
> + level = USM_KEY_MASTER;
> + keylen = EVP_MD_size(usm->digest);
> + } else {
> + if (keylen != (size_t)EVP_MD_size(usm->digest)) {
> + errno = EINVAL;
> + return -1;
> + }
> + if ((lkey = malloc(keylen)) == NULL)
> + return -1;
> + memcpy(lkey, key, keylen);
> + usm->privkey = lkey;
> + }
> + usm->cipher = cipher;
> + usm->privlevel = level;
> + return 0;
> +}
> +
>  int
>  usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
>  {
> @@ -388,6 +563,16 @@ usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
>   free(mkey);
>   usm->authlevel = USM_KEY_LOCALIZED;
>   }
> + if (usm->privlevel == USM_KEY_MASTER) {
> + mkey = usm->privkey;
> + if ((usm->privkey = usm_mkey2lkey(usm, usm->digest,
> +    mkey)) == NULL) {
> + usm->privkey = mkey;
> + return -1;
> + }
> + free(mkey);
> + usm->privlevel = USM_KEY_LOCALIZED;
> + }
>  
>   return 0;
>  }
> diff --git a/usm.h b/usm.h
> index 636cd0e..b1aea8b 100644
> --- a/usm.h
> +++ b/usm.h
> @@ -28,5 +28,7 @@ enum usm_key_level {
>  struct snmp_sec *usm_init(const char *, size_t);
>  int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
>      enum usm_key_level);
> +int usm_setpriv(struct snmp_sec *, const EVP_CIPHER *, const char *, size_t,
> +    enum usm_key_level);
>  int usm_setengineid(struct snmp_sec *, char *, size_t);
>  int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);
>