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 <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/time.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+#include <ber.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+
+#include "smi.h"
+#include "snmp.h"
+#include "usm.h"
+
+#define USM_MAX_DIGESTLEN 48
+#define USM_MAX_TIMEWINDOW 150
+#define USM_SALTOFFSET 8
+
+struct usm_sec {
+       struct snmp_sec snmp;
+       char *user;
+       size_t userlen;
+       int engineidset;
+       char *engineid;
+       size_t engineidlen;
+       int bootsset;
+       uint32_t boots;
+       int timeset;
+       uint32_t time;
+       struct timespec timecheck;
+};
+
+static int usm_doinit(struct snmp_agent *);
+static char *usm_genparams(struct snmp_agent *, size_t *);
+static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
+    size_t, uint8_t);
+static void usm_free(void *);
+
+struct snmp_sec *
+usm_init(const char *user, size_t userlen)
+{
+       struct snmp_sec *sec;
+       struct usm_sec *usm;
+
+       if (user == NULL || user[0] == '\0') {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if ((sec = malloc(sizeof(*sec))) == NULL)
+               return NULL;
+
+       if ((usm = calloc(1, sizeof(struct usm_sec))) == NULL) {
+               free(sec);
+               return NULL;
+       }
+       if ((usm->user = malloc(userlen)) == NULL) {
+               free(sec);
+               free(usm);
+               return NULL;
+       }
+       memcpy(usm->user, user, userlen);
+       usm->userlen = userlen;
+
+       sec->model = SNMP_SEC_USM;
+       sec->init = usm_doinit;
+       sec->genparams = usm_genparams;
+       sec->parseparams = usm_parseparams;
+       sec->free = usm_free;
+       sec->data = usm;
+       return sec;
+}
+
+static int
+usm_doinit(struct snmp_agent *agent)
+{
+       struct ber_element *ber;
+       struct usm_sec *usm = agent->v3->sec->data;
+       int level;
+       size_t userlen;
+
+       if (usm->engineidset && usm->bootsset && usm->timeset)
+               return 0;
+
+       level = agent->v3->level;
+       agent->v3->level = SNMP_MSGFLAG_REPORT;
+       userlen = usm->userlen;
+       usm->userlen = 0;
+
+       if ((ber = snmp_get(agent, NULL, 0)) == NULL) {
+               agent->v3->level = level;
+               usm->userlen = userlen;
+               return -1;
+       }
+       ber_free_element(ber);
+
+       agent->v3->level = level;
+       usm->userlen = userlen;
+
+       return 0;
+}
+
+static char *
+usm_genparams(struct snmp_agent *agent, size_t *len)
+{
+       struct ber ber;
+       struct ber_element *params;
+       struct usm_sec *usm = agent->v3->sec->data;
+       char *secparams = NULL, *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 <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "snmp.h"
+
+struct snmp_sec *usm_init(const char *, size_t);
+int usm_setengineid(struct snmp_sec *, char *, size_t);
+int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);

Reply via email to