This diff adds support for authPriv.

diff --git a/snmp.1 b/snmp.1
index e810560..fe283a5 100644
--- a/snmp.1
+++ b/snmp.1
@@ -28,6 +28,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -36,6 +37,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Ar agent
 .Ar oid ...
@@ -46,6 +49,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -54,6 +58,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm cIipt
 .Op Fl C Cm E Ar endoid
@@ -66,6 +72,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -74,6 +81,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm n Ns Ar nonrep Ns Cm r Ns Ar maxrep
 .Ar agent
@@ -85,6 +94,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -93,6 +103,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Op Fl C Cm cipn Ns Ar nonrep Ns Cm r Ns Ar maxrep
 .Ar agent
@@ -104,6 +116,7 @@
 .Op Fl c Ar community
 .Op Fl E Ar ctxengineid
 .Op Fl e Ar secengineid
+.Op Fl K Ar localpriv
 .Op Fl k Ar localauth
 .Op Fl l Ar seclevel
 .Op Fl n Ar ctxname
@@ -111,6 +124,8 @@
 .Op Fl t Ar timeout
 .Op Fl u Ar user
 .Op Fl v Ar version
+.Op Fl X Ar privpass
+.Op Fl x Ar cipher
 .Op Fl Z Ar boots , Ns Ar time
 .Ar agent uptime trapoid
 .Oo Ar varoid type value Oc ...
@@ -302,6 +317,14 @@ The snmpv3 context engine id.
 Most of the time this value can be safely ignored.
 This option is only used by
 .Fl v Cm 3 .
+.It Fl K Ar localpriv
+The localized privacy password for the user in hexadecimal format
+.Po
+optionally prefixed with a
+.Cm 0x
+.Pc .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl k Ar localauth
 The localized authentication password for the user in hexadecimal format
 .Po
@@ -313,14 +336,24 @@ This option is only used by
 .It Fl l Ar seclevel
 The security level.
 Values can be
-.Cm noAuthNoPriv Pq default
-or
+.Cm noAuthNoPriv Pq default ,
 .Cm authNoPriv
 .Po
 requires either
 .Fl A
 or
 .Fl k
+.Pc
+or
+.Cm authPriv
+.Po
+requires either
+.Fl X
+or
+.Fl K
+in addition to the
+.Cm authNoPriv
+requirements
 .Pc .
 This option is only used by
 .Fl v Cm 3 .
@@ -382,6 +415,22 @@ or
 .Cm 3 .
 Currently defaults to
 .Cm 2c .
+.It Fl X Ar privpass
+The privacy password for the user.
+This will be tansformed to
+.Ar localpriv .
+This option is only used by
+.Fl v Cm 3 .
+.It Fl x Ar cipher
+Sets the cipher
+.Pq privacy
+protocol.
+Options are
+.Cm DES
+and
+.Cm AES .
+This option is only used by
+.Fl v Cm 3 .
 .It Fl Z Ar boots , Ns Ar time
 Set the engine boots and engine time.
 Under normal circumstances this value is discovered via snmpv3 discovery and
diff --git a/snmp.c b/snmp.c
index 57b40e3..ba4dc3c 100644
--- a/snmp.c
+++ b/snmp.c
@@ -357,7 +357,7 @@ static char *
 snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
 {
        struct ber ber;
-       struct ber_element *message, *scopedpdu = NULL, *secparams;
+       struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
        ssize_t securitysize, ret;
        size_t secparamsoffset;
        char *securityparams = NULL, *buf, *packet = NULL;
@@ -400,6 +400,13 @@ snmp_package(struct snmp_agent *agent, struct ber_element 
*pdu, size_t *len)
                        ber_free_elements(scopedpdu);
                        goto fail;
                }
