On Tue, 17 Jul 2012 11:21:04 +0200, Gerhard Roth <gerhard_r...@genua.de> wrote:
Hi all,
below you'll find a patch that adds basic SNMPv3 support to OpenBSD's
snmpd(8). When I say "basic" that's because of some limitations:
- Traps are still sent via SNMPv2 protocol. They can neither be
authenticated nor encrypted.
- Transport mode is still UDP. Not additional transport subsystems
were added.
- Only the User-based Security Model (USM, RFC3414) is supported.
View-Based Access Control (VACM, RFC3415) is not included.
Just to provide you a little background, I'll explain some details
below.
Three security levels are defined in RFC3411:
1) noAuthNoPriv: no authentication, no encryption
2) authNoPriv: with authentication, without encryption
3) authPriv: with authentication, with encryption
There is a new keyword 'seclevel' in snmpd.conf(5) that allows to
define the minimum security level required by snmpd(8). Any requirement
higher than noAuthNoPriv will disable SNMPv2 support.
The USM offers:
- Verification of message contents and authentication of the sender
USM adds a HMAC to the SNMP message. The HMAC is calculated over
the whole message with the HMAC portion set to zeroes.
According to RFC3414 the defined HMAC algorithms are HMAC-MD5-96
and HMAC-SHA-96. The key is derived from an authentication
passphrase.
- Encryption of the PDU
USM encypts only a part of the message, the scoped PDU while the
SNMP header remains plaintext. RFC3414 defines only CBC DES but
RFC3826 adds CFB128 AES 128 encryption (although this is not
part of STD62). The IV is derived from an encryption passphrase.
- Protection agains replay attacks
The non-authoritative SNMP engines have to synchronize their
clocks with the authoritative SNMP engine. RFC3414 demands
to reject any SNMPv3 message that has a timestamp that differs
more than 150 seconds from the local clock.
The USM users together with their HMAC and encryption passphrases
have to be defined in snmpd.conf(5). The code already supports multiple
users, though without VACM there's not much sense to it.
Gerhard
[demime 1.01d removed an attachment of type application/octet-stream which had
a name of snmpv3.patch]
Sorry for using an attachment. Here's the patch inlined:
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/Makefile,v
retrieving revision 1.8
diff -u -p -r1.8 Makefile
--- Makefile 20 Mar 2012 03:01:26 -0000 1.8
+++ Makefile 17 Jul 2012 06:49:17 -0000
@@ -4,9 +4,9 @@ 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
Index: ber.3
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.3,v
retrieving revision 1.9
diff -u -p -r1.9 ber.3
--- ber.3 25 Feb 2010 09:59:55 -0000 1.9
+++ ber.3 17 Jul 2012 06:44:44 -0000
@@ -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_callback
.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_callback" "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_callback ,
.Fn ber_free
.Sh RETURN VALUES
Upon successful completion
Index: ber.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.c,v
retrieving revision 1.23
diff -u -p -r1.23 ber.c
--- ber.c 20 Sep 2010 08:30:13 -0000 1.23
+++ ber.c 17 Jul 2012 06:44:44 -0000
@@ -618,6 +618,7 @@ ber_scanf_elements(struct ber_element *b
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 *b
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 *b
goto fail;
ber = parent[level--];
ret++;
- continue;
+ break;
default:
goto fail;
}
@@ -808,6 +814,12 @@ ber_read_elements(struct ber *ber, struc
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
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:
@@ -1068,6 +1082,7 @@ ber_read_element(struct ber *ber, struct
elm->be_type = type;
elm->be_len = len;
+ elm->be_offs = ber->offs; /* element position within stream */
elm->be_class = class;
if (elm->be_encoding == 0) {
@@ -1199,6 +1214,14 @@ ber_set_application(struct ber *b, unsig
}
void
+ber_set_callback(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 +1231,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 +1261,7 @@ ber_read(struct ber *ber, void *buf, siz
b += r;
remain -= r;
}
- return (b - (u_char *)buf);
+ r = b - (u_char *)buf;
+ ber->offs += r;
+ return r;
}
Index: ber.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.h,v
retrieving revision 1.7
diff -u -p -r1.7 ber.h
--- ber.h 3 Jan 2009 18:41:41 -0000 1.7
+++ ber.h 17 Jul 2012 06:44:44 -0000
@@ -22,8 +22,11 @@ struct ber_element {
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 +39,7 @@ struct ber_element {
struct ber {
int fd;
+ off_t offs;
u_char *br_wbuf;
u_char *br_wptr;
u_char *br_wend;
@@ -120,9 +124,12 @@ ssize_t ber_get_writebuf(struct ber *
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_callback(struct ber_element *,
+ void (*)(void *, size_t), void *);
void ber_free(struct ber *);
__END_DECLS
Index: mib.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mib.c,v
retrieving revision 1.56
diff -u -p -r1.56 mib.c
--- mib.c 8 Jul 2012 11:24:43 -0000 1.56
+++ mib.c 17 Jul 2012 07:26:15 -0000
@@ -330,6 +330,79 @@ mib_setsnmp(struct oid *oid, struct ber_
}
/*
+ * 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)
*/
@@ -722,8 +795,9 @@ mib_hrswrun(struct oid *oid, struct ber_
char *s;
/* Get and verify the current row index */
+ /* kinfo_proc() fails in case of SMALL_KERNEL */
if (kinfo_proc(o->bo_id[OIDIDX_hrSWRunEntry], &kinfo) == -1)
- return (-1);
+ return (1);
if (kinfo == NULL)
return (1);
@@ -3532,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);
Index: mib.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mib.h,v
retrieving revision 1.26
diff -u -p -r1.26 mib.h
--- mib.h 14 Jun 2012 17:31:32 -0000 1.26
+++ mib.h 17 Jul 2012 07:49:13 -0000
@@ -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
@@ -728,6 +754,23 @@
{ MIBDECL(linkUp) }, \
{ 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) }, \
Index: mps.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mps.c,v
retrieving revision 1.14
diff -u -p -r1.14 mps.c
--- mps.c 20 Sep 2010 08:56:16 -0000 1.14
+++ mps.c 17 Jul 2012 06:44:44 -0000
@@ -219,6 +219,7 @@ mps_getnextreq(struct ber_element *root,
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,
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,
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v
retrieving revision 1.20
diff -u -p -r1.20 parse.y
--- parse.y 21 Apr 2011 14:55:22 -0000 1.20
+++ parse.y 17 Jul 2012 06:44:44 -0000
@@ -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 *);
@@ -114,7 +115,7 @@ 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 USER AUTHPASS AUTHHMAC PRIVPASS PRIVCIPHER ERROR
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.string> hostcmn
@@ -236,6 +237,37 @@ main : LISTEN ON STRING {
else
conf->sc_rtfilter = 0;
}
+ | SECLEVEL STRING {
+ if (!strcmp($2, "noAuthNoPriv"))
+ conf->sc_min_seclevel = 0;
+ else if (!strcmp($2, "authNoPriv"))
+ conf->sc_min_seclevel = SNMP_MSGFLAG_AUTH;
+ else if (!strcmp($2, "authPriv"))
+ conf->sc_min_seclevel =
+ SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV;
+ else {
+ yyerror("syntax error, bad security level");
+ free($2);
+ YYERROR;
+ }
+ free($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 +399,41 @@ comma : /* empty */
| ','
;
+userspecs : /* empty */
+ | userspecs userspec
+ ;
+
+userspec : AUTHPASS STRING {
+ user->uu_authkey = $2;
+ }
+ | AUTHHMAC STRING {
+ if (!strcasecmp($2, "MD5"))
+ user->uu_auth = AUTH_MD5;
+ else if (!strcasecmp($2, "SHA"))
+ user->uu_auth = AUTH_SHA;
+ else {
+ yyerror("syntax error, bad auth hmac");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | PRIVPASS STRING {
+ user->uu_privkey = $2;
+ }
+ | PRIVCIPHER STRING {
+ if (!strcasecmp($2, "DES"))
+ user->uu_priv = PRIV_DES;
+ else if (!strcasecmp($2, "AES"))
+ user->uu_priv = PRIV_AES;
+ else {
+ yyerror("syntax error, bad priv cipher");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
%%
struct keywords {
@@ -399,10 +466,13 @@ lookup(char *s)
{
/* this has to be sorted always */
static const struct keywords keywords[] = {
+ { "authpass", AUTHPASS },
+ { "cipher", PRIVCIPHER },
{ "community", COMMUNITY },
{ "contact", CONTACT },
{ "description", DESCR },
{ "filter-routes", RTFILTER },
+ { "hmac", AUTHHMAC },
{ "include", INCLUDE },
{ "integer", INTEGER },
{ "listen", LISTEN },
@@ -410,13 +480,16 @@ lookup(char *s)
{ "name", NAME },
{ "oid", OBJECTID },
{ "on", ON },
+ { "privpass", PRIVPASS },
{ "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;
Index: snmp.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmp.h,v
retrieving revision 1.8
diff -u -p -r1.8 snmp.h
--- snmp.h 26 Nov 2009 17:32:47 -0000 1.8
+++ snmp.h 17 Jul 2012 06:44:44 -0000
@@ -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 */
Index: snmpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.c,v
retrieving revision 1.11
diff -u -p -r1.11 snmpd.c
--- snmpd.c 28 May 2012 20:55:40 -0000 1.11
+++ snmpd.c 17 Jul 2012 07:37:01 -0000
@@ -44,6 +44,7 @@ void snmpd_sig_handler(int, short, voi
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 +172,7 @@ main(int argc, char *argv[])
}
gettimeofday(&env->sc_starttime, NULL);
+ env->sc_engine_boots = 0;
log_info("startup");
@@ -183,6 +185,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 +343,42 @@ snmpd_socket_af(struct sockaddr_storage
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(30155);
+ memcpy(env->sc_engineid, &oid_enterprise, sizeof (oid_enterprise));
+ env->sc_engineid[0] |= 0x80;
+ env->sc_engineid_len = sizeof (oid_enterprise);
+ /* XXX alternatively configure engine id via snmpd.conf */
+ env->sc_engineid[(env->sc_engineid_len)++] = 128;
+ 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;
+}
Index: snmpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.conf.5,v
retrieving revision 1.17
diff -u -p -r1.17 snmpd.conf.5
--- snmpd.conf.5 24 Apr 2012 14:56:09 -0000 1.17
+++ snmpd.conf.5 17 Jul 2012 06:44:44 -0000
@@ -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,16 @@ reduced during bulk updates.
The default is
.Ic no .
.Pp
+.It Xo
+.Ic seclevel
+.Pq Ic noAuthNoPriv \*(Ba\ authNoPriv \*(Ba\ authPriv
+.Xc
+Specify the lowest accepted security level according to RFC3411. If the
+chosen value is different from
+.Ic noAuthNoPriv
+.Xr snmpd 8
+will accept only SNMP V3 requests.
+.Pp
.It Ic system contact Ar string
Specify the name or description of the system contact, typically a
name or an e-mail address.
@@ -169,6 +181,48 @@ The default community is specified by th
.Ic trap community
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 authpass Ar pass Nm hmac Ar hmac
+.Op Ic privpass Ar pass Ic cipher Ar cipher
+.Xc
+Defines a known user. The
+.Ic authpass
+keyword is used to specifiy the digest passphrase 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 MD5
+or
+.Ic AES .
+If omitted the default is
+.Ic MD5 .
+
+With
+.Ic privpass
+the passphrase used to encrypt and decrypt messages for privacy is defined.
+Without a
+.Ic privpass
+specification the user account will neither accept encrypted incoming
+messages nor will it encrypt outgoing messsages. The
+.Ar cipher
+can be either
+.Ic DES
+or
+.Ic AES
+and defaults to
+.Ic DES .
+
+Any user account that has privacy enabled requires authentication to
+be enabled, too.
.El
.Sh OID CONFIGURATION
It is possible to specify user-defined OIDs in the configuration file:
Index: snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.35
diff -u -p -r1.35 snmpd.h
--- snmpd.h 28 May 2012 20:55:40 -0000 1.35
+++ snmpd.h 17 Jul 2012 07:18:50 -0000
@@ -44,6 +44,14 @@
#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
@@ -238,13 +246,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;
+
+ /* V1, V2c */
char sm_community[SNMPD_MAXCOMMUNITYLEN];
- u_int sm_context;
+ int sm_context;
- struct ber_element *sm_header;
- struct ber_element *sm_headerend;
+ /* 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 +326,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 +348,33 @@ struct address {
};
TAILQ_HEAD(addresslist, address);
+enum usmauth {
+ AUTH_NONE = 0,
+ AUTH_MD5, /* HMAC-MD5-96, RFC3414 */
+ AUTH_SHA /* HMAC-SHA-96, RFC3414 */
+};
+
+enum usmpriv {
+ PRIV_NONE = 0,
+ PRIV_DES, /* CBC-DES, RFC3414 */
+ PRIV_AES /* CFB128-AES-128, RFC3826 */
+};
+
+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 +385,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 +394,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 +404,8 @@ struct snmpd {
int sc_ncpu;
int64_t *sc_cpustates;
int sc_rtfilter;
+
+ int sc_min_seclevel;
};
/* control.c */
@@ -382,6 +457,7 @@ struct kroute *kroute_getaddr(in_addr_t,
/* snmpe.c */
pid_t snmpe(struct snmpd *, int [2]);
void snmpe_debug_elements(struct ber_element *);
+char *tohexstr(u_int8_t *, int);
/* trap.c */
void trap_init(void);
@@ -449,5 +525,17 @@ void timer_init(void);
/* snmpd.c */
int snmpd_socket_af(struct sockaddr_storage *, in_port_t);
+u_long snmpd_engine_time(void);
+/* 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 */
Index: snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.28
diff -u -p -r1.28 snmpe.c
--- snmpe.c 20 Sep 2010 12:32:41 -0000 1.28
+++ snmpe.c 17 Jul 2012 07:44:13 -0000
@@ -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_pare
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 *a
{
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 *a
}
/* 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,76 @@ snmpe_recvmsg(int fd, short sig, void *a
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;
+}
+
+char *
+tohexstr(u_int8_t *str, int len)
+{
+#define MAXHEXSTRLEN 256
+#define HEXCHAR_PER_BYTE 2
+ static char hstr[HEXCHAR_PER_BYTE * MAXHEXSTRLEN + 1];
+ char *r = hstr;
+
+ if (len > MAXHEXSTRLEN)
+ len = MAXHEXSTRLEN; /* truncate */
+ while (len-- > 0)
+ r += snprintf(r, len * HEXCHAR_PER_BYTE,
+ "%0*x", HEXCHAR_PER_BYTE, *str++);
+ *r = '\0';
+ return hstr;
}
Index: usm.c
===================================================================
RCS file: usm.c
diff -N usm.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usm.c 17 Jul 2012 08:00:59 -0000
@@ -0,0 +1,644 @@
+/* $OpenBSD$ */
+
+/*
+ * 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>
+#include <assert.h>
+
+#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_SHA:
+ 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_MD5; /* default digest */
+
+ 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_DES; /* default cipher */
+
+ if (up->uu_auth == AUTH_NONE && up->uu_priv != PRIV_NONE) {
+ /* Standard prohibits noAuthPriv */
+ *errp = "auth is mandatory with priv";
+ goto fail;
+ }
+
+ switch (up->uu_auth) {
+ case AUTH_NONE:
+ auth = "noauth";
+ break;
+ case AUTH_MD5:
+ auth = "HMAC-MD5-96";
+ break;
+ case AUTH_SHA:
+ auth = "HMAC-SHA-96";
+ break;
+ }
+
+ switch (up->uu_priv) {
+ case PRIV_NONE:
+ priv = "nopriv";
+ break;
+ case PRIV_DES:
+ priv = "CBC-DES";
+ break;
+ case PRIV_AES:
+ priv = "CFB128-AES-128";
+ break;
+ }
+ log_debug("USM user '%s', auth %s, priv %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 msgj
+ * 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 one the message
+ * is complete.
+ */
+ assert(msg->sm_user != NULL);
+ bzero(digest, sizeof(digest));
+ digestlen = sizeof(digest);
+ } else
+ digestlen = 0;
+
+ if (MSG_HAS_PRIV(msg)) {
+ assert(msg->sm_user != NULL);
+ ++(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_callback(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_callback(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);
+ assert(msg->sm_digest_offs + SNMP_USM_DIGESTLEN <= (size_t)len);
+ assert(!memcmp(buf + msg->sm_digest_offs, digest, SNMP_USM_DIGESTLEN));
+
+ 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;
+
+ assert(offs + digestlen <= msg->sm_datalen);
+ assert(bcmp(&msg->sm_data[offs], digest, digestlen) == 0);
+
+ /* 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;
+ assert(privkey != NULL);
+ 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 */
+ assert(env->sc_engineid_len <= SNMPD_MAXENGINEIDLEN);
+ 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;
+}