summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/snmpd/Makefile6
-rw-r--r--usr.sbin/snmpd/ber.315
-rw-r--r--usr.sbin/snmpd/ber.c67
-rw-r--r--usr.sbin/snmpd/ber.h16
-rw-r--r--usr.sbin/snmpd/mib.c80
-rw-r--r--usr.sbin/snmpd/mib.h48
-rw-r--r--usr.sbin/snmpd/mps.c16
-rw-r--r--usr.sbin/snmpd/parse.y91
-rw-r--r--usr.sbin/snmpd/snmp.h17
-rw-r--r--usr.sbin/snmpd/snmpd.c61
-rw-r--r--usr.sbin/snmpd/snmpd.conf.577
-rw-r--r--usr.sbin/snmpd/snmpd.h116
-rw-r--r--usr.sbin/snmpd/snmpe.c187
-rw-r--r--usr.sbin/snmpd/usm.c658
14 files changed, 1356 insertions, 99 deletions
diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile
index a344792bd8d..9746eda9a87 100644
--- a/usr.sbin/snmpd/Makefile
+++ b/usr.sbin/snmpd/Makefile
@@ -1,12 +1,12 @@
-# $OpenBSD: Makefile,v 1.8 2012/03/20 03:01:26 joel Exp $
+# $OpenBSD: Makefile,v 1.9 2012/09/17 16:30:34 reyk Exp $
PROG= snmpd
MAN= snmpd.8 snmpd.conf.5
SRCS= parse.y ber.c log.c control.c snmpe.c \
mps.c trap.c mib.c smi.c kroute.c snmpd.c timer.c \
- pf.c
+ pf.c usm.c
-LDADD= -levent -lutil -lkvm
+LDADD= -levent -lutil -lkvm -lcrypto
DPADD= ${LIBEVENT} ${LIBUTIL}
CFLAGS+= -Wall -I${.CURDIR}
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
diff --git a/usr.sbin/snmpd/ber.3 b/usr.sbin/snmpd/ber.3
index e69ee7cd560..904e787855b 100644
--- a/usr.sbin/snmpd/ber.3
+++ b/usr.sbin/snmpd/ber.3
@@ -1,6 +1,6 @@
-.\" $OpenBSD: ber.3,v 1.9 2010/02/25 09:59:55 jmc Exp $
+.\" $OpenBSD: ber.3,v 1.10 2012/09/17 16:30:34 reyk Exp $
.\"
-.\" Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+.\" Copyright (c) 2007, 2012 Reyk Floeter <reyk@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
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: February 25 2010 $
+.Dd $Mdocdate: September 17 2012 $
.Dt BER 3
.Os
.Sh NAME
@@ -50,9 +50,11 @@
.Nm ber_write_elements ,
.Nm ber_set_readbuf ,
.Nm ber_read_elements ,
+.Nm ber_getpos ,
.Nm ber_free_elements ,
.Nm ber_calc_len ,
.Nm ber_set_application ,
+.Nm ber_set_writecallback
.Nm ber_free
.Nd parse ASN.1 with Basic Encoding Rules
.Sh SYNOPSIS
@@ -121,6 +123,8 @@
.Fn "ber_set_readbuf" "struct ber *ber" "void *buf" "size_t len"
.Ft "struct"
.Fn "ber_element *ber_read_elements" "struct ber *ber" "struct ber_element *root"
+.Ft off_t
+.Fn "ber_getpos" "struct ber_element *elm"
.Ft "void"
.Fn "ber_free_elements" "struct ber_element *root"
.Ft "size_t"
@@ -128,6 +132,8 @@
.Ft "void"
.Fn "ber_set_application" "struct ber *ber" "unsigned long (*cb)(struct ber_element *)"
.Ft "void"
+.Fn "ber_set_writecallback" "struct ber_element *elm" "void (*cb)(void *arg, size_t offs)" "void *arg"
+.Ft "void"
.Fn "ber_free" "struct ber *ber"
.Sh DESCRIPTION
The
@@ -188,8 +194,10 @@ struct ber_oid {
.Fn ber_write_elements ,
.Fn ber_set_readbuf ,
.Fn ber_read_elements ,
+.Fn ber_getpos ,
.Fn ber_free_elements ,
.Fn ber_set_application ,
+.Fn ber_set_writecallback ,
.Fn ber_free
.Sh RETURN VALUES
Upon successful completion
@@ -226,5 +234,4 @@ library was written by
and
.An Reyk Floeter Aq reyk@openbsd.org .
.Sh BUGS
-The code is buggy and incomplete.
This manpage is a stub.
diff --git a/usr.sbin/snmpd/ber.c b/usr.sbin/snmpd/ber.c
index 462e46bbef1..2a9ddd7114f 100644
--- a/usr.sbin/snmpd/ber.c
+++ b/usr.sbin/snmpd/ber.c
@@ -1,7 +1,7 @@
-/* $OpenBSD: ber.c,v 1.23 2010/09/20 08:30:13 martinh Exp $ */
+/* $OpenBSD: ber.c,v 1.24 2012/09/17 16:30:34 reyk Exp $ */
/*
- * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2007, 2012 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
*
@@ -269,7 +269,7 @@ ber_add_nstring(struct ber_element *prev, const char *string0, size_t len)
struct ber_element *elm;
char *string;
- if ((string = calloc(1, len)) == NULL)
+ if ((string = calloc(1, len + 1)) == NULL)
return NULL;
if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) {
free(string);
@@ -618,6 +618,7 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...)
void **ptr;
size_t *len, ret = 0, n = strlen(fmt);
char **s;
+ off_t *pos;
struct ber_oid *o;
struct ber_element *parent[_MAX_SEQ], **e;
@@ -695,6 +696,11 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...)
goto fail;
ret++;
break;
+ case 'p':
+ pos = va_arg(ap, off_t *);
+ *pos = ber_getpos(ber);
+ ret++;
+ continue;
case '{':
case '(':
if (ber->be_encoding != BER_TYPE_SEQUENCE &&
@@ -712,7 +718,7 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...)
goto fail;
ber = parent[level--];
ret++;
- continue;
+ break;
default:
goto fail;
}
@@ -808,6 +814,12 @@ ber_read_elements(struct ber *ber, struct ber_element *elm)
return root;
}
+off_t
+ber_getpos(struct ber_element *elm)
+{
+ return elm->be_offs;
+}
+
void
ber_free_elements(struct ber_element *root)
{
@@ -867,6 +879,8 @@ ber_dump_element(struct ber *ber, struct ber_element *root)
uint8_t u;
ber_dump_header(ber, root);
+ if (root->be_cb)
+ root->be_cb(root->be_cbarg, ber->br_wptr - ber->br_wbuf);
switch (root->be_encoding) {
case BER_TYPE_BOOLEAN:
@@ -1017,6 +1031,12 @@ get_len(struct ber *b, ssize_t *len)
return 1;
}
+ if (u == 0x80) {
+ /* Indefinite length not supported. */
+ errno = EINVAL;
+ return -1;
+ }
+
n = u & ~BER_TAG_MORE;
if (sizeof(ssize_t) < n) {
errno = ERANGE;
@@ -1036,12 +1056,6 @@ get_len(struct ber *b, ssize_t *len)
return -1;
}
- if (s == 0) {
- /* invalid encoding */
- errno = EINVAL;
- return -1;
- }
-
*len = s;
return r;
}
@@ -1066,8 +1080,16 @@ ber_read_element(struct ber *ber, struct ber_element *elm)
DPRINTF("ber read element size %zd\n", len);
totlen += r + len;
+ /* If using an external buffer and the total size of the element
+ * is larger then the external buffer don't bother to continue. */
+ if (ber->fd == -1 && len > ber->br_rend - ber->br_rptr) {
+ errno = ECANCELED;
+ return -1;
+ }
+
elm->be_type = type;
elm->be_len = len;
+ elm->be_offs = ber->br_offs; /* element position within stream */
elm->be_class = class;
if (elm->be_encoding == 0) {
@@ -1199,6 +1221,15 @@ ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *))
}
void
+ber_set_writecallback(struct ber_element *elm, void (*cb)(void *, size_t),
+ void *arg)
+{
+ elm->be_cb = cb;
+ elm->be_cbarg = arg;
+}
+
+
+void
ber_free(struct ber *b)
{
if (b->br_wbuf != NULL)
@@ -1208,17 +1239,7 @@ ber_free(struct ber *b)
static ssize_t
ber_getc(struct ber *b, u_char *c)
{
- ssize_t r;
- /*
- * XXX calling read here is wrong in many ways. The most obvious one
- * being that we will block till data arrives.
- * But for now it is _good enough_ *gulp*
- */
- if (b->fd == -1)
- r = ber_readbuf(b, c, 1);
- else
- r = read(b->fd, c, 1);
- return r;
+ return ber_read(b, c, 1);
}
static ssize_t
@@ -1248,5 +1269,7 @@ ber_read(struct ber *ber, void *buf, size_t len)
b += r;
remain -= r;
}
- return (b - (u_char *)buf);
+ r = b - (u_char *)buf;
+ ber->br_offs += r;
+ return r;
}
diff --git a/usr.sbin/snmpd/ber.h b/usr.sbin/snmpd/ber.h
index 907a6c5971a..fd43a3a2900 100644
--- a/usr.sbin/snmpd/ber.h
+++ b/usr.sbin/snmpd/ber.h
@@ -1,7 +1,7 @@
-/* $OpenBSD: ber.h,v 1.7 2009/01/03 18:41:41 aschrijver Exp $ */
+/* $OpenBSD: ber.h,v 1.8 2012/09/17 16:30:34 reyk Exp $ */
/*
- * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2007, 2012 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
@@ -17,13 +17,19 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#ifndef _BER_H
+#define _BER_H
+
struct ber_element {
struct ber_element *be_next;
unsigned long be_type;
unsigned long be_encoding;
size_t be_len;
+ off_t be_offs;
int be_free;
u_int8_t be_class;
+ void (*be_cb)(void *, size_t);
+ void *be_cbarg;
union {
struct ber_element *bv_sub;
void *bv_val;
@@ -36,6 +42,7 @@ struct ber_element {
struct ber {
int fd;
+ off_t br_offs;
u_char *br_wbuf;
u_char *br_wptr;
u_char *br_wend;
@@ -120,9 +127,14 @@ ssize_t ber_get_writebuf(struct ber *, void **);
int ber_write_elements(struct ber *, struct ber_element *);
void ber_set_readbuf(struct ber *, void *, size_t);
struct ber_element *ber_read_elements(struct ber *, struct ber_element *);
+off_t ber_getpos(struct ber_element *);
void ber_free_elements(struct ber_element *);
size_t ber_calc_len(struct ber_element *);
void ber_set_application(struct ber *,
unsigned long (*)(struct ber_element *));
+void ber_set_writecallback(struct ber_element *,
+ void (*)(void *, size_t), void *);
void ber_free(struct ber *);
__END_DECLS
+
+#endif /* _BER_H */
diff --git a/usr.sbin/snmpd/mib.c b/usr.sbin/snmpd/mib.c
index 7f726fb6304..e081a521afd 100644
--- a/usr.sbin/snmpd/mib.c
+++ b/usr.sbin/snmpd/mib.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mib.c,v 1.56 2012/07/08 11:24:43 blambert Exp $ */
+/* $OpenBSD: mib.c,v 1.57 2012/09/17 16:30:34 reyk Exp $ */
/*
* Copyright (c) 2012 Joel Knight <joel@openbsd.org>
@@ -330,6 +330,79 @@ mib_setsnmp(struct oid *oid, struct ber_oid *o, struct ber_element **elm)
}
/*
+ * Defined in SNMP-USER-BASED-SM-MIB.txt (RFC 3414)
+ */
+int mib_engine(struct oid *, struct ber_oid *, struct ber_element **);
+int mib_usmstats(struct oid *, struct ber_oid *, struct ber_element **);
+
+static struct oid usm_mib[] = {
+ { MIB(snmpEngine), OID_MIB },
+ { MIB(snmpEngineID), OID_RD, mib_engine },
+ { MIB(snmpEngineBoots), OID_RD, mib_engine },
+ { MIB(snmpEngineTime), OID_RD, mib_engine },
+ { MIB(snmpEngineMaxMsgSize), OID_RD, mib_engine },
+ { MIB(usmStats), OID_MIB },
+ { MIB(usmStatsUnsupportedSecLevels), OID_RD, mib_usmstats },
+ { MIB(usmStatsNotInTimeWindow), OID_RD, mib_usmstats },
+ { MIB(usmStatsUnknownUserNames), OID_RD, mib_usmstats },
+ { MIB(usmStatsUnknownEngineId), OID_RD, mib_usmstats },
+ { MIB(usmStatsWrongDigests), OID_RD, mib_usmstats },
+ { MIB(usmStatsDecryptionErrors), OID_RD, mib_usmstats },
+ { MIBEND }
+};
+
+int
+mib_engine(struct oid *oid, struct ber_oid *o, struct ber_element **elm)
+{
+ switch (oid->o_oid[OIDIDX_snmpEngine]) {
+ case 1:
+ *elm = ber_add_nstring(*elm, env->sc_engineid,
+ env->sc_engineid_len);
+ break;
+ case 2:
+ *elm = ber_add_integer(*elm, env->sc_engine_boots);
+ break;
+ case 3:
+ *elm = ber_add_integer(*elm, snmpd_engine_time());
+ break;
+ case 4:
+ *elm = ber_add_integer(*elm, READ_BUF_SIZE);
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+int
+mib_usmstats(struct oid *oid, struct ber_oid *o, struct ber_element **elm)
+{
+ struct snmp_stats *stats = &env->sc_stats;
+ long long i;
+ struct statsmap {
+ u_int8_t m_id;
+ u_int32_t *m_ptr;
+ } mapping[] = {
+ { OIDVAL_usmErrSecLevel, &stats->snmp_usmbadseclevel },
+ { OIDVAL_usmErrTimeWindow, &stats->snmp_usmtimewindow },
+ { OIDVAL_usmErrUserName, &stats->snmp_usmnosuchuser },
+ { OIDVAL_usmErrEngineId, &stats->snmp_usmnosuchengine },
+ { OIDVAL_usmErrDigest, &stats->snmp_usmwrongdigest },
+ { OIDVAL_usmErrDecrypt, &stats->snmp_usmdecrypterr },
+ };
+
+ for (i = 0; (u_int)i < (sizeof(mapping) / sizeof(mapping[0])); i++) {
+ if (oid->o_oid[OIDIDX_usmStats] == mapping[i].m_id) {
+ *elm = ber_add_integer(*elm, *mapping[i].m_ptr);
+ ber_set_header(*elm, BER_CLASS_APPLICATION,
+ SNMP_T_COUNTER32);
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+/*
* Defined in HOST-RESOURCES-MIB.txt (RFC 2790)
*/
@@ -723,7 +796,7 @@ mib_hrswrun(struct oid *oid, struct ber_oid *o, struct ber_element **elm)
/* Get and verify the current row index */
if (kinfo_proc(o->bo_id[OIDIDX_hrSWRunEntry], &kinfo) == -1)
- return (-1);
+ return (1);
if (kinfo == NULL)
return (1);
@@ -3533,6 +3606,9 @@ mib_init(void)
/* SNMPv2-MIB */
smi_mibtree(base_mib);
+ /* SNMP-USER-BASED-SM-MIB */
+ smi_mibtree(usm_mib);
+
/* HOST-RESOURCES-MIB */
smi_mibtree(hr_mib);
diff --git a/usr.sbin/snmpd/mib.h b/usr.sbin/snmpd/mib.h
index 44e694aa674..b03c3475479 100644
--- a/usr.sbin/snmpd/mib.h
+++ b/usr.sbin/snmpd/mib.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: mib.h,v 1.26 2012/06/14 17:31:32 matthew Exp $ */
+/* $OpenBSD: mib.h,v 1.27 2012/09/17 16:30:34 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -106,6 +106,32 @@
#define MIB_authenticationFailure MIB_snmpTraps, 5
#define MIB_egpNeighborLoss MIB_snmpTraps, 6
+/* SNMP-USER-BASED-SM-MIB */
+#define MIB_framework MIB_snmpModules, 10
+#define MIB_frameworkObjects MIB_framework, 2
+#define OIDIDX_snmpEngine 9
+#define MIB_snmpEngine MIB_frameworkObjects, 1
+#define MIB_snmpEngineID MIB_snmpEngine, 1
+#define MIB_snmpEngineBoots MIB_snmpEngine, 2
+#define MIB_snmpEngineTime MIB_snmpEngine, 3
+#define MIB_snmpEngineMaxMsgSize MIB_snmpEngine, 4
+#define MIB_usm MIB_snmpModules, 15
+#define MIB_usmObjects MIB_usm, 1
+#define MIB_usmStats MIB_usmObjects, 1
+#define OIDIDX_usmStats 9
+#define OIDVAL_usmErrSecLevel 1
+#define OIDVAL_usmErrTimeWindow 2
+#define OIDVAL_usmErrUserName 3
+#define OIDVAL_usmErrEngineId 4
+#define OIDVAL_usmErrDigest 5
+#define OIDVAL_usmErrDecrypt 6
+#define MIB_usmStatsUnsupportedSecLevels MIB_usmStats, OIDVAL_usmErrSecLevel
+#define MIB_usmStatsNotInTimeWindow MIB_usmStats, OIDVAL_usmErrTimeWindow
+#define MIB_usmStatsUnknownUserNames MIB_usmStats, OIDVAL_usmErrUserName
+#define MIB_usmStatsUnknownEngineId MIB_usmStats, OIDVAL_usmErrEngineId
+#define MIB_usmStatsWrongDigests MIB_usmStats, OIDVAL_usmErrDigest
+#define MIB_usmStatsDecryptionErrors MIB_usmStats, OIDVAL_usmErrDecrypt
+
/* HOST-RESOURCES-MIB */
#define MIB_host MIB_mib_2, 25
#define MIB_hrSystem MIB_host, 1
@@ -395,7 +421,8 @@
#define MIB_sFlow MIB_enterprises, 14706
#define MIB_microSystems MIB_enterprises, 18623
#define MIB_vantronix MIB_enterprises, 26766
-#define MIB_openBSD MIB_enterprises, 30155
+#define OIDVAL_openBSD_eid 30155
+#define MIB_openBSD MIB_enterprises, OIDVAL_openBSD_eid
/* UCD-DISKIO-MIB */
#define MIB_ucdExperimental MIB_ucDavis, 13
@@ -729,6 +756,23 @@
{ MIBDECL(authenticationFailure) }, \
{ MIBDECL(egpNeighborLoss) }, \
\
+ { MIBDECL(framework) }, \
+ { MIBDECL(frameworkObjects) }, \
+ { MIBDECL(snmpEngine) }, \
+ { MIBDECL(snmpEngineID) }, \
+ { MIBDECL(snmpEngineBoots) }, \
+ { MIBDECL(snmpEngineTime) }, \
+ { MIBDECL(snmpEngineMaxMsgSize) }, \
+ { MIBDECL(usm) }, \
+ { MIBDECL(usmObjects) }, \
+ { MIBDECL(usmStats) }, \
+ { MIBDECL(usmStatsUnsupportedSecLevels) }, \
+ { MIBDECL(usmStatsNotInTimeWindow) }, \
+ { MIBDECL(usmStatsUnknownUserNames) }, \
+ { MIBDECL(usmStatsUnknownEngineId) }, \
+ { MIBDECL(usmStatsWrongDigests) }, \
+ { MIBDECL(usmStatsDecryptionErrors) }, \
+ \
{ MIBDECL(host) }, \
{ MIBDECL(hrSystem) }, \
{ MIBDECL(hrSystemUptime) }, \
diff --git a/usr.sbin/snmpd/mps.c b/usr.sbin/snmpd/mps.c
index dcdbc9d3853..6e87e804d70 100644
--- a/usr.sbin/snmpd/mps.c
+++ b/usr.sbin/snmpd/mps.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mps.c,v 1.14 2010/09/20 08:56:16 martinh Exp $ */
+/* $OpenBSD: mps.c,v 1.15 2012/09/17 16:30:34 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -219,6 +219,7 @@ mps_getnextreq(struct ber_element *root, struct ber_oid *o)
return (ber);
}
+getnext:
for (next = value; next != NULL;) {
next = smi_next(next);
if (next == NULL)
@@ -231,11 +232,18 @@ mps_getnextreq(struct ber_element *root, struct ber_oid *o)
if (next->o_flags & OID_TABLE) {
/* Get the next table row for this column */
- if (mps_table(next, o, &no) == NULL)
- return (ber);
+ if (mps_table(next, o, &no) == NULL) {
+ value = next;
+ goto getnext;
+ }
bcopy(&no, o, sizeof(*o));
- if ((ret = next->o_get(next, o, &ber)) != 0)
+ if ((ret = next->o_get(next, o, &ber)) != 0) {
+ if (ret == 1) {
+ value = next;
+ goto getnext;
+ }
return (NULL);
+ }
} else {
bcopy(&next->o_id, o, sizeof(*o));
ber = ber_add_noid(ber, &next->o_id,
diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y
index b6503233529..1815f88d459 100644
--- a/usr.sbin/snmpd/parse.y
+++ b/usr.sbin/snmpd/parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: parse.y,v 1.20 2011/04/21 14:55:22 sthen Exp $ */
+/* $OpenBSD: parse.y,v 1.21 2012/09/17 16:30:34 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -84,6 +84,7 @@ char *symget(const char *);
struct snmpd *conf = NULL;
static int errors = 0;
static struct addresslist *hlist;
+static struct usmuser *user = NULL;
struct address *host_v4(const char *);
struct address *host_v6(const char *);
@@ -104,6 +105,8 @@ typedef struct {
void *data;
long long value;
} data;
+ enum usmauth auth;
+ enum usmpriv enc;
} v;
int lineno;
} YYSTYPE;
@@ -114,13 +117,15 @@ typedef struct {
%token LISTEN ON
%token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER
%token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER
-%token ERROR
+%token SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.string> hostcmn
-%type <v.number> optwrite yesno
+%type <v.number> optwrite yesno seclevel
%type <v.data> objtype
%type <v.oid> oid hostoid
+%type <v.auth> auth
+%type <v.enc> enc
%%
@@ -236,6 +241,25 @@ main : LISTEN ON STRING {
else
conf->sc_rtfilter = 0;
}
+ | SECLEVEL seclevel {
+ conf->sc_min_seclevel = $2;
+ }
+ | USER STRING {
+ const char *errstr;
+ user = usm_newuser($2, &errstr);
+ if (user == NULL) {
+ yyerror(errstr);
+ free($2);
+ YYERROR;
+ }
+ } userspecs {
+ const char *errstr;
+ if (usm_checkuser(user, &errstr) < 0) {
+ yyerror(errstr);
+ YYERROR;
+ }
+ user = NULL;
+ }
;
system : SYSTEM sysmib
@@ -367,6 +391,58 @@ comma : /* empty */
| ','
;
+seclevel : NONE { $$ = 0; }
+ | AUTH { $$ = SNMP_MSGFLAG_AUTH; }
+ | ENC { $$ = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV; }
+ ;
+
+userspecs : /* empty */
+ | userspecs userspec
+ ;
+
+userspec : AUTHKEY STRING {
+ user->uu_authkey = $2;
+ }
+ | AUTH auth {
+ user->uu_auth = $2;
+ }
+ | ENCKEY STRING {
+ user->uu_privkey = $2;
+ }
+ | ENC enc {
+ user->uu_priv = $2;
+ }
+ ;
+
+auth : STRING {
+ if (!strcasecmp($1, "hmac-md5"))
+ $$ = AUTH_MD5;
+ else if (!strcasecmp($1, "hmac-sha1"))
+ $$ = AUTH_SHA1;
+ else {
+ yyerror("syntax error, bad auth hmac");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+enc : STRING {
+ if (!strcasecmp($1, "des"))
+ $$ = PRIV_DES;
+ else if (!strcasecmp($1, "aes"))
+ $$ = PRIV_AES;
+ else {
+ yyerror("syntax error, bad encryption cipher");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+
+ }
+ ;
+
%%
struct keywords {
@@ -399,24 +475,31 @@ lookup(char *s)
{
/* this has to be sorted always */
static const struct keywords keywords[] = {
+ { "auth", AUTH },
+ { "authkey", AUTHKEY },
{ "community", COMMUNITY },
{ "contact", CONTACT },
{ "description", DESCR },
+ { "enc", ENC },
+ { "enckey", ENCKEY },
{ "filter-routes", RTFILTER },
{ "include", INCLUDE },
{ "integer", INTEGER },
{ "listen", LISTEN },
{ "location", LOCATION },
{ "name", NAME },
+ { "none", NONE },
{ "oid", OBJECTID },
{ "on", ON },
{ "read-only", READONLY },
{ "read-write", READWRITE },
{ "receiver", RECEIVER },
+ { "seclevel", SECLEVEL },
{ "services", SERVICES },
{ "string", OCTETSTRING },
{ "system", SYSTEM },
- { "trap", TRAP }
+ { "trap", TRAP },
+ { "user", USER }
};
const struct keywords *p;
diff --git a/usr.sbin/snmpd/snmp.h b/usr.sbin/snmpd/snmp.h
index e44f6758fb3..642d90d0c6c 100644
--- a/usr.sbin/snmpd/snmp.h
+++ b/usr.sbin/snmpd/snmp.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: snmp.h,v 1.8 2009/11/26 17:32:47 reyk Exp $ */
+/* $OpenBSD: snmp.h,v 1.9 2012/09/17 16:30:34 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -135,4 +135,19 @@ enum snmp_error {
SNMP_ERROR_INCONNAME = 18
};
+enum snmp_security_model {
+ SNMP_SEC_ANY = 0,
+ SNMP_SEC_SNMPv1 = 1,
+ SNMP_SEC_SNMPv2c = 2,
+ SNMP_SEC_USM = 3,
+ SNMP_SEC_TSM = 4
+};
+
+#define SNMP_MSGFLAG_AUTH 0x01
+#define SNMP_MSGFLAG_PRIV 0x02
+#define SNMP_MSGFLAG_SECMASK (SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV)
+#define SNMP_MSGFLAG_REPORT 0x04
+
+#define SNMP_MAX_TIMEWINDOW 150 /* RFC3414 */
+
#endif /* SNMP_HEADER */
diff --git a/usr.sbin/snmpd/snmpd.c b/usr.sbin/snmpd/snmpd.c
index b1d70834846..61b9f6ffac5 100644
--- a/usr.sbin/snmpd/snmpd.c
+++ b/usr.sbin/snmpd/snmpd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: snmpd.c,v 1.11 2012/05/28 20:55:40 joel Exp $ */
+/* $OpenBSD: snmpd.c,v 1.12 2012/09/17 16:30:35 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -37,6 +37,7 @@
#include <pwd.h>
#include "snmpd.h"
+#include "mib.h"
__dead void usage(void);
@@ -44,6 +45,7 @@ void snmpd_sig_handler(int, short, void *);
void snmpd_shutdown(struct snmpd *);
void snmpd_dispatch_snmpe(int, short, void *);
int check_child(pid_t, const char *);
+void snmpd_generate_engineid(struct snmpd *);
struct snmpd *snmpd_env;
@@ -171,6 +173,7 @@ main(int argc, char *argv[])
}
gettimeofday(&env->sc_starttime, NULL);
+ env->sc_engine_boots = 0;
log_info("startup");
@@ -183,6 +186,8 @@ main(int argc, char *argv[])
session_socket_blockmode(pipe_parent2snmpe[0], BM_NONBLOCK);
session_socket_blockmode(pipe_parent2snmpe[1], BM_NONBLOCK);
+ snmpd_generate_engineid(env);
+
snmpe_pid = snmpe(env, pipe_parent2snmpe);
setproctitle("parent");
@@ -339,3 +344,57 @@ snmpd_socket_af(struct sockaddr_storage *ss, in_port_t port)
return (s);
}
+void
+snmpd_generate_engineid(struct snmpd *env)
+{
+ u_int32_t oid_enterprise, rnd, tim;
+
+ /* RFC 3411 */
+ memset(env->sc_engineid, 0, sizeof (env->sc_engineid));
+ oid_enterprise = htonl(OIDVAL_openBSD_eid);
+ memcpy(env->sc_engineid, &oid_enterprise, sizeof (oid_enterprise));
+ env->sc_engineid[0] |= SNMP_ENGINEID_NEW;
+ env->sc_engineid_len = sizeof (oid_enterprise);
+ /* XXX alternatively configure engine id via snmpd.conf */
+ env->sc_engineid[(env->sc_engineid_len)++] = SNMP_ENGINEID_FMT_EID;
+ rnd = arc4random();
+ memcpy(&env->sc_engineid[env->sc_engineid_len], &rnd, sizeof (rnd));
+ env->sc_engineid_len += sizeof (rnd);
+ tim = htonl(env->sc_starttime.tv_sec);
+ memcpy(&env->sc_engineid[env->sc_engineid_len], &tim, sizeof (tim));
+ env->sc_engineid_len += sizeof (tim);
+ return;
+}
+
+u_long
+snmpd_engine_time(void)
+{
+ struct timeval now;
+
+ /*
+ * snmpEngineBoots should be stored in a non-volatile storage.
+ * snmpEngineTime is the number of seconds since snmpEngineBoots
+ * was last incremented. We don't rely on non-volatile storage.
+ * snmpEngineBoots is set to zero and snmpEngineTime to the system
+ * clock. Hence, the tuple (snmpEngineBoots, snmpEngineTime) is
+ * still unique and protects us against replay attacks. It only
+ * 'expires' a little bit sooner than the RFC3414 method.
+ */
+ gettimeofday(&now, NULL);
+ return now.tv_sec;
+}
+
+char *
+tohexstr(u_int8_t *str, int len)
+{
+#define MAXHEXSTRLEN 256
+ static char hstr[2 * MAXHEXSTRLEN + 1];
+ char *r = hstr;
+
+ if (len > MAXHEXSTRLEN)
+ len = MAXHEXSTRLEN; /* truncate */
+ while (len-- > 0)
+ r += snprintf(r, len * 2, "%0*x", 2, *str++);
+ *r = '\0';
+ return hstr;
+}
diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5
index ff8093240f2..768ad06b99f 100644
--- a/usr.sbin/snmpd/snmpd.conf.5
+++ b/usr.sbin/snmpd/snmpd.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: snmpd.conf.5,v 1.17 2012/04/24 14:56:09 jmc Exp $
+.\" $OpenBSD: snmpd.conf.5,v 1.18 2012/09/17 16:30:35 reyk Exp $
.\"
.\" Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: April 24 2012 $
+.Dd $Mdocdate: September 17 2012 $
.Dt SNMPD.CONF 5
.Os
.Sh NAME
@@ -36,6 +36,8 @@ configuration file.
.It Sy Global Configuration
Global runtime settings for
.Xr snmpd 8 .
+.It Sy User Configuration
+USM user definitions.
.It Sy OID Configuration
Custom configuration of SNMP object identifiers and values.
.El
@@ -94,7 +96,7 @@ The default value is
.Pp
.It Xo
.Ic filter-routes
-.Pq Ic yes Ns | Ns Ic no
+.Pq Ic yes \*(Ba\ no
.Xc
If set to
.Ic yes ,
@@ -104,6 +106,33 @@ reduced during bulk updates.
The default is
.Ic no .
.Pp
+.It Xo
+.Ic seclevel
+.Pq Ic none \*(Ba\ auth \*(Ba\ enc
+.Xc
+Specify the lowest security level that
+.Xr snmpd 8
+accepts:
+.Bl -tag -width "auth" -offset ident
+.It Ic none
+Both authentication and encryption of messages is optional.
+This is the default value.
+.It Ic auth
+Authentication of messages is mandatory.
+.Xr snmpd 8
+will discard any messages that don't have a valid digest.
+Encryption of messages is optional.
+.It Ic enc
+Messages must be encrypted and must have a valid digest for authentication.
+Otherwise they will be discarded.
+.El
+.Pp
+If the chosen value is different from
+.Ic none
+.Xr snmpd 8
+will accept only SNMPv3 requests since older versions neither support
+authentication nor encryption.
+.Pp
.It Ic system contact Ar string
Specify the name or description of the system contact, typically a
name or an e-mail address.
@@ -170,6 +199,48 @@ The default community is specified by the global
option.
.Pp
.El
+.Sh User Configuration
+Users for the SNMP User-based Security Model (USM, RFC3414) must be
+defined in the configuration file:
+.Pp
+.Bl -tag -width xxxx
+.It Xo
+.Ic user Ar name
+.Op Ic authkey Ar key Ic auth Ar hmac
+.Op Ic enckey Ar key Ic enc Ar cipher
+.Xc
+Defines a known user. The
+.Ic authkey
+keyword is required to specifiy the digest key used to authenticate
+messages. If this keyword is omitted then authentication is disabled
+for this user account. Optionally the HMAC algorithm used for authentication
+can be specified.
+.Ar hmac
+must be either
+.Ic hmac-md5
+or
+.Ic hmac-sha1 .
+If omitted the default is
+.Ic hmac-sha1 .
+
+With
+.Ic enckey
+the encryption key used to encrypt and decrypt messages for privacy is defined.
+Without an
+.Ic enckey
+specification the user account will neither accept encrypted incoming
+messages nor will it encrypt outgoing messsages. The
+.Ar enc
+algorithm can be either
+.Ic des
+or
+.Ic aes
+and defaults to
+.Ic des .
+
+Any user account that has encryption enabled requires authentication to
+be enabled, too.
+.El
.Sh OID CONFIGURATION
It is possible to specify user-defined OIDs in the configuration file:
.Pp
diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h
index 3e2a7cd53af..5e3a1eb6e80 100644
--- a/usr.sbin/snmpd/snmpd.h
+++ b/usr.sbin/snmpd/snmpd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: snmpd.h,v 1.35 2012/05/28 20:55:40 joel Exp $ */
+/* $OpenBSD: snmpd.h,v 1.36 2012/09/17 16:30:35 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -25,7 +25,7 @@
#include <net/pfvar.h>
#include <net/route.h>
-#include <ber.h>
+#include "ber.h"
#include <snmp.h>
#include <imsg.h>
@@ -44,12 +44,30 @@
#define SNMPD_MAXCOMMUNITYLEN SNMPD_MAXSTRLEN
#define SNMPD_MAXVARBIND 0x7fffffff
#define SNMPD_MAXVARBINDLEN 1210
+#define SNMPD_MAXENGINEIDLEN 32
+#define SNMPD_MAXUSERNAMELEN 32
+#define SNMPD_MAXCONTEXNAMELEN 32
+
+#define SNMP_USM_DIGESTLEN 12
+#define SNMP_USM_SALTLEN 8
+#define SNMP_USM_KEYLEN 64
+#define SNMP_CIPHER_KEYLEN 16
#define SMALL_READ_BUF_SIZE 1024
#define READ_BUF_SIZE 65535
#define RT_BUF_SIZE 16384
#define MAX_RTSOCK_BUF (128 * 1024)
+#define SNMP_ENGINEID_OLD 0x00
+#define SNMP_ENGINEID_NEW 0x80 /* RFC3411 */
+
+#define SNMP_ENGINEID_FMT_IPv4 1
+#define SNMP_ENGINEID_FMT_IPv6 2
+#define SNMP_ENGINEID_FMT_MAC 3
+#define SNMP_ENGINEID_FMT_TEXT 4
+#define SNMP_ENGINEID_FMT_OCT 5
+#define SNMP_ENGINEID_FMT_EID 128
+
enum imsg_type {
IMSG_NONE,
IMSG_CTL_OK, /* answer to snmpctl requests */
@@ -238,13 +256,39 @@ struct pfr_buffer {
* daemon structures
*/
+#define MSG_HAS_AUTH(m) (((m)->sm_flags & SNMP_MSGFLAG_AUTH) != 0)
+#define MSG_HAS_PRIV(m) (((m)->sm_flags & SNMP_MSGFLAG_PRIV) != 0)
+#define MSG_SECLEVEL(m) ((m)->sm_flags & SNMP_MSGFLAG_SECMASK)
+#define MSG_REPORT(m) (((m)->sm_flags & SNMP_MSGFLAG_REPORT) != 0)
+
struct snmp_message {
+ struct ber_element *sm_resp;
+ u_int8_t sm_data[READ_BUF_SIZE];
+ size_t sm_datalen;
+
u_int sm_version;
- char sm_community[SNMPD_MAXCOMMUNITYLEN];
- u_int sm_context;
- struct ber_element *sm_header;
- struct ber_element *sm_headerend;
+ /* V1, V2c */
+ char sm_community[SNMPD_MAXCOMMUNITYLEN];
+ int sm_context;
+
+ /* V3 */
+ long long sm_msgid;
+ long long sm_max_msg_size;
+ u_int8_t sm_flags;
+ long long sm_secmodel;
+ u_int32_t sm_engine_boots;
+ u_int32_t sm_engine_time;
+ char sm_ctxengineid[SNMPD_MAXENGINEIDLEN];
+ size_t sm_ctxengineid_len;
+ char sm_ctxname[SNMPD_MAXCONTEXNAMELEN+1];
+
+ /* USM */
+ char sm_username[SNMPD_MAXUSERNAMELEN+1];
+ struct usmuser *sm_user;
+ size_t sm_digest_offs;
+ char sm_salt[SNMP_USM_SALTLEN];
+ int sm_usmerr;
long long sm_request;
@@ -292,6 +336,14 @@ struct snmp_stats {
int snmp_enableauthentraps;
u_int32_t snmp_silentdrops;
u_int32_t snmp_proxydrops;
+
+ /* USM stats (RFC 3414) */
+ u_int32_t snmp_usmbadseclevel;
+ u_int32_t snmp_usmtimewindow;
+ u_int32_t snmp_usmnosuchuser;
+ u_int32_t snmp_usmnosuchengine;
+ u_int32_t snmp_usmwrongdigest;
+ u_int32_t snmp_usmdecrypterr;
};
struct address {
@@ -306,6 +358,37 @@ struct address {
};
TAILQ_HEAD(addresslist, address);
+enum usmauth {
+ AUTH_NONE = 0,
+ AUTH_MD5, /* HMAC-MD5-96, RFC3414 */
+ AUTH_SHA1 /* HMAC-SHA-96, RFC3414 */
+};
+
+#define AUTH_DEFAULT AUTH_SHA1 /* Default digest */
+
+enum usmpriv {
+ PRIV_NONE = 0,
+ PRIV_DES, /* CBC-DES, RFC3414 */
+ PRIV_AES /* CFB128-AES-128, RFC3826 */
+};
+
+#define PRIV_DEFAULT PRIV_DES /* Default cipher */
+
+struct usmuser {
+ char *uu_name;
+
+ enum usmauth uu_auth;
+ char *uu_authkey;
+ unsigned uu_authkeylen;
+
+
+ enum usmpriv uu_priv;
+ char *uu_privkey;
+ unsigned long long uu_salt;
+
+ SLIST_ENTRY(usmuser) uu_next;
+};
+
struct snmpd {
u_int8_t sc_flags;
#define SNMPD_F_VERBOSE 0x01
@@ -316,6 +399,7 @@ struct snmpd {
int sc_sock;
struct event sc_ev;
struct timeval sc_starttime;
+ u_int32_t sc_engine_boots;
struct control_sock sc_csock;
struct control_sock sc_rcsock;
@@ -324,6 +408,9 @@ struct snmpd {
char sc_rwcommunity[SNMPD_MAXCOMMUNITYLEN];
char sc_trcommunity[SNMPD_MAXCOMMUNITYLEN];
+ char sc_engineid[SNMPD_MAXENGINEIDLEN];
+ size_t sc_engineid_len;
+
struct snmp_stats sc_stats;
struct addresslist sc_trapreceivers;
@@ -331,6 +418,8 @@ struct snmpd {
int sc_ncpu;
int64_t *sc_cpustates;
int sc_rtfilter;
+
+ int sc_min_seclevel;
};
/* control.c */
@@ -449,5 +538,18 @@ void timer_init(void);
/* snmpd.c */
int snmpd_socket_af(struct sockaddr_storage *, in_port_t);
-
+u_long snmpd_engine_time(void);
+char *tohexstr(u_int8_t *, int);
+
+/* usm.c */
+void usm_generate_keys(void);
+struct usmuser *usm_newuser(char *name, const char **);
+struct usmuser *usm_finduser(char *name);
+int usm_checkuser(struct usmuser *, const char **);
+struct ber_element *usm_decode(struct snmp_message *, struct ber_element *,
+ const char **);
+struct ber_element *usm_encode(struct snmp_message *, struct ber_element *);
+struct ber_element *usm_encrypt(struct snmp_message *, struct ber_element *);
+void usm_finalize_digest(struct snmp_message *, char *, ssize_t);
+void usm_make_report(struct snmp_message *);
#endif /* _SNMPD_H */
diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c
index 5614afd1db8..4b017dca28f 100644
--- a/usr.sbin/snmpd/snmpe.c
+++ b/usr.sbin/snmpd/snmpe.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: snmpe.c,v 1.28 2010/09/20 12:32:41 martinh Exp $ */
+/* $OpenBSD: snmpe.c,v 1.29 2012/09/17 16:30:35 reyk Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net>
@@ -39,6 +39,7 @@
#include <vis.h>
#include "snmpd.h"
+#include "mib.h"
int snmpe_parse(struct sockaddr_storage *,
struct ber_element *, struct snmp_message *);
@@ -49,6 +50,7 @@ void snmpe_shutdown(void);
void snmpe_dispatch_parent(int, short, void *);
int snmpe_bind(struct address *);
void snmpe_recvmsg(int fd, short, void *);
+int snmpe_encode(struct snmp_message *);
struct snmpd *env = NULL;
@@ -164,6 +166,8 @@ snmpe(struct snmpd *x_env, int pipe_parent2snmpe[2])
trap_init();
timer_init();
+ usm_generate_keys();
+
event_dispatch();
snmpe_shutdown();
@@ -495,29 +499,67 @@ snmpe_parse(struct sockaddr_storage *ss,
unsigned long type;
u_int class, state, i = 0, j = 0;
char *comn, buf[BUFSIZ], host[MAXHOSTNAMELEN];
+ char *flagstr, *ctxname;
struct ber_oid o;
size_t len;
- bzero(msg, sizeof(*msg));
-
- if (ber_scanf_elements(root, "e{ieset{e",
- &msg->sm_header, &ver, &msg->sm_headerend, &comn,
- &msg->sm_pdu, &class, &type, &a) != 0)
+ if (ber_scanf_elements(root, "{ie", &ver, &a) != 0)
goto parsefail;
/* SNMP version and community */
- switch (ver) {
+ msg->sm_version = ver;
+ switch (msg->sm_version) {
case SNMP_V1:
case SNMP_V2:
- msg->sm_version = ver;
+ if (env->sc_min_seclevel != 0)
+ goto badversion;
+ if (ber_scanf_elements(a, "se", &comn, &msg->sm_pdu) != 0)
+ goto parsefail;
+ if (strlcpy(msg->sm_community, comn,
+ sizeof(msg->sm_community)) >= sizeof(msg->sm_community)) {
+ stats->snmp_inbadcommunitynames++;
+ errstr = "community name too long";
+ goto fail;
+ }
break;
case SNMP_V3:
+ if (ber_scanf_elements(a, "{iisi}e",
+ &msg->sm_msgid, &msg->sm_max_msg_size, &flagstr,
+ &msg->sm_secmodel, &a) != 0)
+ goto parsefail;
+
+ msg->sm_flags = *flagstr;
+ if (MSG_SECLEVEL(msg) < env->sc_min_seclevel ||
+ msg->sm_secmodel != SNMP_SEC_USM) {
+ /* XXX currently only USM supported */
+ errstr = "unsupported security model";
+ stats->snmp_usmbadseclevel++;
+ msg->sm_usmerr = OIDVAL_usmErrSecLevel;
+ goto parsefail;
+ }
+
+ if ((a = usm_decode(msg, a, &errstr)) == NULL)
+ goto parsefail;
+
+ if (ber_scanf_elements(a, "{xxe",
+ &msg->sm_ctxengineid, &msg->sm_ctxengineid_len,
+ &ctxname, &len, &msg->sm_pdu) != 0)
+ goto parsefail;
+ if (len > SNMPD_MAXCONTEXNAMELEN)
+ goto parsefail;
+ memcpy(msg->sm_ctxname, ctxname, len);
+ msg->sm_ctxname[len] = '\0';
+ break;
default:
+ badversion:
stats->snmp_inbadversions++;
errstr = "bad snmp version";
goto fail;
}
+ if (ber_scanf_elements(msg->sm_pdu, "t{e", &class, &type, &a) != 0)
+ goto parsefail;
+
/* SNMP PDU context */
if (class != BER_CLASS_CONTEXT)
goto parsefail;
@@ -535,8 +577,9 @@ snmpe_parse(struct sockaddr_storage *ss,
case SNMP_C_GETNEXTREQ:
if (type == SNMP_C_GETNEXTREQ)
stats->snmp_ingetnexts++;
- if (strcmp(env->sc_rdcommunity, comn) != 0 &&
- strcmp(env->sc_rwcommunity, comn) != 0) {
+ if (msg->sm_version != SNMP_V3 &&
+ strcmp(env->sc_rdcommunity, msg->sm_community) != 0 &&
+ strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
stats->snmp_inbadcommunitynames++;
errstr = "wrong read community";
goto fail;
@@ -545,8 +588,9 @@ snmpe_parse(struct sockaddr_storage *ss,
break;
case SNMP_C_SETREQ:
stats->snmp_insetrequests++;
- if (strcmp(env->sc_rwcommunity, comn) != 0) {
- if (strcmp(env->sc_rdcommunity, comn) != 0)
+ if (msg->sm_version != SNMP_V3 &&
+ strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
+ if (strcmp(env->sc_rdcommunity, msg->sm_community) != 0)
stats->snmp_inbadcommunitynames++;
else
stats->snmp_inbadcommunityuses++;
@@ -561,7 +605,8 @@ snmpe_parse(struct sockaddr_storage *ss,
goto parsefail;
case SNMP_C_TRAP:
case SNMP_C_TRAPV2:
- if (strcmp(env->sc_trcommunity, comn) != 0) {
+ if (msg->sm_version != SNMP_V3 &&
+ strcmp(env->sc_trcommunity, msg->sm_community) != 0) {
stats->snmp_inbadcommunitynames++;
errstr = "wrong trap community";
goto fail;
@@ -574,13 +619,6 @@ snmpe_parse(struct sockaddr_storage *ss,
goto parsefail;
}
- if (strlcpy(msg->sm_community, comn, sizeof(msg->sm_community)) >=
- sizeof(msg->sm_community)) {
- stats->snmp_inbadcommunitynames++;
- errstr = "community name too long";
- goto fail;
- }
-
/* SNMP PDU */
if (ber_scanf_elements(a, "iiie{et",
&req, &errval, &erridx, &msg->sm_pduend,
@@ -600,9 +638,17 @@ snmpe_parse(struct sockaddr_storage *ss,
msg->sm_errorindex = erridx;
print_host(ss, host, sizeof(host));
- log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d request %lld",
- host, msg->sm_version + 1, msg->sm_community, msg->sm_context,
- msg->sm_request);
+ if (msg->sm_version == SNMP_V3)
+ log_debug("snmpe_parse: %s: SNMPv3 context %d, flags %#x, "
+ "secmodel %lld, user '%s', ctx-engine %s, ctx-name '%s', "
+ "request %lld", host, msg->sm_context, msg->sm_flags,
+ msg->sm_secmodel, msg->sm_username,
+ tohexstr(msg->sm_ctxengineid, msg->sm_ctxengineid_len),
+ msg->sm_ctxname, msg->sm_request);
+ else
+ log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d "
+ "request %lld", host, msg->sm_version + 1,
+ msg->sm_community, msg->sm_context, msg->sm_request);
errstr = "invalid varbind element";
for (i = 1, a = msg->sm_varbind, last = NULL;
@@ -719,40 +765,47 @@ snmpe_recvmsg(int fd, short sig, void *arg)
{
struct snmp_stats *stats = &env->sc_stats;
struct sockaddr_storage ss;
- u_int8_t buf[READ_BUF_SIZE], *ptr = NULL;
+ u_int8_t *ptr = NULL;
socklen_t slen;
ssize_t len;
struct ber ber;
- struct ber_element *req = NULL, *resp = NULL;
+ struct ber_element *req = NULL;
struct snmp_message msg;
+ bzero(&msg, sizeof(msg));
slen = sizeof(ss);
- if ((len = recvfrom(fd, buf, sizeof(buf), 0,
+ if ((len = recvfrom(fd, msg.sm_data, sizeof(msg.sm_data), 0,
(struct sockaddr *)&ss, &slen)) < 1)
return;
stats->snmp_inpkts++;
+ msg.sm_datalen = (size_t)len;
bzero(&ber, sizeof(ber));
ber.fd = -1;
ber_set_application(&ber, snmpe_application);
- ber_set_readbuf(&ber, buf, len);
+ ber_set_readbuf(&ber, msg.sm_data, msg.sm_datalen);
req = ber_read_elements(&ber, NULL);
-
if (req == NULL) {
stats->snmp_inasnparseerrs++;
goto done;
}
#ifdef DEBUG
+ fprintf(stderr, "recv msg:\n");
snmpe_debug_elements(req);
#endif
- if (snmpe_parse(&ss, req, &msg) == -1)
- goto done;
+ if (snmpe_parse(&ss, req, &msg) == -1) {
+ if (msg.sm_usmerr != 0 && MSG_REPORT(&msg))
+ usm_make_report(&msg);
+ else
+ goto done;
+ } else
+ msg.sm_context = SNMP_C_GETRESP;
- if (msg.sm_varbindresp == NULL)
+ if (msg.sm_varbindresp == NULL && msg.sm_pduend != NULL)
msg.sm_varbindresp = ber_unlink_elements(msg.sm_pduend);
switch (msg.sm_error) {
@@ -775,21 +828,14 @@ snmpe_recvmsg(int fd, short sig, void *arg)
}
/* Create new SNMP packet */
- resp = ber_add_sequence(NULL);
- ber_printf_elements(resp, "ds{tiii{e}}.",
- msg.sm_version, msg.sm_community,
- BER_CLASS_CONTEXT, SNMP_C_GETRESP,
- msg.sm_request, msg.sm_error, msg.sm_errorindex,
- msg.sm_varbindresp);
-
-#ifdef DEBUG
- snmpe_debug_elements(resp);
-#endif
+ if (snmpe_encode(&msg) < 0)
+ goto done;
- len = ber_write_elements(&ber, resp);
+ len = ber_write_elements(&ber, msg.sm_resp);
if (ber_get_writebuf(&ber, (void *)&ptr) == -1)
goto done;
+ usm_finalize_digest(&msg, ptr, len);
len = sendto(fd, ptr, len, 0, (struct sockaddr *)&ss, slen);
if (len != -1)
stats->snmp_outpkts++;
@@ -798,6 +844,59 @@ snmpe_recvmsg(int fd, short sig, void *arg)
ber_free(&ber);
if (req != NULL)
ber_free_elements(req);
- if (resp != NULL)
- ber_free_elements(resp);
+ if (msg.sm_resp != NULL)
+ ber_free_elements(msg.sm_resp);
+}
+
+int
+snmpe_encode(struct snmp_message *msg)
+{
+ struct ber_element *ehdr;
+ struct ber_element *pdu, *epdu;
+
+ msg->sm_resp = ber_add_sequence(NULL);
+ if ((ehdr = ber_add_integer(msg->sm_resp, msg->sm_version)) == NULL)
+ return -1;
+ if (msg->sm_version == SNMP_V3) {
+ char f = MSG_SECLEVEL(msg);
+
+ if ((ehdr = ber_printf_elements(ehdr, "{iixi}", msg->sm_msgid,
+ msg->sm_max_msg_size, &f, sizeof (f),
+ msg->sm_secmodel)) == NULL)
+ return -1;
+
+ /* XXX currently only USM supported */
+ if ((ehdr = usm_encode(msg, ehdr)) == NULL)
+ return -1;
+ } else {
+ if ((ehdr = ber_add_string(ehdr, msg->sm_community)) == NULL)
+ return -1;
+ }
+
+ pdu = epdu = ber_add_sequence(NULL);
+ if (msg->sm_version == SNMP_V3) {
+ if ((epdu = ber_printf_elements(epdu, "xs{", env->sc_engineid,
+ env->sc_engineid_len, msg->sm_ctxname)) == NULL) {
+ ber_free_elements(pdu);
+ return -1;
+ }
+ }
+
+ if (!ber_printf_elements(epdu, "tiii{e}.", BER_CLASS_CONTEXT,
+ msg->sm_context, msg->sm_request,
+ msg->sm_error, msg->sm_errorindex,
+ msg->sm_varbindresp)) {
+ ber_free_elements(pdu);
+ return -1;
+ }
+
+ if (MSG_HAS_PRIV(msg))
+ pdu = usm_encrypt(msg, pdu);
+ ber_link_elements(ehdr, pdu);
+
+#ifdef DEBUG
+ fprintf(stderr, "resp msg:\n");
+ snmpe_debug_elements(msg->sm_resp);
+#endif
+ return 0;
}
diff --git a/usr.sbin/snmpd/usm.c b/usr.sbin/snmpd/usm.c
new file mode 100644
index 00000000000..643fcf537d9
--- /dev/null
+++ b/usr.sbin/snmpd/usm.c
@@ -0,0 +1,658 @@
+/* $OpenBSD: usm.c,v 1.1 2012/09/17 16:30:35 reyk Exp $ */
+
+/*
+ * Copyright (c) 2012 GeNUA mbH
+ *
+ * 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/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#ifdef DEBUG
+#include <assert.h>
+#endif
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+#include "snmpd.h"
+#include "mib.h"
+
+extern struct snmpd *env;
+
+SLIST_HEAD(, usmuser) usmuserlist;
+
+const EVP_MD *usm_get_md(enum usmauth);
+const EVP_CIPHER *usm_get_cipher(enum usmpriv);
+void usm_cb_digest(void *, size_t);
+int usm_valid_digest(struct snmp_message *, off_t, char *,
+ size_t);
+struct ber_element *usm_decrypt(struct snmp_message *,
+ struct ber_element *);
+ssize_t usm_crypt(struct snmp_message *, u_char *, int,
+ u_char *, int);
+char *usm_passwd2key(const EVP_MD *, char *, int *);
+
+void
+usm_generate_keys(void)
+{
+ struct usmuser *up;
+ const EVP_MD *md;
+ char *key;
+ int len;
+
+ SLIST_FOREACH(up, &usmuserlist, uu_next) {
+ if ((md = usm_get_md(up->uu_auth)) == NULL)
+ continue;
+
+ /* convert auth password to key */
+ len = 0;
+ key = usm_passwd2key(md, up->uu_authkey, &len);
+ free(up->uu_authkey);
+ up->uu_authkey = key;
+ up->uu_authkeylen = len;
+
+ /* optionally convert privacy password to key */
+ if (up->uu_priv != PRIV_NONE) {
+ arc4random_buf(&up->uu_salt, sizeof (up->uu_salt));
+
+ len = SNMP_CIPHER_KEYLEN;
+ key = usm_passwd2key(md, up->uu_privkey, &len);
+ free(up->uu_privkey);
+ up->uu_privkey = key;
+ }
+ }
+ return;
+}
+
+const EVP_MD *
+usm_get_md(enum usmauth ua)
+{
+ switch (ua) {
+ case AUTH_MD5:
+ return EVP_md5();
+ case AUTH_SHA1:
+ return EVP_sha1();
+ case AUTH_NONE:
+ default:
+ return NULL;
+ }
+}
+
+const EVP_CIPHER *
+usm_get_cipher(enum usmpriv up)
+{
+ switch (up) {
+ case PRIV_DES:
+ return EVP_des_cbc();
+ case PRIV_AES:
+ return EVP_aes_128_cfb128();
+ case PRIV_NONE:
+ default:
+ return NULL;
+ }
+}
+
+struct usmuser *
+usm_newuser(char *name, const char **errp)
+{
+ struct usmuser *up = usm_finduser(name);
+ if (up != NULL) {
+ *errp = "user redefined";
+ return NULL;
+ }
+ if ((up = calloc(1, sizeof (*up))) == NULL)
+ fatal("usm");
+ up->uu_name = name;
+ SLIST_INSERT_HEAD(&usmuserlist, up, uu_next);
+ return up;
+}
+
+struct usmuser *
+usm_finduser(char *name)
+{
+ struct usmuser *up;
+
+ SLIST_FOREACH(up, &usmuserlist, uu_next) {
+ if (!strcmp(up->uu_name, name))
+ return up;
+ }
+ return NULL;
+}
+
+int
+usm_checkuser(struct usmuser *up, const char **errp)
+{
+ char *auth, *priv;
+
+ if (up->uu_auth != AUTH_NONE && up->uu_authkey == NULL) {
+ *errp = "missing auth passphrase";
+ goto fail;
+ } else if (up->uu_auth == AUTH_NONE && up->uu_authkey != NULL)
+ up->uu_auth = AUTH_DEFAULT;
+
+ if (up->uu_priv != PRIV_NONE && up->uu_privkey == NULL) {
+ *errp = "missing priv passphrase";
+ goto fail;
+ } else if (up->uu_priv == PRIV_NONE && up->uu_privkey != NULL)
+ up->uu_priv = PRIV_DEFAULT;
+
+ if (up->uu_auth == AUTH_NONE && up->uu_priv != PRIV_NONE) {
+ /* Standard prohibits noAuthPriv */
+ *errp = "auth is mandatory with enc";
+ goto fail;
+ }
+
+ switch (up->uu_auth) {
+ case AUTH_NONE:
+ auth = "none";
+ break;
+ case AUTH_MD5:
+ auth = "HMAC-MD5-96";
+ break;
+ case AUTH_SHA1:
+ auth = "HMAC-SHA-96";
+ break;
+ }
+
+ switch (up->uu_priv) {
+ case PRIV_NONE:
+ priv = "none";
+ break;
+ case PRIV_DES:
+ priv = "CBC-DES";
+ break;
+ case PRIV_AES:
+ priv = "CFB128-AES-128";
+ break;
+ }
+ log_debug("USM user '%s', auth %s, enc %s", up->uu_name, auth, priv);
+ return 0;
+
+fail:
+ free(up->uu_name);
+ free(up->uu_authkey);
+ free(up->uu_privkey);
+ SLIST_REMOVE(&usmuserlist, up, usmuser, uu_next);
+ free(up);
+ return -1;
+}
+
+struct ber_element *
+usm_decode(struct snmp_message *msg, struct ber_element *elm, const char **errp)
+{
+ struct snmp_stats *stats = &env->sc_stats;
+ off_t offs, offs2;
+ char *usmparams;
+ size_t len;
+ size_t enginelen, userlen, digestlen, saltlen;
+ struct ber ber;
+ struct ber_element *usm = NULL, *next = NULL, *decr;
+ char *engineid;
+ char *user;
+ char *digest, *salt;
+ u_long now;
+ long long engine_boots, engine_time;
+
+ bzero(&ber, sizeof(ber));
+ offs = ber_getpos(elm);
+
+ if (ber_get_nstring(elm, (void *)&usmparams, &len) < 0) {
+ *errp = "cannot decode security params";
+ goto done;
+ }
+
+ ber.fd = -1;
+ ber_set_readbuf(&ber, usmparams, len);
+ usm = ber_read_elements(&ber, NULL);
+ if (usm == NULL) {
+ *errp = "cannot decode security params";
+ goto done;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "decode USM parameters:\n");
+ snmpe_debug_elements(usm);
+#endif
+
+ if (ber_scanf_elements(usm, "{xiixpxx", &engineid, &enginelen,
+ &engine_boots, &engine_time, &user, &userlen, &offs2,
+ &digest, &digestlen, &salt, &saltlen) != 0) {
+ *errp = "cannot decode USM params";
+ goto done;
+ }
+
+ log_debug("USM: engineid '%s', engine boots %lld, engine time %lld, "
+ "user '%s'", tohexstr(engineid, enginelen), engine_boots,
+ engine_time, user);
+
+ if (enginelen > SNMPD_MAXENGINEIDLEN ||
+ userlen > SNMPD_MAXUSERNAMELEN ||
+ (digestlen != (MSG_HAS_AUTH(msg) ? SNMP_USM_DIGESTLEN : 0)) ||
+ (saltlen != (MSG_HAS_PRIV(msg) ? SNMP_USM_SALTLEN : 0))) {
+ *errp = "bad field length";
+ goto done;
+ }
+
+ if (enginelen != env->sc_engineid_len ||
+ memcmp(engineid, env->sc_engineid, enginelen) != 0) {
+ *errp = "unknown engine id";
+ msg->sm_usmerr = OIDVAL_usmErrEngineId;
+ stats->snmp_usmnosuchengine++;
+ goto done;
+ }
+
+ if (engine_boots != 0LL && engine_time != 0LL) {
+ now = snmpd_engine_time();
+ if (engine_boots != env->sc_engine_boots ||
+ engine_time < (now - SNMP_MAX_TIMEWINDOW) ||
+ engine_time > (now + SNMP_MAX_TIMEWINDOW)) {
+ *errp = "out of time window";
+ msg->sm_usmerr = MIB_usmStatsNotInTimeWindow;
+ stats->snmp_usmtimewindow++;
+ goto done;
+ }
+ }
+
+ msg->sm_engine_boots = (u_int32_t)engine_boots;
+ msg->sm_engine_time = (u_int32_t)engine_time;
+
+ memcpy(msg->sm_username, user, userlen);
+ msg->sm_username[userlen] = '\0';
+ if (MSG_SECLEVEL(msg) > 0) {
+ msg->sm_user = usm_finduser(msg->sm_username);
+ if (msg->sm_user == NULL) {
+ *errp = "no such user";
+ msg->sm_usmerr = OIDVAL_usmErrUserName;
+ stats->snmp_usmnosuchuser++;
+ goto done;
+ }
+ }
+
+ /*
+ * offs is the offset of the USM string within the serialized msg
+ * and offs2 the offset of the digest within the USM string.
+ */
+ if (!usm_valid_digest(msg, offs + offs2, digest, digestlen)) {
+ *errp = "bad msg digest";
+ msg->sm_usmerr = OIDVAL_usmErrDigest;
+ stats->snmp_usmwrongdigest++;
+ goto done;
+ }
+
+ if (MSG_HAS_PRIV(msg)) {
+ memcpy(msg->sm_salt, salt, saltlen);
+ if ((decr = usm_decrypt(msg, elm->be_next)) == NULL) {
+ *errp = "cannot decrypt msg";
+ msg->sm_usmerr = OIDVAL_usmErrDecrypt;
+ stats->snmp_usmdecrypterr++;
+ goto done;
+ }
+ ber_replace_elements(elm, decr);
+ }
+ next = elm->be_next;
+
+done:
+ ber_free(&ber);
+ if (usm != NULL)
+ ber_free_elements(usm);
+ return next;
+}
+
+struct ber_element *
+usm_encode(struct snmp_message *msg, struct ber_element *e)
+{
+ struct ber ber;
+ struct ber_element *usm, *a, *res = NULL;
+ void *ptr;
+ char digest[SNMP_USM_DIGESTLEN];
+ size_t digestlen, saltlen, len;
+
+ msg->sm_digest_offs = 0;
+ bzero(&ber, sizeof(ber));
+ ber.fd = -1;
+
+ usm = ber_add_sequence(NULL);
+
+ if (MSG_HAS_AUTH(msg)) {
+ /*
+ * Fill in enough zeroes and remember the position within the
+ * messages. The digest will be calculated once the message
+ * is complete.
+ */
+#ifdef DEBUG
+ assert(msg->sm_user != NULL);
+#endif
+ bzero(digest, sizeof(digest));
+ digestlen = sizeof(digest);
+ } else
+ digestlen = 0;
+
+ if (MSG_HAS_PRIV(msg)) {
+#ifdef DEBUG
+ assert(msg->sm_user != NULL);
+#endif
+ ++(msg->sm_user->uu_salt);
+ memcpy(msg->sm_salt, &msg->sm_user->uu_salt,
+ sizeof (msg->sm_salt));
+ saltlen = sizeof (msg->sm_salt);
+ } else
+ saltlen = 0;
+
+ msg->sm_engine_boots = (u_int32_t)env->sc_engine_boots;
+ msg->sm_engine_time = (u_int32_t)snmpd_engine_time();
+ if ((a = ber_printf_elements(usm, "xdds",
+ env->sc_engineid, env->sc_engineid_len, msg->sm_engine_boots,
+ msg->sm_engine_time, msg->sm_username)) == NULL)
+ goto done;
+
+ if ((a = ber_add_nstring(a, digest, digestlen)) == NULL)
+ goto done;
+ if (digestlen > 0)
+ ber_set_writecallback(a, usm_cb_digest, msg);
+
+ if ((a = ber_add_nstring(a, msg->sm_salt, saltlen)) == NULL)
+ goto done;
+
+#ifdef DEBUG
+ fprintf(stderr, "encode USM parameters:\n");
+ snmpe_debug_elements(usm);
+#endif
+ len = ber_write_elements(&ber, usm);
+ if (ber_get_writebuf(&ber, &ptr) > 0) {
+ res = ber_add_nstring(e, (char *)ptr, len);
+ if (digestlen > 0)
+ ber_set_writecallback(res, usm_cb_digest, msg);
+ }
+
+done:
+ ber_free(&ber);
+ ber_free_elements(usm);
+ return res;
+}
+
+void
+usm_cb_digest(void *arg, size_t offs)
+{
+ struct snmp_message *msg = arg;
+ msg->sm_digest_offs += offs;
+}
+
+struct ber_element *
+usm_encrypt(struct snmp_message *msg, struct ber_element *pdu)
+{
+ struct ber ber;
+ struct ber_element *encrpdu = NULL;
+ void *ptr;
+ int len;
+ ssize_t elen;
+ u_char encbuf[READ_BUF_SIZE];
+
+ if (!MSG_HAS_PRIV(msg))
+ return pdu;
+
+ bzero(&ber, sizeof(ber));
+ ber.fd = -1;
+
+#ifdef DEBUG
+ fprintf(stderr, "encrypted PDU:\n");
+ snmpe_debug_elements(pdu);
+#endif
+
+ len = ber_write_elements(&ber, pdu);
+ if (ber_get_writebuf(&ber, &ptr) > 0) {
+ elen = usm_crypt(msg, ptr, len, encbuf, 1);
+ if (elen > 0)
+ encrpdu = ber_add_nstring(NULL, (char *)encbuf, elen);
+ }
+
+ ber_free(&ber);
+ ber_free_elements(pdu);
+ return encrpdu;
+}
+
+/*
+ * Calculate message digest and replace within message
+ */
+void
+usm_finalize_digest(struct snmp_message *msg, char *buf, ssize_t len)
+{
+ const EVP_MD *md;
+ u_char digest[EVP_MAX_MD_SIZE];
+ unsigned hlen;
+
+ if (msg->sm_resp == NULL ||
+ !MSG_HAS_AUTH(msg) ||
+ msg->sm_user == NULL ||
+ msg->sm_digest_offs == 0 ||
+ len <= 0)
+ return;
+ bzero(digest, SNMP_USM_DIGESTLEN);
+#ifdef DEBUG
+ assert(msg->sm_digest_offs + SNMP_USM_DIGESTLEN <= (size_t)len);
+ assert(!memcmp(buf + msg->sm_digest_offs, digest, SNMP_USM_DIGESTLEN));
+#endif
+
+ if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL)
+ return;
+
+ HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen,
+ (u_char*)buf, (size_t)len, digest, &hlen);
+
+ memcpy(buf + msg->sm_digest_offs, digest, SNMP_USM_DIGESTLEN);
+ return;
+}
+
+void
+usm_make_report(struct snmp_message *msg)
+{
+ struct ber_oid usmstat = OID(MIB_usmStats, 0, 0);
+
+ /* Always send report in clear-text */
+ msg->sm_flags = 0;
+ msg->sm_context = SNMP_C_REPORT;
+ msg->sm_username[0] = '\0';
+ usmstat.bo_id[OIDIDX_usmStats] = msg->sm_usmerr;
+ usmstat.bo_n = OIDIDX_usmStats + 2;
+ if (msg->sm_varbindresp != NULL)
+ ber_free_elements(msg->sm_varbindresp);
+ msg->sm_varbindresp = ber_add_sequence(NULL);
+ mps_getreq(msg->sm_varbindresp, &usmstat, msg->sm_version);
+ return;
+}
+
+int
+usm_valid_digest(struct snmp_message *msg, off_t offs,
+ char *digest, size_t digestlen)
+{
+ const EVP_MD *md;
+ u_char exp_digest[EVP_MAX_MD_SIZE];
+ unsigned hlen;
+
+ if (!MSG_HAS_AUTH(msg) || msg->sm_user == NULL)
+ return 1;
+
+ if (digestlen != SNMP_USM_DIGESTLEN)
+ return 0;
+
+#ifdef DEBUG
+ assert(offs + digestlen <= msg->sm_datalen);
+ assert(bcmp(&msg->sm_data[offs], digest, digestlen) == 0);
+#endif
+
+ /* Ignore provided digest if user has no auth passphrase */
+ if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL)
+ return 1;
+
+ memset(&msg->sm_data[offs], 0, digestlen);
+ HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen,
+ msg->sm_data, msg->sm_datalen, exp_digest, &hlen);
+ /* we don't bother to restore the original message */
+
+ if (hlen < digestlen)
+ return 0;
+
+ return memcmp(digest, exp_digest, digestlen) == 0;
+}
+
+struct ber_element *
+usm_decrypt(struct snmp_message *msg, struct ber_element *encr)
+{
+ u_char *privstr;
+ size_t privlen;
+ u_char buf[READ_BUF_SIZE];
+ struct ber ber;
+ struct ber_element *scoped_pdu = NULL;
+ ssize_t scoped_pdu_len;
+
+ if (ber_get_nstring(encr, (void *)&privstr, &privlen) < 0)
+ return NULL;
+
+ scoped_pdu_len = usm_crypt(msg, privstr, (int)privlen, buf, 0);
+ if (scoped_pdu_len < 0)
+ return NULL;
+
+ bzero(&ber, sizeof(ber));
+ ber.fd = -1;
+ ber_set_readbuf(&ber, buf, scoped_pdu_len);
+ scoped_pdu = ber_read_elements(&ber, NULL);
+
+#ifdef DEBUG
+ if (scoped_pdu != NULL) {
+ fprintf(stderr, "decrypted scoped PDU:\n");
+ snmpe_debug_elements(scoped_pdu);
+ }
+#endif
+
+ ber_free(&ber);
+ return scoped_pdu;
+}
+
+ssize_t
+usm_crypt(struct snmp_message *msg, u_char *inbuf, int inlen, u_char *outbuf,
+ int do_encrypt)
+{
+ const EVP_CIPHER *cipher;
+ EVP_CIPHER_CTX ctx;
+ u_char *privkey;
+ int i;
+ u_char iv[EVP_MAX_IV_LENGTH];
+ int len, len2;
+ int rv;
+ u_int32_t ivv;
+
+ if ((cipher = usm_get_cipher(msg->sm_user->uu_priv)) == NULL)
+ return -1;
+
+ privkey = (u_char *)msg->sm_user->uu_privkey;
+#ifdef DEBUG
+ assert(privkey != NULL);
+#endif
+ switch (msg->sm_user->uu_priv) {
+ case PRIV_DES:
+ /* RFC3414, chap 8.1.1.1. */
+ for (i = 0; i < 8; i++)
+ iv[i] = msg->sm_salt[i] ^ privkey[SNMP_USM_SALTLEN + i];
+ break;
+ case PRIV_AES:
+ /* RFC3826, chap 3.1.2.1. */
+ ivv = htobe32(msg->sm_engine_boots);
+ memcpy(iv, &ivv, sizeof (ivv));
+ ivv = htobe32(msg->sm_engine_time);
+ memcpy(iv + sizeof (ivv), &ivv, sizeof (ivv));
+ memcpy(iv + 2 * sizeof (ivv), msg->sm_salt, SNMP_USM_SALTLEN);
+ break;
+ default:
+ return -1;
+ }
+
+ if (!EVP_CipherInit(&ctx, cipher, privkey, iv, do_encrypt))
+ return -1;
+
+ if (!do_encrypt)
+ EVP_CIPHER_CTX_set_padding(&ctx, 0);
+
+ if (EVP_CipherUpdate(&ctx, outbuf, &len, inbuf, inlen) &&
+ EVP_CipherFinal(&ctx, outbuf + len, &len2))
+ rv = len + len2;
+ else
+ rv = -1;
+
+ EVP_CIPHER_CTX_cleanup(&ctx);
+ return rv;
+}
+
+/*
+ * RFC3414, Password to Key Algorithm
+ */
+char *
+usm_passwd2key(const EVP_MD *md, char *passwd, int *maxlen)
+{
+ EVP_MD_CTX ctx;
+ int i, count;
+ u_char *pw, *c;
+ u_char pwbuf[2 * EVP_MAX_MD_SIZE + SNMPD_MAXENGINEIDLEN];
+ u_char keybuf[EVP_MAX_MD_SIZE];
+ unsigned dlen;
+ char *key;
+
+ EVP_DigestInit(&ctx, md);
+ pw = (u_char *)passwd;
+ for (count = 0; count < 1048576; count += 64) {
+ c = pwbuf;
+ for (i = 0; i < 64; i++) {
+ if (*pw == '\0')
+ pw = (u_char *)passwd;
+ *c++ = *pw++;
+ }
+ EVP_DigestUpdate(&ctx, pwbuf, 64);
+ }
+ EVP_DigestFinal(&ctx, keybuf, &dlen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ /* Localize the key */
+#ifdef DEBUG
+ assert(env->sc_engineid_len <= SNMPD_MAXENGINEIDLEN);
+#endif
+ memcpy(pwbuf, keybuf, dlen);
+ memcpy(pwbuf + dlen, env->sc_engineid, env->sc_engineid_len);
+ memcpy(pwbuf + dlen + env->sc_engineid_len, keybuf, dlen);
+
+ EVP_DigestInit(&ctx, md);
+ EVP_DigestUpdate(&ctx, pwbuf, 2 * dlen + env->sc_engineid_len);
+ EVP_DigestFinal(&ctx, keybuf, &dlen);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ if (*maxlen > 0 && dlen > (unsigned)*maxlen)
+ dlen = (unsigned)*maxlen;
+ if ((key = malloc(dlen)) == NULL)
+ fatal("key");
+ memcpy(key, keybuf, dlen);
+ *maxlen = (int)dlen;
+ return key;
+}