+               if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
+                       if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
+                           cookie)) == NULL)
+                               goto fail;
+                       ber_free_elements(scopedpdu);
+                       scopedpdu = encpdu;
+               }
                if (ber_printf_elements(message, "d{idxd}xe",
                    agent->version, msgid, 1472, &(agent->v3->level),
                    (size_t) 1, agent->v3->sec->model, securityparams,
@@ -449,8 +456,9 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t 
buflen)
        size_t msgflagslen, secparamslen;
        struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname;
        off_t secparamsoffset;
-       char *engineid;
-       size_t engineidlen;
+       char *encpdu, *engineid;
+       size_t encpdulen, engineidlen;
+       void *cookie = NULL;
 
        bzero(&ber, sizeof(ber));
        ber_set_application(&ber, smi_application);
@@ -482,9 +490,19 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, size_t 
buflen)
                if (msgflagslen != 1)
                        goto fail;
                if (agent->v3->sec->parseparams(agent, buf, buflen,
-                   secparamsoffset, secparams, secparamslen,
-                   msgflags[0]) == -1)
+                   secparamsoffset, secparams, secparamslen, msgflags[0],
+                   &cookie) == -1) {
+                       cookie = NULL;
                        goto fail;
+               }
+               if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
+                       if (ber_scanf_elements(scopedpdu, "x", &encpdu,
+                           &encpdulen) == -1)
+                               goto fail;
+                       if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
+                           encpdulen, cookie)) == NULL)
+                               goto fail;
+               }
                if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid,
                    &engineidlen, &ctxname) == -1)
                        goto fail;
@@ -502,11 +520,14 @@ snmp_unpackage(struct snmp_agent *agent, char *buf, 
size_t buflen)
                }
 
                ber_free_elements(message);
+               agent->v3->sec->freecookie(cookie);
                return pdu;
        }
        /* NOTREACHED */
 
 fail:
+       if (version == SNMP_V3)
+               agent->v3->sec->freecookie(cookie);
        ber_free_elements(message);
        return NULL;
 }
diff --git a/snmp.h b/snmp.h
index df20f0d..ce62119 100644
--- a/snmp.h
+++ b/snmp.h
@@ -114,9 +114,13 @@ struct snmp_sec {
        enum snmp_security_model model;
        int (*init)(struct snmp_agent *);
        char *(*genparams)(struct snmp_agent *, size_t *, void **);
+       struct ber_element *(*encpdu)(struct snmp_agent *,
+           struct ber_element *, void *);
        int (*finalparams)(struct snmp_agent *, char *, size_t, size_t, void *);
        int (*parseparams)(struct snmp_agent *, char *, size_t, off_t, char *,
-           size_t, uint8_t);
+           size_t, uint8_t, void **);
+       struct ber_element *(*decpdu)(struct snmp_agent *, char *, size_t,
+           void *);
        void (*free)(void *);
        void (*freecookie)(void *);
        void *data;
diff --git a/snmpc.c b/snmpc.c
index edaa50c..658ca82 100644
--- a/snmpc.c
+++ b/snmpc.c
@@ -42,7 +42,7 @@
 #include "snmp.h"
 #include "usm.h"
 
-#define GETOPT_COMMON          "A:a:c:E:e:k:l:n:O:r:t:u:v:Z:"
+#define GETOPT_COMMON          "A:a:c:E:e:K:k:l:n:O:r:t:u:v:X:x:Z:"
 
 int snmpc_get(int, char *[]);
 int snmpc_walk(int, char *[]);
@@ -98,11 +98,15 @@ int
 main(int argc, char *argv[])
 {
        const EVP_MD *md = NULL;
+       const EVP_CIPHER *cipher = NULL;
        struct snmp_sec *sec;
        char *user = NULL;
        enum usm_key_level authkeylevel;
        char *authkey = NULL;
        size_t authkeylen = 0;
+       enum usm_key_level privkeylevel;
+       char *privkey = NULL;
+       size_t privkeylen = 0;
        int seclevel = SNMP_MSGFLAG_REPORT;
        char *ctxname = NULL;
        char *ctxengineid = NULL, *secengineid = NULL;
@@ -193,6 +197,16 @@ main(int argc, char *argv[])
                                err(1, "-3e");
                        }
                        break;
