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;
+}

Reply via email to