]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - pageant.c
first pass
[PuTTY.git] / pageant.c
index a091b06c18f11563681dd93b8785d4f16c30e9f6..2d9a740236739923800f4d2cfc79a540302c5fdb 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -27,6 +27,8 @@ int random_byte(void)
     return 0;                 /* unreachable, but placate optimiser */
 }
 
+static int pageant_local = FALSE;
+
 /*
  * rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys.
  */
@@ -257,20 +259,48 @@ void *pageant_make_keylist2(int *length)
     return ret;
 }
 
-void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
+static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
+#ifdef __GNUC__
+__attribute__ ((format (printf, 3, 4)))
+#endif
+    ;
+
+static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
+{
+    /*
+     * This is the wrapper that takes a variadic argument list and
+     * turns it into the va_list that the log function really expects.
+     * It's safe to call this with logfn==NULL, because we
+     * double-check that below; but if you're going to do lots of work
+     * before getting here (such as looping, or hashing things) then
+     * you should probably check logfn manually before doing that.
+     */
+    if (logfn) {
+        va_list ap;
+        va_start(ap, fmt);
+        logfn(logctx, fmt, ap);
+        va_end(ap);
+    }
+}
+
+void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
+                         void *logctx, pageant_logfn_t logfn)
 {
     const unsigned char *p = msg;
     const unsigned char *msgend;
     unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char);
     int type;
+    const char *fail_reason;
 
     msgend = p + msglen;
 
     /*
      * Get the message type.
      */
-    if (msgend < p+1)
+    if (msgend < p+1) {
+        fail_reason = "message contained no type code";
        goto failure;
+    }
     type = *p++;
 
     switch (type) {
@@ -282,14 +312,28 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            int len;
            void *keylist;
 
+            plog(logctx, logfn, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES");
+
            ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
            keylist = pageant_make_keylist1(&len);
            if (len + 5 > AGENT_MAX_MSGLEN) {
                sfree(keylist);
+                fail_reason = "output would exceed max msglen";
                goto failure;
            }
            PUT_32BIT(ret, len + 1);
            memcpy(ret + 5, keylist, len);
+
+            plog(logctx, logfn, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER");
+            if (logfn) {               /* skip this loop if not logging */
+                int i;
+                struct RSAKey *rkey;
+                for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
+                    char fingerprint[128];
+                    rsa_fingerprint(fingerprint, sizeof(fingerprint), rkey);
+                    plog(logctx, logfn, "returned key: %s", fingerprint);
+                }
+            }
            sfree(keylist);
        }
        break;
@@ -301,14 +345,31 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            int len;
            void *keylist;
 
+            plog(logctx, logfn, "request: SSH2_AGENTC_REQUEST_IDENTITIES");
+
            ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
            keylist = pageant_make_keylist2(&len);
            if (len + 5 > AGENT_MAX_MSGLEN) {
                sfree(keylist);
+                fail_reason = "output would exceed max msglen";
                goto failure;
            }
            PUT_32BIT(ret, len + 1);
            memcpy(ret + 5, keylist, len);
+
+            plog(logctx, logfn, "reply: SSH2_AGENT_IDENTITIES_ANSWER");
+            if (logfn) {               /* skip this loop if not logging */
+                int i;
+                struct ssh2_userkey *skey;
+                for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
+                    char *fingerprint = ssh2_fingerprint(skey->alg,
+                                                         skey->data);
+                    plog(logctx, logfn, "returned key: %s %s",
+                         fingerprint, skey->comment);
+                    sfree(fingerprint);
+                }
+            }
+
            sfree(keylist);
        }
        break;
@@ -325,14 +386,19 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            struct MD5Context md5c;
            int i, len;
 
+            plog(logctx, logfn, "request: SSH1_AGENTC_RSA_CHALLENGE");
+
            p += 4;
            i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
-           if (i < 0)
+           if (i < 0) {
+                fail_reason = "request truncated before key exponent";
                goto failure;
+            }
            p += i;
            i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
            if (i < 0) {
                 freebn(reqkey.exponent);
+                fail_reason = "request truncated before key modulus";
                goto failure;
             }
            p += i;