+               case 'K':
+                       privkey = snmpc_hex2bin(optarg, &privkeylen);
+                       if (privkey == NULL) {
+                               if (errno == EINVAL)
+                                       errx(1, "Bad key value after "
+                                           "-3K flag.");
+                               errx(1, "-3K");
+                       }
+                       privkeylevel = USM_KEY_LOCALIZED;
+                               break;
                case 'k':
                        authkey = snmpc_hex2bin(optarg, &authkeylen);
                        if (authkey == NULL) {
@@ -208,6 +222,9 @@ main(int argc, char *argv[])
                        else if (strcmp(optarg, "authNoPriv") == 0)
                                seclevel = SNMP_MSGFLAG_AUTH |
                                    SNMP_MSGFLAG_REPORT;
+                       else if (strcmp(optarg, "authPriv") == 0)
+                               seclevel = SNMP_MSGFLAG_AUTH |
+                                   SNMP_MSGFLAG_PRIV | SNMP_MSGFLAG_REPORT;
                        else
                                errx(1, "Invalid security level specified "
                                    "after -l flag: %s", optarg);
@@ -369,6 +386,21 @@ main(int argc, char *argv[])
                                }
                        }
                        break;
+               case 'X':
+                       privkey = optarg;
+                       privkeylen = strlen(privkey);
+                       privkeylevel = USM_KEY_PASSWORD;
+                       break;
+               case 'x':
+                       if (strcasecmp(optarg, "DES") == 0)
+                               cipher = EVP_des_cbc();
+                       else if (strcasecmp(optarg, "AES") == 0)
+                               cipher = EVP_aes_128_cfb128();
+                       else
+                               errx(1, "Invalid privacy protocol "
+                                   "specified after -3x flag: %s",
+                                   optarg);
+                       break;
                case 'Z':
                        boots = strtoll(optarg, &strtolp, 10);
                        if (boots < 0 || strtolp == optarg || strtolp[0] != ',')
@@ -403,6 +435,15 @@ main(int argc, char *argv[])
                            authkeylevel) == -1)
                                err(1, "Can't set authkey");
                }
+               if (seclevel & SNMP_MSGFLAG_PRIV) {
+                       if (cipher == NULL)
+                               cipher = EVP_des_cbc();
+                       if (privkey == NULL)
+                               errx(1, "No privKey or privPassword specified");
+                       if (usm_setpriv(sec, cipher, privkey, privkeylen,
+                           privkeylevel) == -1)
+                               err(1, "Can't set authkey");
+               }
                if (secengineid != NULL) {
                        if (usm_setengineid(sec, secengineid,
                            secengineidlen) == -1)
@@ -1087,9 +1128,9 @@ usage(void)
                    snmp_app->name,
                    snmp_app->usecommonopt ?
                    " [-A authpass] [-a digest] [-c community] [-e 
secengineid]\n"
-                   "            [-E ctxengineid] [-k localauth] [-l seclevel] 
[-n ctxname]\n"
-                   "            [-O afnqvxSQ] [-r retries] [-t timeout] [-u 
user] [-v version]\n"
-                   "            [-Z boots,time]\n"
+                   "            [-E ctxengineid] [-K localpriv] [-k localauth] 
[-l seclevel]\n"
+                   "            [-n ctxname] [-O afnqvxSQ] [-r retries] [-t 
timeout] [-u user]\n"
+                   "            [-v version] [-X privpass] [-x cipher] [-Z 
boots,time]\n"
                    "            " : "",
                    snmp_app->usage == NULL ? "" : snmp_app->usage);
                exit(1);
diff --git a/usm.c b/usm.c
index df9a53d..ba2020c 100644
--- a/usm.c
+++ b/usm.c
@@ -44,6 +44,9 @@ struct usm_sec {
        enum usm_key_level authlevel;
        const EVP_MD *digest;
        char *authkey;
+       enum usm_key_level privlevel;
+       const EVP_CIPHER *cipher;
+       char *privkey;
        int bootsset;
        uint32_t boots;
        int timeset;
@@ -53,13 +56,21 @@ struct usm_sec {
 
 struct usm_cookie {
        size_t digestoffset;
+       long long salt;
+       uint32_t boots;
+       uint32_t time;
 };
 
 static int usm_doinit(struct snmp_agent *);
 static char *usm_genparams(struct snmp_agent *, size_t *, void **);
 static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void 
*);
+static struct ber_element *usm_encpdu(struct snmp_agent *agent,
+    struct ber_element *pdu, void *cookie);
+static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *,
+    char *, size_t, size_t *);
 static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
