Hi Reyk,
On Wed, 18 Jul 2012 21:42:34 +0200, Reyk Floeter <r...@openbsd.org> wrote:
How about:
noAuthNoPriv -> none
authNoPriv -> auth
authPriv -> encr
Is there a better alternative for "encr"? Maybe just "enc" (I know it
would complicate the grammar because it's a reserved keyword) or
something more abstract like "high" or "all"?
the yacc grammar was exactly why I chose "encr" instead of "enc". ;)
I changed it to "enc" now. But now "none", "auth" and "enc" are
reserved words. Hope nobody uses them as community string or
elsewhere.
Also noticed that you wanted me to keep the "auth" keyword in front
of "hmac-*". Fixed this one, too.
Find the updated patch below.
Gerhard
Index: usr.sbin/snmpd/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/Makefile,v
retrieving revision 1.8
diff -u -p -r1.8 Makefile
--- usr.sbin/snmpd/Makefile 20 Mar 2012 03:01:26 -0000 1.8
+++ usr.sbin/snmpd/Makefile 18 Jul 2012 14:03:06 -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: usr.sbin/snmpd/ber.3
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.3,v
retrieving revision 1.9
diff -u -p -r1.9 ber.3
--- usr.sbin/snmpd/ber.3 25 Feb 2010 09:59:55 -0000 1.9
+++ usr.sbin/snmpd/ber.3 18 Jul 2012 14:03:06 -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_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
Index: usr.sbin/snmpd/ber.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.c,v
retrieving revision 1.23
diff -u -p -r1.23 ber.c
--- usr.sbin/snmpd/ber.c 20 Sep 2010 08:30:13 -0000 1.23
+++ usr.sbin/snmpd/ber.c 18 Jul 2012 14:03:06 -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->br_offs; /* element position within stream */
elm->be_class = class;
if (elm->be_encoding == 0) {
@@ -1199,6 +1214,15 @@ ber_set_application(struct ber *b, unsig
}
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 +1232,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 +1262,7 @@ ber_read(struct ber *ber, void *buf, siz
b += r;
remain -= r;
}
- return (b - (u_char *)buf);
+ r = b - (u_char *)buf;
+ ber->br_offs += r;
+ return r;
}
Index: usr.sbin/snmpd/ber.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/ber.h,v
retrieving revision 1.7
diff -u -p -r1.7 ber.h
--- usr.sbin/snmpd/ber.h 3 Jan 2009 18:41:41 -0000 1.7
+++ usr.sbin/snmpd/ber.h 18 Jul 2012 14:03:06 -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 br_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_writecallback(struct ber_element *,
+ void (*)(void *, size_t), void *);
void ber_free(struct ber *);
__END_DECLS
Index: usr.sbin/snmpd/mib.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mib.c,v
retrieving revision 1.56
diff -u -p -r1.56 mib.c
--- usr.sbin/snmpd/mib.c 8 Jul 2012 11:24:43 -0000 1.56
+++ usr.sbin/snmpd/mib.c 18 Jul 2012 14:03:06 -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)
*/
@@ -723,7 +796,7 @@ mib_hrswrun(struct oid *oid, struct ber_
/* 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);
@@ -3532,6 +3605,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: usr.sbin/snmpd/mib.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mib.h,v
retrieving revision 1.26
diff -u -p -r1.26 mib.h
--- usr.sbin/snmpd/mib.h 14 Jun 2012 17:31:32 -0000 1.26
+++ usr.sbin/snmpd/mib.h 18 Jul 2012 14:03:06 -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
@@ -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
@@ -728,6 +755,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: usr.sbin/snmpd/mps.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mps.c,v
retrieving revision 1.14
diff -u -p -r1.14 mps.c
--- usr.sbin/snmpd/mps.c 20 Sep 2010 08:56:16 -0000 1.14
+++ usr.sbin/snmpd/mps.c 18 Jul 2012 14:03:06 -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: usr.sbin/snmpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v
retrieving revision 1.20
diff -u -p -r1.20 parse.y
--- usr.sbin/snmpd/parse.y 21 Apr 2011 14:55:22 -0000 1.20
+++ usr.sbin/snmpd/parse.y 19 Jul 2012 09:06:50 -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 *);
@@ -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;
Index: usr.sbin/snmpd/snmp.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmp.h,v
retrieving revision 1.8
diff -u -p -r1.8 snmp.h
--- usr.sbin/snmpd/snmp.h 26 Nov 2009 17:32:47 -0000 1.8
+++ usr.sbin/snmpd/snmp.h 18 Jul 2012 14:03:06 -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: usr.sbin/snmpd/snmpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.c,v
retrieving revision 1.11
diff -u -p -r1.11 snmpd.c
--- usr.sbin/snmpd/snmpd.c 28 May 2012 20:55:40 -0000 1.11
+++ usr.sbin/snmpd/snmpd.c 18 Jul 2012 14:03:06 -0000
@@ -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, 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 +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
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;
+}
Index: usr.sbin/snmpd/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
--- usr.sbin/snmpd/snmpd.conf.5 24 Apr 2012 14:56:09 -0000 1.17
+++ usr.sbin/snmpd/snmpd.conf.5 19 Jul 2012 09:06:50 -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,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.
@@ -169,6 +198,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 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:
Index: usr.sbin/snmpd/snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.35
diff -u -p -r1.35 snmpd.h
--- usr.sbin/snmpd/snmpd.h 28 May 2012 20:55:40 -0000 1.35
+++ usr.sbin/snmpd/snmpd.h 18 Jul 2012 14:03:06 -0000
@@ -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;
+
+ /* 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 +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 */
Index: usr.sbin/snmpd/snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.28
diff -u -p -r1.28 snmpe.c
--- usr.sbin/snmpd/snmpe.c 20 Sep 2010 12:32:41 -0000 1.28
+++ usr.sbin/snmpd/snmpe.c 18 Jul 2012 14:03:06 -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,59 @@ 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;
}
Index: usr.sbin/snmpd/usm.c
===================================================================
RCS file: usr.sbin/snmpd/usm.c
diff -N usr.sbin/snmpd/usm.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/snmpd/usm.c 18 Jul 2012 14:03:06 -0000
@@ -0,0 +1,658 @@
+/* $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>
+#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;
+}