@@ -340,6 +406,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (i < 0) {
                 freebn(reqkey.exponent);
                 freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "request truncated before challenge";
                goto failure;
             }
            p += i;
@@ -347,16 +415,36 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
                freebn(reqkey.exponent);
                freebn(reqkey.modulus);
                freebn(challenge);
+                fail_reason = "request truncated before session id";
                goto failure;
            }
            memcpy(response_source + 32, p, 16);
            p += 16;
-           if (msgend < p+4 ||
-               GET_32BIT(p) != 1 ||
-               (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
+           if (msgend < p+4) {
                freebn(reqkey.exponent);
                freebn(reqkey.modulus);
                freebn(challenge);
+                fail_reason = "request truncated before response type";
+               goto failure;
+            }
+            if (GET_32BIT(p) != 1) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "response type other than 1 not supported";
+               goto failure;
+            }
+            if (logfn) {
+                char fingerprint[128];
+                reqkey.comment = NULL;
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
+                plog(logctx, logfn, "requested key: %s", fingerprint);
+            }
+            if ((key = find234(rsakeys, &reqkey, NULL)) == NULL) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "key not found";
                goto failure;
            }
            response = rsadecrypt(challenge, key);
@@ -380,6 +468,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            PUT_32BIT(ret, len - 4);
            ret[4] = SSH1_AGENT_RSA_RESPONSE;
            memcpy(ret + 5, response_md5, 16);
+
+            plog(logctx, logfn, "reply: SSH1_AGENT_RSA_RESPONSE");
        }
        break;
       case SSH2_AGENTC_SIGN_REQUEST:
@@ -395,24 +485,41 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
             unsigned char *signature;
            int datalen, siglen, len;
 
-           if (msgend < p+4)
+            plog(logctx, logfn, "request: SSH2_AGENTC_SIGN_REQUEST");
+
+           if (msgend < p+4) {
+                fail_reason = "request truncated before public key";
                goto failure;
+            }
            b.len = toint(GET_32BIT(p));
-            if (b.len < 0 || b.len > msgend - (p+4))
+            if (b.len < 0 || b.len > msgend - (p+4)) {
+                fail_reason = "request truncated before public key";
                 goto failure;
+            }
            p += 4;
            b.blob = p;
            p += b.len;
-           if (msgend < p+4)
+           if (msgend < p+4) {
+                fail_reason = "request truncated before string to sign";
                goto failure;
+            }
            datalen = toint(GET_32BIT(p));
            p += 4;
-           if (datalen < 0 || datalen > msgend - p)
+           if (datalen < 0 || datalen > msgend - p) {
+                fail_reason = "request truncated before string to sign";
                goto failure;
+            }
            data = p;
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
+                plog(logctx, logfn, "requested key: %s", fingerprint);
+                sfree(fingerprint);
+            }
            key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
-           if (!key)
+           if (!key) {
+                fail_reason = "key not found";
                goto failure;
+            }
            signature = key->alg->sign(key->data, (const char *)data,
                                        datalen, &siglen);
            len = 5 + 4 + siglen;
@@ -421,6 +528,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            PUT_32BIT(ret + 5, siglen);
            memcpy(ret + 5 + 4, signature, siglen);
            sfree(signature);
+
+            plog(logctx, logfn, "reply: SSH2_AGENT_SIGN_RESPONSE");
        }
        break;
       case SSH1_AGENTC_ADD_RSA_IDENTITY:
@@ -433,6 +542,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            char *comment;
             int n, commentlen;
 
+            plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY");
+
            key = snew(struct RSAKey);
            memset(key, 0, sizeof(struct RSAKey));
 
@@ -440,6 +551,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (n < 0) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before public key";
                goto failure;
            }
            p += n;
@@ -448,30 +560,38 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (n < 0) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before private key";
                goto failure;
            }
            p += n;
 
+            /* SSH-1 names p and q the other way round, i.e. we have
+             * the inverse of p mod q and not of q mod p. We swap the
+             * names, because our internal RSA wants iqmp. */
+
            n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
            if (n < 0) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before iqmp";
                goto failure;
            }
            p += n;
 
-           n = ssh1_read_bignum(p, msgend - p, &key->p);  /* p */
+           n = ssh1_read_bignum(p, msgend - p, &key->q);  /* p */
            if (n < 0) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before p";
                goto failure;
            }
            p += n;
 
