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

Reply via email to