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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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);