-           n = ssh1_read_bignum(p, msgend - p, &key->q);  /* q */
+           n = ssh1_read_bignum(p, msgend - p, &key->p);  /* q */
            if (n < 0) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before q";
                goto failure;
            }
            p += n;
@@ -479,6 +599,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (msgend < p+4) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before key comment";
                goto failure;
            }
             commentlen = toint(GET_32BIT(p));
@@ -486,6 +607,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (commentlen < 0 || commentlen > msgend - p) {
                freersakey(key);
                sfree(key);
+                fail_reason = "request truncated before key comment";
                goto failure;
            }
 
@@ -495,14 +617,25 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
                 comment[commentlen] = '\0';
                key->comment = comment;
            }
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
+
+            if (logfn) {
+                char fingerprint[128];
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), key);
+                plog(logctx, logfn, "submitted key: %s", fingerprint);
+            }
+
            if (add234(rsakeys, key) == key) {
                keylist_update();
+                PUT_32BIT(ret, 1);
                ret[4] = SSH_AGENT_SUCCESS;
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
            } else {
                freersakey(key);
                sfree(key);
+
+                fail_reason = "key already present";
+                goto failure;
            }
        }
        break;
@@ -518,37 +651,34 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            int alglen, commlen;
            int bloblen;
 
+            plog(logctx, logfn, "request: SSH2_AGENTC_ADD_IDENTITY");
 
-           if (msgend < p+4)
+           if (msgend < p+4) {
+                fail_reason = "request truncated before key algorithm";
                goto failure;
+            }
            alglen = toint(GET_32BIT(p));
            p += 4;
-           if (alglen < 0 || alglen > msgend - p)
+           if (alglen < 0 || alglen > msgend - p) {
+                fail_reason = "request truncated before key algorithm";
                goto failure;
+            }
            alg = (const char *)p;
            p += alglen;
 
            key = snew(struct ssh2_userkey);
-           /* Add further algorithm names here. */
-           if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
-               key->alg = &ssh_rsa;
-           else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
-               key->alg = &ssh_dss;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19))
-                key->alg = &ssh_ecdsa_nistp256;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19))
-                key->alg = &ssh_ecdsa_nistp384;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19))
-                key->alg = &ssh_ecdsa_nistp521;
-           else {
+            key->alg = find_pubkey_alg_len(alglen, alg);
+           if (!key->alg) {
                sfree(key);
+                fail_reason = "algorithm unknown";
                goto failure;
            }
 
            bloblen = msgend - p;
-           key->data = key->alg->openssh_createkey(&p, &bloblen);
+           key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen);
            if (!key->data) {
                sfree(key);
+                fail_reason = "key setup failed";
                goto failure;
            }
 
@@ -561,6 +691,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (msgend < p+4) {
                key->alg->freekey(key->data);
                sfree(key);
+                fail_reason = "request truncated before key comment";
                goto failure;
            }
            commlen = toint(GET_32BIT(p));
@@ -569,6 +700,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            if (commlen < 0 || commlen > msgend - p) {
                key->alg->freekey(key->data);
                sfree(key);
+                fail_reason = "request truncated before key comment";
                goto failure;
            }
            comment = snewn(commlen + 1, char);
@@ -578,15 +710,26 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            }
            key->comment = comment;
 
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint(key->alg, key->data);
+                plog(logctx, logfn, "submitted key: %s %s",
+                     fingerprint, key->comment);
+                sfree(fingerprint);
+            }
+
            if (add234(ssh2keys, key) == key) {
                keylist_update();
+                PUT_32BIT(ret, 1);
                ret[4] = SSH_AGENT_SUCCESS;
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
            } else {
                key->alg->freekey(key->data);
                sfree(key->comment);
                sfree(key);
+
+                fail_reason = "key already present";
+                goto failure;
            }
        }
        break;
@@ -600,22 +743,39 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            struct RSAKey reqkey, *key;
            int n;
 