-    size_t, uint8_t);
+    size_t, uint8_t, void **);
+struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *);
 static void usm_digest_pos(void *, size_t);
 static void usm_free(void *);
 static char *usm_passwd2mkey(const EVP_MD *, const char *);
@@ -95,7 +106,9 @@ usm_init(const char *user, size_t userlen)
        sec->model = SNMP_SEC_USM;
        sec->init = usm_doinit;
        sec->genparams = usm_genparams;
+       sec->encpdu = usm_encpdu;
        sec->parseparams = usm_parseparams;
+       sec->decpdu = usm_decpdu;
        sec->finalparams = usm_finalparams;
        sec->free = usm_free;
        sec->freecookie = free;
@@ -143,7 +156,6 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void 
**cookie)
        ssize_t berlen;
        struct usm_cookie *usmcookie;
        struct timespec now, timediff;
-       uint32_t boots, time;
 
        bzero(digest, sizeof(digest));
 
@@ -151,21 +163,24 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void 
**cookie)
                return NULL;
        *cookie = usmcookie;
 
+       arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt));
        if (usm->timeset) {
                if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
                        free(usmcookie);
                        return NULL;
                }
                timespecsub(&now, &(usm->timecheck), &timediff);
-               time = usm->time + timediff.tv_sec;
+               usmcookie->time = usm->time + timediff.tv_sec;
        } else
-               time = 0;
-       boots = usm->boots;
+               usmcookie->time = 0;
+       usmcookie->boots = usm->boots;
 
        if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
-           usm->engineidlen, boots, time, usm->user, usm->userlen, digest,
-           agent->v3->level & SNMP_MSGFLAG_AUTH ? usm_digestlen(usm->digest) :
-           (size_t) 0, NULL, (size_t) 0)) == NULL) {
+           usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user,
+           usm->userlen, digest, agent->v3->level & SNMP_MSGFLAG_AUTH ?
+           usm_digestlen(usm->digest) : (size_t) 0, &(usmcookie->salt),
+           agent->v3->level & SNMP_MSGFLAG_AUTH ? sizeof(usmcookie->salt) :
+           (size_t) 0)) == NULL) {
                free(usmcookie);
                return NULL;
        }
@@ -190,6 +205,93 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void 
**cookie)
        return secparams;
 }
 
+static struct ber_element *
+usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie)
+{
+       struct usm_sec *usm = agent->v3->sec->data;
+       struct usm_cookie *usmcookie = cookie;
+       struct ber ber;
+       struct ber_element *retpdu;
+       char *serialpdu, *encpdu;
+       ssize_t pdulen;
+       size_t encpdulen;
+
+       bzero(&ber, sizeof(ber));
+       ber_set_application(&ber, smi_application);
+       pdulen = ber_write_elements(&ber, pdu);
+       if (pdulen == -1)
+               return NULL;
+
+       ber_get_writebuf(&ber, (void **)&serialpdu);
+
+       encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu,
+           pdulen, &encpdulen);
+       ber_free(&ber);
+       if (encpdu == NULL)
+               return NULL;
+
+       retpdu = ber_add_nstring(NULL, encpdu, encpdulen);
+       free(encpdu);
+       return retpdu;
+}
+
+static char *
+usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key,
+    struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen)
+{
+       EVP_CIPHER_CTX ctx;
+       size_t i;
+       char iv[EVP_MAX_IV_LENGTH];
+       char *salt = (char *)&(cookie->salt);
+       char *outtext;
+       int len, len2;
+       uint32_t ivv;
+
+       switch (EVP_CIPHER_type(cipher)) {
+       case NID_des_cbc:
+               /* RFC3414, chap 8.1.1.1. */
+               for (i = 0; i < 8; i++)
+                       iv[i] = salt[i] ^ key[USM_SALTOFFSET + i];
+               break;
+       case NID_aes_128_cfb128:
+               /* RFC3826, chap 3.1.2.1. */
+               ivv = htobe32(cookie->boots);
+               memcpy(iv, &ivv, sizeof(ivv));
+               ivv = htobe32(cookie->time);
+               memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
+               memcpy(iv + 2 * sizeof(ivv), &(cookie->salt),
+                   sizeof(cookie->salt));
+               break;
+       default:
+               return NULL;
+       }
+
+       bzero(&ctx, sizeof(ctx));
+       if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc))
+               return NULL;
+
+       EVP_CIPHER_CTX_set_padding(&ctx, do_enc);
+
+       *outlen = EVP_CIPHER_block_size(cipher);
+       /* Maximum output size */
+       *outlen = pdulen + (*outlen - (pdulen % *outlen));
+
+       if ((outtext = malloc(*outlen)) == NULL)
+               return NULL;
+
+       if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) &&
+           EVP_CipherFinal_ex(&ctx, outtext + len, &len2))
+               *outlen = len + len2;
+       else {
+               free(outtext);
+               outtext = NULL;
+       }
+
+       EVP_CIPHER_CTX_cleanup(&ctx);
+
+       return outtext;
+}
+
 static int
 usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
     size_t secparamsoffset, void *cookie)
