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 <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;
> + 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 <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"
> +
> +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);
>