+            plog(logctx, logfn, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY");
+
            n = makekey(p, msgend - p, &reqkey, NULL, 0);
-           if (n < 0)
+           if (n < 0) {
+                fail_reason = "request truncated before public key";
                goto failure;
+            }
+
+            if (logfn) {
+                char fingerprint[128];
+                reqkey.comment = NULL;
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
+                plog(logctx, logfn, "unwanted key: %s", fingerprint);
+            }
 
            key = find234(rsakeys, &reqkey, NULL);
            freebn(reqkey.exponent);
            freebn(reqkey.modulus);
            PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
            if (key) {
+                plog(logctx, logfn, "found with comment: %s", key->comment);
+
                del234(rsakeys, key);
                keylist_update();
                freersakey(key);
                sfree(key);
                ret[4] = SSH_AGENT_SUCCESS;
-           }
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+           } else {
+                fail_reason = "key not found";
+                goto failure;
+            }
        }
        break;
       case SSH2_AGENTC_REMOVE_IDENTITY:
@@ -628,29 +788,44 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
            struct ssh2_userkey *key;
            struct blob b;
 
-           if (msgend < p+4)
+            plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_IDENTITY");
+
+           if (msgend < p+4) {
+                fail_reason = "request truncated before public key";
                goto failure;
+            }
            b.len = toint(GET_32BIT(p));
            p += 4;
 
-           if (b.len < 0 || b.len > msgend - p)
+           if (b.len < 0 || b.len > msgend - p) {
+                fail_reason = "request truncated before public key";
                goto failure;
+            }
            b.blob = p;
            p += b.len;
 
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
+                plog(logctx, logfn, "unwanted key: %s", fingerprint);
+                sfree(fingerprint);
+            }
+
            key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
-           if (!key)
+           if (!key) {
+                fail_reason = "key not found";
                goto failure;
+            }
+
+            plog(logctx, logfn, "found with comment: %s", key->comment);
 
+            del234(ssh2keys, key);
+            keylist_update();
+            key->alg->freekey(key->data);
+            sfree(key);
            PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (key) {
-               del234(ssh2keys, key);
-               keylist_update();
-               key->alg->freekey(key->data);
-               sfree(key);
-               ret[4] = SSH_AGENT_SUCCESS;
-           }
+            ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
        }
        break;
       case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