@@ -215,17 +317,18 @@ usm_finalparams(struct snmp_agent *agent, char *buf, 
size_t buflen,
 
 static int
 usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
-    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level)
+    off_t secparamsoffset, char *buf, size_t buflen, uint8_t level,
+    void **cookie)
 {
        struct usm_sec *usm = agent->v3->sec->data;
        struct ber ber;
        struct ber_element *secparams;
-       char *engineid, *user, *digest;
-       size_t engineidlen, userlen, digestlen;
+       char *engineid, *user, *digest, *salt;
+       size_t engineidlen, userlen, digestlen, saltlen;
        struct timespec now, timediff;
        off_t digestoffset;
        char exp_digest[EVP_MAX_MD_SIZE];
-       uint32_t boots, time;
+       struct usm_cookie *usmcookie;
 
        bzero(&ber, sizeof(ber));
        bzero(exp_digest, sizeof(exp_digest));
@@ -236,10 +339,17 @@ usm_parseparams(struct snmp_agent *agent, char *packet, 
size_t packetlen,
                return -1;
        ber_free(&ber);
 
-       if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen,
-           &boots, &time, &user, &userlen, &digestoffset, &digest,
-           &digestlen) == -1)
+       if ((usmcookie = malloc(sizeof(*usmcookie))) == NULL)
+               goto fail;
+       *cookie = usmcookie;
+
+       if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen,
+           &(usmcookie->boots), &(usmcookie->time), &user, &userlen,
+           &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1)
                goto fail;