@@ -660,6 +835,9 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
        {
            struct RSAKey *rkey;
 
+            plog(logctx, logfn, "request:"
+                " SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES");
+
            while ((rkey = index234(rsakeys, 0)) != NULL) {
                del234(rsakeys, rkey);
                freersakey(rkey);
@@ -669,6 +847,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
 
            PUT_32BIT(ret, 1);
            ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
        }
        break;
       case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
@@ -678,6 +858,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
        {
            struct ssh2_userkey *skey;
 
+            plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES");
+
            while ((skey = index234(ssh2keys, 0)) != NULL) {
                del234(ssh2keys, skey);
                skey->alg->freekey(skey->data);
@@ -687,15 +869,22 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
 
            PUT_32BIT(ret, 1);
            ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
        }
        break;
       default:
+        plog(logctx, logfn, "request: unknown message type %d", type);
+
+        fail_reason = "unrecognised message";
+        /* fall through */
       failure:
        /*
         * Unrecognised message. Return SSH_AGENT_FAILURE.
         */
        PUT_32BIT(ret, 1);
        ret[4] = SSH_AGENT_FAILURE;
+        plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", fail_reason);
        break;
     }
 
@@ -714,6 +903,7 @@ void *pageant_failure_msg(int *outlen)
 
 void pageant_init(void)
 {
+    pageant_local = TRUE;
     rsakeys = newtree234(cmpkeys_rsa);
     ssh2keys = newtree234(cmpkeys_ssh2);
 }
@@ -740,12 +930,12 @@ int pageant_count_ssh2_keys(void)
 
 int pageant_add_ssh1_key(struct RSAKey *rkey)
 {
-    return add234(rsakeys, rkey) != rkey;
+    return add234(rsakeys, rkey) == rkey;
 }
 
 int pageant_add_ssh2_key(struct ssh2_userkey *skey)
 {
-    return add234(ssh2keys, skey) != skey;
+    return add234(ssh2keys, skey) == skey;
 }
 
 int pageant_delete_ssh1_key(struct RSAKey *rkey)
@@ -790,7 +980,7 @@ struct pageant_conn_state {
 
     Socket connsock;
     void *logctx;
-    void (*logfn)(void *logctx, const char *fmt, ...);
+    pageant_logfn_t logfn;
     unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN];
     unsigned len, got;
     int real_packet;
@@ -801,8 +991,10 @@ static int pageant_conn_closing(Plug plug, const char *error_msg,
                                 int error_code, int calling_back)
 {
     struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
-    if (error_msg && pc->logfn)
-        pc->logfn(pc->logctx, "Pageant connection socket: %s", error_msg);
+    if (error_msg)
+        plog(pc->logctx, pc->logfn, "%p: error: %s", pc, error_msg);
+    else
+        plog(pc->logctx, pc->logfn, "%p: connection closed", pc);
     sk_close(pc->connsock);
     sfree(pc);
     return 1;
@@ -820,6 +1012,15 @@ static void pageant_conn_sent(Plug plug, int bufsize)
      */
 }
 
+static void pageant_conn_log(void *logctx, const char *fmt, va_list ap)
+{
+    /* Wrapper on pc->logfn that prefixes the connection identifier */
+    struct pageant_conn_state *pc = (struct pageant_conn_state *)logctx;
+    char *formatted = dupvprintf(fmt, ap);
+    plog(pc->logctx, pc->logfn, "%p: %s", pc, formatted);
+    sfree(formatted);
+}
+
 static int pageant_conn_receive(Plug plug, int urgent, char *data, int len)
 {
     struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
@@ -850,8 +1051,13 @@ static int pageant_conn_receive(Plug plug, int urgent, char *data, int len)
             int replylen;
 
             if (pc->real_packet) {
-                reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen);
+                reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen, pc,
+                                           pc->logfn?pageant_conn_log:NULL);
             } else {
+                plog(pc->logctx, pc->logfn, "%p: overlong message (%u)",
+                     pc, pc->len);
+                plog(pc->logctx, pc->logfn, "%p: reply: SSH_AGENT_FAILURE "
+                     "(message too long)", pc);
                 reply = pageant_failure_msg(&replylen);
             }
             sk_write(pc->connsock, reply, replylen);
@@ -868,15 +1074,15 @@ struct pageant_listen_state {
 
     Socket listensock;
     void *logctx;
-    void (*logfn)(void *logctx, const char *fmt, ...);
+    pageant_logfn_t logfn;
 };
 
 static int pageant_listen_closing(Plug plug, const char *error_msg,
                                   int error_code, int calling_back)
 {
     struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
-    if (error_msg && pl->logfn)
-        pl->logfn(pl->logctx, "Pageant listening socket: %s", error_msg);
+    if (error_msg)
+        plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg);
     sk_close(pl->listensock);
     pl->listensock = NULL;
     return 1;
@@ -895,6 +1101,7 @@ static int pageant_listen_accepting(Plug plug,
     struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
     struct pageant_conn_state *pc;
     const char *err;
+    char *peerinfo;
 
     pc = snew(struct pageant_conn_state);
     pc->fn = &connection_fn_table;
@@ -911,15 +1118,18 @@ static int pageant_listen_accepting(Plug plug,
 
     sk_set_frozen(pc->connsock, 0);
 
-    /* FIXME: can we get any useful peer id info? */
-    if (pl->logfn)
-        pl->logfn(pl->logctx, "Pageant socket connected");
+    peerinfo = sk_peer_info(pc->connsock);
+    if (peerinfo) {
+        plog(pl->logctx, pl->logfn, "%p: new connection from %s",
+             pc, peerinfo);
+    } else {
+        plog(pl->logctx, pl->logfn, "%p: new connection", pc);
+    }
 
     return 0;
 }
 
-struct pageant_listen_state *pageant_listener_new
-(void *logctx, void (*logfn)(void *logctx, const char *fmt, ...))
+struct pageant_listen_state *pageant_listener_new(void)
 {
     static const struct plug_function_table listener_fn_table = {
         NULL, /* no log function, because that's for outgoing connections */
@@ -931,8 +1141,8 @@ struct pageant_listen_state *pageant_listener_new
 
     struct pageant_listen_state *pl = snew(struct pageant_listen_state);
     pl->fn = &listener_fn_table;
-    pl->logctx = logctx;
-    pl->logfn = logfn;
+    pl->logctx = NULL;
+    pl->logfn = NULL;
     pl->listensock = NULL;
     return pl;
 }
@@ -942,9 +1152,684 @@ void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock)
     pl->listensock = sock;
 }
 