+       if (saltlen != sizeof(usmcookie->salt) && saltlen != 0)
+               goto fail;
+       memcpy(&(usmcookie->salt), salt, saltlen);
 
        if (!usm->engineidset) {
                if (usm_setengineid(agent->v3->sec, engineid,
@@ -253,12 +363,12 @@ usm_parseparams(struct snmp_agent *agent, char *packet, 
size_t packetlen,
        }
 
        if (!usm->bootsset) {
-               usm->boots = boots;
+               usm->boots = usmcookie->boots;
                usm->bootsset = 1;
        } else {
-               if (boots < usm->boots)
+               if (usmcookie->boots < usm->boots)
                        goto fail;
-               if (boots > usm->boots) {
+               if (usmcookie->boots > usm->boots) {
                        usm->bootsset = 0;
                        usm->timeset = 0;
                        usm_doinit(agent);
@@ -267,7 +377,7 @@ usm_parseparams(struct snmp_agent *agent, char *packet, 
size_t packetlen,
        }
 
        if (!usm->timeset) {
-               usm->time = time;
+               usm->time = usmcookie->time;
                if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1)
                        goto fail;
                usm->timeset = 1;
@@ -275,8 +385,10 @@ usm_parseparams(struct snmp_agent *agent, char *packet, 
size_t packetlen,
                if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
                        goto fail;
                timespecsub(&now, &(usm->timecheck), &timediff);
-               if (time < usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
-                   time > usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
+               if (usmcookie->time <
+                   usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
+                   usmcookie->time >
+                   usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
                        usm->bootsset = 0;
                        usm->timeset = 0;
                        usm_doinit(agent);
@@ -308,10 +420,35 @@ usm_parseparams(struct snmp_agent *agent, char *packet, 
size_t packetlen,
        return 0;
 
 fail:
+       free(usmcookie);
        ber_free_element(secparams);
        return -1;
 }
 
+struct ber_element *
+usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void 
*cookie)
+{
+       struct usm_sec *usm = agent->v3->sec->data;
+       struct usm_cookie *usmcookie = cookie;
+       struct ber ber;
+       struct ber_element *scopedpdu;
+       char *rawpdu;
+       size_t rawpdulen;
+
+       if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie,
+           encpdu, encpdulen, &rawpdulen)) == NULL)
+               return NULL;
+
+       bzero(&ber, sizeof(ber));
+       ber_set_application(&ber, smi_application);
+       ber_set_readbuf(&ber, rawpdu, rawpdulen);
+       scopedpdu = ber_read_elements(&ber, NULL);
+       ber_free(&ber);
+       free(rawpdu);
+
+       return scopedpdu;
+}
+
 static void
 usm_digest_pos(void *data, size_t offset)
 {
@@ -327,6 +464,7 @@ usm_free(void *data)
 
        free(usm->user);
        free(usm->authkey);
+       free(usm->privkey);
        free(usm->engineid);
        free(usm);
 }
@@ -364,6 +502,43 @@ usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, 
const char *key,
        return 0;
 }
 
+int
+usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key,
+    size_t keylen, enum usm_key_level level)
+{
+       struct usm_sec *usm = sec->data;
+       char *lkey;
+
+       if (usm->digest == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /*
+        * We could transform a master key to a local key here if we already
+        * have usm_setengineid called. Sine snmpc.c is the only caller at
+        * the moment there's no need, since it always calls us first.
+        */
+       if (level == USM_KEY_PASSWORD) {
+               if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL)
+                       return -1;
+               level = USM_KEY_MASTER;
+               keylen = EVP_MD_size(usm->digest);
+       } else {
+               if (keylen != (size_t)EVP_MD_size(usm->digest)) {
+                       errno = EINVAL;
+                       return -1;
+               }
+               if ((lkey = malloc(keylen)) == NULL)
+                       return -1;
+               memcpy(lkey, key, keylen);
+               usm->privkey = lkey;
+       }
+       usm->cipher = cipher;
+       usm->privlevel = level;
+       return 0;
+}
+
 int
 usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
 {
@@ -388,6 +563,16 @@ usm_setengineid(struct snmp_sec *sec, char *engineid, 
size_t engineidlen)
                free(mkey);
                usm->authlevel = USM_KEY_LOCALIZED;
        }
+       if (usm->privlevel == USM_KEY_MASTER) {
+               mkey = usm->privkey;
+               if ((usm->privkey = usm_mkey2lkey(usm, usm->digest,
+                   mkey)) == NULL) {
+                       usm->privkey = mkey;
+                       return -1;
+               }
+               free(mkey);
+               usm->privlevel = USM_KEY_LOCALIZED;
+       }
 
        return 0;
 }
diff --git a/usm.h b/usm.h
index 636cd0e..b1aea8b 100644
--- a/usm.h
+++ b/usm.h
@@ -28,5 +28,7 @@ enum usm_key_level {
 struct snmp_sec *usm_init(const char *, size_t);
 int usm_setauth(struct snmp_sec *, const EVP_MD *, const char *, size_t,
     enum usm_key_level);
+int usm_setpriv(struct snmp_sec *, const EVP_CIPHER *, const char *, size_t,
+    enum usm_key_level);
 int usm_setengineid(struct snmp_sec *, char *, size_t);
 int usm_setbootstime(struct snmp_sec *, uint32_t, uint32_t);

Reply via email to