+void pageant_listener_set_logfn(struct pageant_listen_state *pl,
+                                void *logctx, pageant_logfn_t logfn)
+{
+    pl->logctx = logctx;
+    pl->logfn = logfn;
+}
+
 void pageant_listener_free(struct pageant_listen_state *pl)
 {
     if (pl->listensock)
         sk_close(pl->listensock);
     sfree(pl);
 }
+
+/* ----------------------------------------------------------------------
+ * Code to perform agent operations either as a client, or within the
+ * same process as the running agent.
+ */
+
+static tree234 *passphrases = NULL;
+
+/*
+ * After processing a list of filenames, we want to forget the
+ * passphrases.
+ */
+void pageant_forget_passphrases(void)
+{
+    if (!passphrases)                  /* in case we never set it up at all */
+        return;
+
+    while (count234(passphrases) > 0) {
+       char *pp = index234(passphrases, 0);
+       smemclr(pp, strlen(pp));
+       delpos234(passphrases, 0);
+       free(pp);
+    }
+}
+
+void *pageant_get_keylist1(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen;
+
+       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+        agent_query_synchronous(request, 5, &vresponse, &resplen);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist1(length);
+    }
+    return ret;
+}
+
+void *pageant_get_keylist2(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen;
+
+       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+       agent_query_synchronous(request, 5, &vresponse, &resplen);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist2(length);
+    }
+    return ret;
+}
+
+int pageant_add_keyfile(Filename *filename, const char *passphrase,
+                        char **retstr)
+{
+    struct RSAKey *rkey = NULL;
+    struct ssh2_userkey *skey = NULL;
+    int needs_pass;
+    int ret;
+    int attempts;
+    char *comment;
+    const char *this_passphrase;
+    const char *error = NULL;
+    int type;
+
+    if (!passphrases) {
+        passphrases = newtree234(NULL);
+    }
+
+    *retstr = NULL;
+
+    type = key_type(filename);
+    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
+       *retstr = dupprintf("Couldn't load this key (%s)",
+                            key_type_to_str(type));
+       return PAGEANT_ACTION_FAILURE;
+    }
+
+    /*
+     * See if the key is already loaded (in the primary Pageant,
+     * which may or may not be us).
+     */
+    {
+       void *blob;
+       unsigned char *keylist, *p;
+       int i, nkeys, bloblen, keylistlen;
+
+       if (type == SSH_KEYTYPE_SSH1) {
+           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+                return PAGEANT_ACTION_FAILURE;
+           }
+           keylist = pageant_get_keylist1(&keylistlen);
+       } else {
+           unsigned char *blob2;
+           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
+                                       NULL, &error);
+           if (!blob) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           /* For our purposes we want the blob prefixed with its length */
+           blob2 = snewn(bloblen+4, unsigned char);
+           PUT_32BIT(blob2, bloblen);
+           memcpy(blob2 + 4, blob, bloblen);
+           sfree(blob);
+           blob = blob2;
+
+           keylist = pageant_get_keylist2(&keylistlen);
+       }
+       if (keylist) {
+           if (keylistlen < 4) {
+               *retstr = dupstr("Received broken key list from agent");
+                sfree(keylist);
+                sfree(blob);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           nkeys = toint(GET_32BIT(keylist));
+           if (nkeys < 0) {
+               *retstr = dupstr("Received broken key list from agent");
+                sfree(keylist);
+                sfree(blob);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           p = keylist + 4;
+           keylistlen -= 4;
+
+           for (i = 0; i < nkeys; i++) {
+               if (!memcmp(blob, p, bloblen)) {
+                   /* Key is already present; we can now leave. */
+                   sfree(keylist);
+                   sfree(blob);
+                    return PAGEANT_ACTION_OK;
+               }
+               /* Now skip over public blob */
+               if (type == SSH_KEYTYPE_SSH1) {
+                   int n = rsa_public_blob_len(p, keylistlen);
+                   if (n < 0) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               } else {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = GET_32BIT(p);
+                    p += 4;
+                    keylistlen -= 4;
+
+                   if (n < 0 || n > keylistlen) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+               /* Now skip over comment field */
+               {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = GET_32BIT(p);
+                    p += 4;
+                    keylistlen -= 4;
+
+                   if (n < 0 || n > keylistlen) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+           }
+
+           sfree(keylist);
+       }
+
+       sfree(blob);
+    }
+
+    error = NULL;
+    if (type == SSH_KEYTYPE_SSH1)
+       needs_pass = rsakey_encrypted(filename, &comment);
+    else
+       needs_pass = ssh2_userkey_encrypted(filename, &comment);
+    attempts = 0;
+    if (type == SSH_KEYTYPE_SSH1)
+       rkey = snew(struct RSAKey);
+
+    /*
+     * Loop round repeatedly trying to load the key, until we either
+     * succeed, fail for some serious reason, or run out of
+     * passphrases to try.
+     */
+    while (1) {
+       if (needs_pass) {
+
+            /*
+             * If we've been given a passphrase on input, try using
+             * it. Otherwise, try one from our tree234 of previously
+             * useful passphrases.
+             */
+            if (passphrase) {
+                this_passphrase = (attempts == 0 ? passphrase : NULL);
+            } else {
+                this_passphrase = (const char *)index234(passphrases, attempts);
+            }
+
+            if (!this_passphrase) {
+                /*
+                 * Run out of passphrases to try.
+                 */
+                *retstr = comment;
+                sfree(rkey);
+                return PAGEANT_ACTION_NEED_PP;
+            }
+       } else
+           this_passphrase = "";
+
+       if (type == SSH_KEYTYPE_SSH1)
+           ret = loadrsakey(filename, rkey, this_passphrase, &error);
+       else {
+           skey = ssh2_load_userkey(filename, this_passphrase, &error);
+           if (skey == SSH2_WRONG_PASSPHRASE)
+               ret = -1;
+           else if (!skey)
+               ret = 0;
+           else
+               ret = 1;
+       }
+
+        if (ret == 0) {
+            /*
+             * Failed to load the key file, for some reason other than
+             * a bad passphrase.
+             */
+            *retstr = dupstr(error);
+            sfree(rkey);
+            return PAGEANT_ACTION_FAILURE;
+        } else if (ret == 1) {
+            /*
+             * Successfully loaded the key file.
+             */
+            break;
+        } else {
+            /*
+             * Passphrase wasn't right; go round again.
+             */
+            attempts++;
+        }
+    }
+
+    /*
+     * If we get here, we've succesfully loaded the key into
+     * rkey/skey, but not yet added it to the agent.
+     */
+
+    /*
+     * If the key was successfully decrypted, save the passphrase for
+     * use with other keys we try to load.
+     */
+    {
+        char *pp_copy = dupstr(this_passphrase);
+       if (addpos234(passphrases, pp_copy, 0) != pp_copy) {
+            /* No need; it was already there. */
+            smemclr(pp_copy, strlen(pp_copy));
+            sfree(pp_copy);
+        }
+    }
+
+    if (comment)
+       sfree(comment);
+
+    if (type == SSH_KEYTYPE_SSH1) {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, clen, resplen;
+
+           clen = strlen(rkey->comment);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 +                    /* bit count */
+               ssh1_bignum_length(rkey->modulus) +
+               ssh1_bignum_length(rkey->exponent) +
+               ssh1_bignum_length(rkey->private_exponent) +
+               ssh1_bignum_length(rkey->iqmp) +
+               ssh1_bignum_length(rkey->p) +
+               ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
+           reqlen += 4;
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
+           reqlen +=
+               ssh1_write_bignum(request + reqlen,
+                                 rkey->private_exponent);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, rkey->comment, clen);
+           reqlen += 4 + clen;
+           PUT_32BIT(request, reqlen - 4);
+
+           agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                freersakey(rkey);
+                sfree(rkey);
+                sfree(request);
+                sfree(response);
+                return PAGEANT_ACTION_FAILURE;
+            }
+            freersakey(rkey);
+            sfree(rkey);
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh1_key(rkey)) {
+                freersakey(rkey);
+               sfree(rkey);           /* already present, don't waste RAM */
+            }
+       }
+    } else {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, alglen, clen, keybloblen, resplen;
+           alglen = strlen(skey->alg->name);
+           clen = strlen(skey->comment);
+
+           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 + alglen +           /* algorithm name */
+               keybloblen +           /* key data */
+               4 + clen               /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH2_AGENTC_ADD_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, alglen);
+           reqlen += 4;
+           memcpy(request + reqlen, skey->alg->name, alglen);
+           reqlen += alglen;
+           reqlen += skey->alg->openssh_fmtkey(skey->data,
+                                               request + reqlen,
+                                               keybloblen);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, skey->comment, clen);
+           reqlen += clen + 4;
+           PUT_32BIT(request, reqlen - 4);
+
+           agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                sfree(request);
+                sfree(response);
+                return PAGEANT_ACTION_FAILURE;
+            }
+
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh2_key(skey)) {
+               skey->alg->freekey(skey->data);
+               sfree(skey);           /* already present, don't waste RAM */
+           }
+       }
+    }
+    return PAGEANT_ACTION_OK;
+}
+
+int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
+                      char **retstr)
+{
+    unsigned char *keylist, *p;
+    int i, nkeys, keylistlen;
+    char *comment;
+    struct pageant_pubkey cbkey;
+
+    keylist = pageant_get_keylist1(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        struct RSAKey rkey;
+        char fingerprint[128];
+        int n;
+
+        /* public blob and fingerprint */
+        memset(&rkey, 0, sizeof(rkey));
+        n = makekey(p, keylistlen, &rkey, NULL, 0);
+        if (n < 0 || n > keylistlen) {
+            freersakey(&rkey);
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        p += n, keylistlen -= n;
+        rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey);
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen);
+        cbkey.comment = comment;
+        cbkey.ssh_version = 1;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(cbkey.blob);
+        freersakey(&rkey);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    keylist = pageant_get_keylist2(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        char *fingerprint;
+        int n;
+
+        /* public blob */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        fingerprint = ssh2_fingerprint_blob(p, n);
+        cbkey.blob = p;
+        cbkey.bloblen = n;
+        p += n, keylistlen -= n;
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.ssh_version = 2;
+        cbkey.comment = comment;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(fingerprint);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    return PAGEANT_ACTION_OK;
+}
+
+int pageant_delete_key(struct pageant_pubkey *key, char **retstr)
+{
+    unsigned char *request, *response;
+    int reqlen, resplen, ret;
+    void *vresponse;
+
+    if (key->ssh_version == 1) {
+        reqlen = 5 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY;
+        memcpy(request + 5, key->blob, key->bloblen);
+    } else {
+        reqlen = 9 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH2_AGENTC_REMOVE_IDENTITY;
+        PUT_32BIT(request + 5, key->bloblen);
+        memcpy(request + 9, key->blob, key->bloblen);
+    }
+
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+        *retstr = dupstr("Agent failed to delete key");
+        ret = PAGEANT_ACTION_FAILURE;
+    } else {
+        *retstr = NULL;
+        ret = PAGEANT_ACTION_OK;
+    }
+    sfree(request);
+    sfree(response);
+    return ret;
+}
+
+int pageant_delete_all_keys(char **retstr)
+{
+    unsigned char request[5], *response;
+    int reqlen, resplen, success;
+    void *vresponse;
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES;
+    reqlen = 5;
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-2 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES;
+    reqlen = 5;
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-1 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    *retstr = NULL;
+    return PAGEANT_ACTION_OK;
+}
+
+struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key)
+{
+    struct pageant_pubkey *ret = snew(struct pageant_pubkey);
+    ret->blob = snewn(key->bloblen, unsigned char);
+    memcpy(ret->blob, key->blob, key->bloblen);
+    ret->bloblen = key->bloblen;
+    ret->comment = key->comment ? dupstr(key->comment) : NULL;
+    ret->ssh_version = key->ssh_version;
+    return ret;
+}
+
+void pageant_pubkey_free(struct pageant_pubkey *key)
+{
+    sfree(key->comment);
+    sfree(key->blob);
+    sfree(key);
+}