Pageant interface changes. You can now do `pageant -c command' to
[u/mdw/putty] / pageant.c
index 62f60db..aad4226 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -3,8 +3,24 @@
  */
 
 #include <windows.h>
+#ifndef NO_SECURITY
 #include <aclapi.h>
+#endif
 #include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <tchar.h>
+
+// FIXME
+#define DEBUG
+#ifdef DEBUG
+void dprintf(char *fmt, ...);
+#define debug(x) (dprintf x)
+#else
+#define debug(x)
+#endif
+
+
 #include "ssh.h"
 #include "tree234.h"
 
 
 #define IDM_CLOSE    0x0010
 #define IDM_VIEWKEYS 0x0020
-#define IDM_ABOUT    0x0030
+#define IDM_ADDKEY   0x0030
+#define IDM_ABOUT    0x0040
 
 #define APPNAME "Pageant"
 
-#define SSH_AGENTC_REQUEST_RSA_IDENTITIES    1
-#define SSH_AGENT_RSA_IDENTITIES_ANSWER      2
-#define SSH_AGENTC_RSA_CHALLENGE             3
-#define SSH_AGENT_RSA_RESPONSE               4
-#define SSH_AGENT_FAILURE                    5
-#define SSH_AGENT_SUCCESS                    6
-#define SSH_AGENTC_ADD_RSA_IDENTITY          7
-#define SSH_AGENTC_REMOVE_RSA_IDENTITY       8
-
 extern char ver[];
 
-HINSTANCE instance;
-HWND hwnd;
-HWND keylist;
-HWND aboutbox;
-HMENU systray_menu;
+static HINSTANCE instance;
+static HWND hwnd;
+static HWND keylist;
+static HWND aboutbox;
+static HMENU systray_menu;
+static int already_running;
 
-tree234 *rsakeys;
+static tree234 *rsakeys, *ssh2keys;
 
-int has_security;
+static int has_security;
+#ifndef NO_SECURITY
 typedef DWORD (WINAPI *gsi_fn_t)
     (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
                                  PSID *, PSID *, PACL *, PACL *,
                                  PSECURITY_DESCRIPTOR *);
-gsi_fn_t getsecurityinfo;
+static gsi_fn_t getsecurityinfo;
+#endif
 
 /*
  * We need this to link with the RSA code, because rsaencrypt()
- * pads its data with random bytes. Since we only use rsadecrypt(),
- * which is deterministic, this should never be called.
- *
+ * pads its data with random bytes. Since we only use rsadecrypt()
+ * and the signing functions, which are deterministic, this should
+ * never be called.
+ * 
  * If it _is_ called, there is a _serious_ problem, because it
  * won't generate true random numbers. So we must scream, panic,
  * and exit immediately if that should happen.
@@ -69,6 +81,16 @@ int random_byte(void) {
 }
 
 /*
+ * Blob structure for passing to the asymmetric SSH2 key compare
+ * function, prototyped here.
+ */
+struct blob {
+    unsigned char *blob;
+    int len;
+};
+static int cmpkeys_ssh2_asymm(void *av, void *bv);
+
+/*
  * This function is needed to link with the DES code. We need not
  * have it do anything at all.
  */
@@ -157,6 +179,23 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
 
     switch (msg) {
       case WM_INITDIALOG:
+       /*
+        * Centre the window.
+        */
+       {                              /* centre the window */
+           RECT rs, rd;
+           HWND hw;
+
+           hw = GetDesktopWindow();
+           if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
+               MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
+                           (rs.bottom + rs.top + rd.top - rd.bottom)/2,
+                           rd.right-rd.left, rd.bottom-rd.top, TRUE);
+       }
+
+        SetForegroundWindow(hwnd);
+        SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
+                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
         p = (struct PassphraseProcStruct *)lParam;
         passphrase = p->passphrase;
         if (p->comment)
@@ -192,24 +231,46 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
 /*
  * Update the visible key list.
  */
-void keylist_update(void) {
-    struct RSAKey *key;
+static void keylist_update(void) {
+    struct RSAKey *rkey;
+    struct ssh2_userkey *skey;
     enum234 e;
 
     if (keylist) {
         SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
-        for (key = first234(rsakeys, &e); key; key = next234(&e)) {
+        for (rkey = first234(rsakeys, &e); rkey; rkey = next234(&e)) {
             char listentry[512], *p;
             /*
              * Replace two spaces in the fingerprint with tabs, for
              * nice alignment in the box.
              */
-            rsa_fingerprint(listentry, sizeof(listentry), key);
+           strcpy(listentry, "ssh1\t");
+           p = listentry+strlen(listentry);
+            rsa_fingerprint(p, sizeof(listentry)-(p-listentry), rkey);
             p = strchr(listentry, ' '); if (p) *p = '\t';
             p = strchr(listentry, ' '); if (p) *p = '\t';
             SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
                                 0, (LPARAM)listentry);
         }
+        for (skey = first234(ssh2keys, &e); skey; skey = next234(&e)) {
+            char listentry[512], *p;
+           int len;
+            /*
+             * Replace two spaces in the fingerprint with tabs, for
+             * nice alignment in the box.
+             */
+           p = skey->alg->fingerprint(skey->data);
+           strncpy(listentry, p, sizeof(listentry));
+            p = strchr(listentry, ' '); if (p) *p = '\t';
+            p = strchr(listentry, ' '); if (p) *p = '\t';
+           len = strlen(listentry);
+           if (len < sizeof(listentry)-2) {
+               listentry[len] = '\t';
+               strncpy(listentry+len+1, skey->comment, sizeof(listentry)-len-1);
+           }
+            SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
+                                0, (LPARAM)listentry);
+        }
         SendDlgItemMessage (keylist, 100, LB_SETCURSEL, (WPARAM) -1, 0);
     }
 }
@@ -217,51 +278,201 @@ void keylist_update(void) {
 /*
  * This function loads a key from a file and adds it.
  */
-void add_keyfile(char *filename) {
+static void add_keyfile(char *filename) {
     char passphrase[PASSPHRASE_MAXLEN];
-    struct RSAKey *key;
+    struct RSAKey *rkey;
+    struct ssh2_userkey *skey;
     int needs_pass;
     int ret;
     int attempts;
     char *comment;
     struct PassphraseProcStruct pps;
+    int ver;
 
-    needs_pass = rsakey_encrypted(filename, &comment);
+    ver = keyfile_version(filename);
+    if (ver == 0) {
+        MessageBox(NULL, "Couldn't load private key.", APPNAME,
+                   MB_OK | MB_ICONERROR);
+        return;
+    }
+
+debug(("ooh %d\n", __LINE__));
+    if (ver == 1)
+       needs_pass = rsakey_encrypted(filename, &comment);
+    else
+       needs_pass = ssh2_userkey_encrypted(filename, &comment);
+debug(("ooh %d\n", __LINE__));
     attempts = 0;
-    key = malloc(sizeof(*key));
+debug(("ooh %d\n", __LINE__));
+    if (ver == 1)
+       rkey = smalloc(sizeof(*rkey));
+debug(("ooh %d\n", __LINE__));
     pps.passphrase = passphrase;
     pps.comment = comment;
+debug(("ooh %d\n", __LINE__));
     do {
+debug(("ooh %d\n", __LINE__));
         if (needs_pass) {
             int dlgret;
             dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
                                     NULL, PassphraseProc,
                                     (LPARAM)&pps);
             if (!dlgret) {
-                if (comment) free(comment);
-                free(key);
+                if (comment) sfree(comment);
+                if (ver == 1)
+                   sfree(rkey);
                 return;                /* operation cancelled */
             }
         } else
             *passphrase = '\0';
-        ret = loadrsakey(filename, key, passphrase);
+debug(("ooh %d\n", __LINE__));
+       if (ver == 1)
+           ret = loadrsakey(filename, rkey, passphrase);
+       else {
+debug(("ooh %d\n", __LINE__));
+           skey = ssh2_load_userkey(filename, passphrase);
+debug(("ooh %d\n", __LINE__));
+           if (skey == SSH2_WRONG_PASSPHRASE)
+               ret = -1;
+           else if (!skey)
+               ret = 0;
+           else
+               ret = 1;
+       }
         attempts++;
     } while (ret == -1);
-    if (comment) free(comment);
+debug(("ooh %d\n", __LINE__));
+    if (comment) sfree(comment);
+debug(("ooh %d\n", __LINE__));
     if (ret == 0) {
         MessageBox(NULL, "Couldn't load private key.", APPNAME,
                    MB_OK | MB_ICONERROR);
-        free(key);
+        if (ver == 1)
+           sfree(rkey);
         return;
     }
-    if (add234(rsakeys, key) != key)
-        free(key);                     /* already present, don't waste RAM */
+debug(("ooh %d\n", __LINE__));
+    if (ver == 1) {
+       if (already_running) {
+           unsigned char *request, *response;
+           int reqlen, clen, resplen;
+
+debug(("ooh %d\n", __LINE__));
+           clen = strlen(rkey->comment);
+debug(("ooh %d\n", __LINE__));
+
+           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 */
+               ;
+debug(("ooh %d %d\n", __LINE__, reqlen));
+
+           request = smalloc(reqlen);
+debug(("ooh %d\n", __LINE__));
+
+debug(("ooh %d\n", __LINE__));
+           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
+debug(("ooh %d\n", __LINE__));
+           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);
+
+debug(("ooh %d\n", __LINE__));
+           agent_query(request, reqlen, &response, &resplen);
+debug(("ooh %d\n", __LINE__));
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
+               MessageBox(NULL, "The already running Pageant "
+                          "refused to add the key.", APPNAME,
+                          MB_OK | MB_ICONERROR);
+       } else {
+           if (add234(rsakeys, rkey) != rkey)
+               sfree(rkey);           /* already present, don't waste RAM */
+       }
+    } else {
+       if (already_running) {
+           unsigned char *request, *response;
+           int reqlen, alglen, clen, keybloblen, resplen;
+debug(("ooh %d\n", __LINE__));
+           alglen = strlen(skey->alg->name);
+debug(("ooh %d\n", __LINE__));
+           clen = strlen(skey->comment);
+debug(("ooh %d\n", __LINE__));
+
+debug(("ooh %d\n", __LINE__));
+           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
+debug(("ooh %d\n", __LINE__));
+
+debug(("ooh %d\n", __LINE__));
+           reqlen = 4 + 1 +           /* length, message type */
+               4 + alglen +           /* algorithm name */
+               keybloblen +           /* key data */
+               4 + clen               /* comment */
+               ;
+debug(("ooh %d\n", __LINE__));
+
+debug(("ooh %d\n", __LINE__));
+           request = smalloc(reqlen);
+debug(("ooh %d\n", __LINE__));
+
+           request[4] = SSH2_AGENTC_ADD_IDENTITY;
+debug(("ooh %d\n", __LINE__));
+           reqlen = 5;
+debug(("ooh %d\n", __LINE__));
+           PUT_32BIT(request+reqlen, alglen);
+debug(("ooh %d\n", __LINE__));
+           reqlen += 4;
+debug(("ooh %d\n", __LINE__));
+           memcpy(request+reqlen, skey->alg->name, alglen);
+debug(("ooh %d\n", __LINE__));
+           reqlen += alglen;
+debug(("ooh %d\n", __LINE__));
+           reqlen += skey->alg->openssh_fmtkey(skey->data,
+                                               request+reqlen, keybloblen);
+debug(("ooh %d\n", __LINE__));
+           PUT_32BIT(request+reqlen, clen);
+debug(("ooh %d\n", __LINE__));
+           memcpy(request+reqlen+4, skey->comment, clen);
+debug(("ooh %d\n", __LINE__));
+           PUT_32BIT(request, reqlen-4);
+debug(("ooh %d\n", __LINE__));
+           reqlen += clen+4;
+
+           agent_query(request, reqlen, &response, &resplen);
+debug(("ooh %d\n", __LINE__));
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
+               MessageBox(NULL, "The already running Pageant"
+                          "refused to add the key.", APPNAME,
+                          MB_OK | MB_ICONERROR);
+debug(("ooh %d\n", __LINE__));
+       } else {
+           if (add234(ssh2keys, skey) != skey) {
+               skey->alg->freekey(skey->data);
+               sfree(skey);           /* already present, don't waste RAM */
+           }
+       }
+    }
 }
 
 /*
  * This is the main agent function that answers messages.
  */
-void answer_msg(void *msg) {
+static void answer_msg(void *msg) {
     unsigned char *p = msg;
     unsigned char *ret = msg;
     int type;
@@ -273,9 +484,9 @@ void answer_msg(void *msg) {
 
     p += 5;
     switch (type) {
-      case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
+      case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
         /*
-         * Reply with SSH_AGENT_RSA_IDENTITIES_ANSWER.
+         * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
          */
         {
             enum234 e;
@@ -302,11 +513,11 @@ void answer_msg(void *msg) {
             if (len > AGENT_MAX_MSGLEN)
                 goto failure;          /* aaargh! too much stuff! */
             PUT_32BIT(ret, len-4);
-            ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER;
+            ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
             PUT_32BIT(ret+5, nkeys);
             p = ret + 5 + 4;
             for (key = first234(rsakeys, &e); key; key = next234(&e)) {
-                PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
+                PUT_32BIT(p, bignum_bitcount(key->modulus));
                 p += 4;
                 p += ssh1_write_bignum(p, key->exponent);
                 p += ssh1_write_bignum(p, key->modulus);
@@ -316,9 +527,57 @@ void answer_msg(void *msg) {
             }
         }
         break;
-      case SSH_AGENTC_RSA_CHALLENGE:
+      case SSH2_AGENTC_REQUEST_IDENTITIES:
+        /*
+         * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
+         */
+        {
+            enum234 e;
+            struct ssh2_userkey *key;
+            int len, nkeys;
+           unsigned char *blob;
+           int bloblen;
+
+            /*
+             * Count up the number and length of keys we hold.
+             */
+            len = nkeys = 0;
+            for (key = first234(ssh2keys, &e); key; key = next234(&e)) {
+                nkeys++;
+                len += 4;              /* length field */
+               blob = key->alg->public_blob(key->data, &bloblen);
+               len += bloblen;
+               sfree(blob);
+                len += 4 + strlen(key->comment);
+            }
+
+            /*
+             * Packet header is the obvious five bytes, plus four
+             * bytes for the key count.
+             */
+            len += 5 + 4;
+            if (len > AGENT_MAX_MSGLEN)
+                goto failure;          /* aaargh! too much stuff! */
+            PUT_32BIT(ret, len-4);
+            ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
+            PUT_32BIT(ret+5, nkeys);
+            p = ret + 5 + 4;
+            for (key = first234(ssh2keys, &e); key; key = next234(&e)) {
+               blob = key->alg->public_blob(key->data, &bloblen);
+                PUT_32BIT(p, bloblen);
+                p += 4;
+               memcpy(p, blob, bloblen);
+               p += bloblen;
+               sfree(blob);
+                PUT_32BIT(p, strlen(key->comment));
+                memcpy(p+4, key->comment, strlen(key->comment));
+                p += 4 + strlen(key->comment);
+            }
+        }
+        break;
+      case SSH1_AGENTC_RSA_CHALLENGE:
         /*
-         * Reply with either SSH_AGENT_RSA_RESPONSE or
+         * Reply with either SSH1_AGENT_RSA_RESPONSE or
          * SSH_AGENT_FAILURE, depending on whether we have that key
          * or not.
          */
@@ -360,11 +619,42 @@ void answer_msg(void *msg) {
              */
             len = 5 + 16;
             PUT_32BIT(ret, len-4);
-            ret[4] = SSH_AGENT_RSA_RESPONSE;
+            ret[4] = SSH1_AGENT_RSA_RESPONSE;
             memcpy(ret+5, response_md5, 16);
         }
         break;
-      case SSH_AGENTC_ADD_RSA_IDENTITY:
+      case SSH2_AGENTC_SIGN_REQUEST:
+        /*
+         * Reply with either SSH2_AGENT_RSA_RESPONSE or
+         * SSH_AGENT_FAILURE, depending on whether we have that key
+         * or not.
+         */
+       {
+           struct ssh2_userkey *key;
+           struct blob b;
+           unsigned char *data, *signature;
+           int datalen, siglen, len;
+
+           b.len = GET_32BIT(p);
+           p += 4;
+           b.blob = p;
+           p += b.len;
+           datalen = GET_32BIT(p);
+           p += 4;
+           data = p;
+           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+           if (!key)
+               goto failure;
+           signature = key->alg->sign(key->data, data, datalen, &siglen);
+           len = 5+4+siglen;
+            PUT_32BIT(ret, len-4);
+            ret[4] = SSH2_AGENT_SIGN_RESPONSE;
+            PUT_32BIT(ret+5, siglen);
+           memcpy(ret+5+4, signature, siglen);
+           sfree(signature);
+       }
+       break;
+      case SSH1_AGENTC_ADD_RSA_IDENTITY:
         /*
          * Add to the list and return SSH_AGENT_SUCCESS, or
          * SSH_AGENT_FAILURE if the key was malformed.
@@ -372,14 +662,14 @@ void answer_msg(void *msg) {
         {
             struct RSAKey *key;
             char *comment;
-            key = malloc(sizeof(struct RSAKey));
+            key = smalloc(sizeof(struct RSAKey));
             memset(key, 0, sizeof(key));
             p += makekey(p, key, NULL, 1);
             p += makeprivate(p, key);
-            p += ssh1_read_bignum(p, NULL);    /* p^-1 mod q */
-            p += ssh1_read_bignum(p, NULL);    /* p */
-            p += ssh1_read_bignum(p, NULL);    /* q */
-            comment = malloc(GET_32BIT(p));
+            p += ssh1_read_bignum(p, key->iqmp); /* p^-1 mod q */
+            p += ssh1_read_bignum(p, key->p);    /* p */
+            p += ssh1_read_bignum(p, key->q);    /* q */
+            comment = smalloc(GET_32BIT(p));
             if (comment) {
                 memcpy(comment, p+4, GET_32BIT(p));
                 key->comment = comment;
@@ -391,11 +681,61 @@ void answer_msg(void *msg) {
                 ret[4] = SSH_AGENT_SUCCESS;
             } else {
                 freersakey(key);
-                free(key);
+                sfree(key);
             }
         }
         break;
-      case SSH_AGENTC_REMOVE_RSA_IDENTITY:
+      case SSH2_AGENTC_ADD_IDENTITY:
+        /*
+         * Add to the list and return SSH_AGENT_SUCCESS, or
+         * SSH_AGENT_FAILURE if the key was malformed.
+         */
+        {
+            struct ssh2_userkey *key;
+            char *comment, *alg;
+           int alglen, commlen;
+           int bloblen;
+
+           key = smalloc(sizeof(struct ssh2_userkey));
+
+           alglen = GET_32BIT(p); p += 4;
+           alg = p; p += alglen;
+           /* Add further algorithm names here. */
+           if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
+               key->alg = &ssh_rsa;
+           else {
+               sfree(key);
+               goto failure;
+           }
+
+           bloblen = GET_32BIT((unsigned char *)msg) - (p-(unsigned char *)msg-4);
+           key->data = key->alg->openssh_createkey(&p, &bloblen);
+           if (!key->data) {
+               sfree(key);
+               goto failure;
+           }
+           commlen = GET_32BIT(p); p += 4;
+
+            comment = smalloc(commlen+1);
+            if (comment) {
+                memcpy(comment, p, commlen);
+               comment[commlen] = '\0';
+            }
+           key->comment = comment;
+
+            PUT_32BIT(ret, 1);
+            ret[4] = SSH_AGENT_FAILURE;
+            if (add234(ssh2keys, key) == key) {
+                keylist_update();
+                ret[4] = SSH_AGENT_SUCCESS;
+            } else {
+               key->alg->freekey(key->data);
+               sfree(key->comment);
+                sfree(key);
+            }
+        }
+        break;
+      case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
         /*
          * Remove from the list and return SSH_AGENT_SUCCESS, or
          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
@@ -414,10 +754,78 @@ void answer_msg(void *msg) {
                 del234(rsakeys, key);
                 keylist_update();
                 freersakey(key);
+               sfree(key);
                 ret[4] = SSH_AGENT_SUCCESS;
             }
         }
         break;
+      case SSH2_AGENTC_REMOVE_IDENTITY:
+        /*
+         * Remove from the list and return SSH_AGENT_SUCCESS, or
+         * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
+         * start with.
+         */
+        {
+            struct ssh2_userkey *key;
+           struct blob b;
+
+           b.len = GET_32BIT(p);
+           p += 4;
+           b.blob = p;
+           p += b.len;
+           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+           if (!key)
+               goto failure;
+
+            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;
+            }
+        }
+        break;
+      case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
+        /*
+         * Remove all SSH1 keys. Always returns success.
+         */
+        {
+           struct RSAKey *rkey;
+           enum234 e;
+
+            while ( (rkey = first234(rsakeys, &e)) != NULL ) {
+                del234(rsakeys, rkey);
+               freersakey(rkey);
+               sfree(rkey);
+            }
+           keylist_update();
+
+           PUT_32BIT(ret, 1);
+           ret[4] = SSH_AGENT_SUCCESS;
+        }
+        break;
+      case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
+        /*
+         * Remove all SSH2 keys. Always returns success.
+         */
+        {
+            struct ssh2_userkey *skey;
+           enum234 e;
+
+            while ( (skey = first234(ssh2keys, &e)) != NULL ) {
+                del234(ssh2keys, skey);
+               skey->alg->freekey(skey->data);
+               sfree(skey);
+            }
+           keylist_update();
+
+           PUT_32BIT(ret, 1);
+           ret[4] = SSH_AGENT_SUCCESS;
+        }
+        break;
       default:
         failure:
         /*
@@ -432,7 +840,7 @@ void answer_msg(void *msg) {
 /*
  * Key comparison function for the 2-3-4 tree of RSA keys.
  */
-int cmpkeys(void *av, void *bv) {
+static int cmpkeys_rsa(void *av, void *bv) {
     struct RSAKey *a = (struct RSAKey *)av;
     struct RSAKey *b = (struct RSAKey *)bv;
     Bignum am, bm;
@@ -443,8 +851,8 @@ int cmpkeys(void *av, void *bv) {
     /*
      * Compare by length of moduli.
      */
-    alen = ssh1_bignum_bitcount(am);
-    blen = ssh1_bignum_bitcount(bm);
+    alen = bignum_bitcount(am);
+    blen = bignum_bitcount(bm);
     if (alen > blen) return +1; else if (alen < blen) return -1;
     /*
      * Now compare by moduli themselves.
@@ -462,27 +870,137 @@ int cmpkeys(void *av, void *bv) {
     return 0;
 }
 
+/*
+ * Key comparison function for the 2-3-4 tree of SSH2 keys.
+ */
+static int cmpkeys_ssh2(void *av, void *bv) {
+    struct ssh2_userkey *a = (struct ssh2_userkey *)av;
+    struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
+    int i;
+    int alen, blen;
+    unsigned char *ablob, *bblob;
+    int c;
+    
+    /*
+     * Compare purely by public blob.
+     */
+    ablob = a->alg->public_blob(a->data, &alen);
+    bblob = b->alg->public_blob(b->data, &blen);
+
+    c = 0;
+    for (i = 0; i < alen && i < blen; i++) {
+       if (ablob[i] < bblob[i]) {
+           c = -1; break;
+       } else if (ablob[i] > bblob[i]) {
+           c = +1; break;
+       }
+    }
+    if (c == 0 && i < alen) c = +1;    /* a is longer */
+    if (c == 0 && i < blen) c = -1;    /* a is longer */
+
+    sfree(ablob);
+    sfree(bblob);
+
+    return c;
+}
+
+/*
+ * Key comparison function for looking up a blob in the 2-3-4 tree
+ * of SSH2 keys.
+ */
+static int cmpkeys_ssh2_asymm(void *av, void *bv) {
+    struct blob *a = (struct blob *)av;
+    struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
+    int i;
+    int alen, blen;
+    unsigned char *ablob, *bblob;
+    int c;
+    
+    /*
+     * Compare purely by public blob.
+     */
+    ablob = a->blob;
+    alen = a->len;
+    bblob = b->alg->public_blob(b->data, &blen);
+
+    c = 0;
+    for (i = 0; i < alen && i < blen; i++) {
+       if (ablob[i] < bblob[i]) {
+           c = -1; break;
+       } else if (ablob[i] > bblob[i]) {
+           c = +1; break;
+       }
+    }
+    if (c == 0 && i < alen) c = +1;    /* a is longer */
+    if (c == 0 && i < blen) c = -1;    /* a is longer */
+
+    sfree(bblob);
+
+    return c;
+}
+
 static void error(char *s) {
     MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR);
 }
 
 /*
+ * Prompt for a key file to add, and add it.
+ */
+static void prompt_add_keyfile(void) {
+    OPENFILENAME of;
+    char filename[FILENAME_MAX];
+    memset(&of, 0, sizeof(of));
+#ifdef OPENFILENAME_SIZE_VERSION_400
+    of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+    of.lStructSize = sizeof(of);
+#endif
+    of.hwndOwner = hwnd;
+    of.lpstrFilter = "All Files\0*\0\0\0";
+    of.lpstrCustomFilter = NULL;
+    of.nFilterIndex = 1;
+    of.lpstrFile = filename; *filename = '\0';
+    of.nMaxFile = sizeof(filename);
+    of.lpstrFileTitle = NULL;
+    of.lpstrInitialDir = NULL;
+    of.lpstrTitle = "Select Private Key File";
+    of.Flags = 0;
+    if (GetOpenFileName(&of)) {
+        add_keyfile(filename);
+        keylist_update();
+    }
+}
+
+/*
  * Dialog-box function for the key list box.
  */
 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                 WPARAM wParam, LPARAM lParam) {
     enum234 e;
-    struct RSAKey *key;
-    OPENFILENAME of;
-    char filename[FILENAME_MAX];
+    struct RSAKey *rkey;
+    struct ssh2_userkey *skey;
 
     switch (msg) {
       case WM_INITDIALOG:
+       /*
+        * Centre the window.
+        */
+       {                              /* centre the window */
+           RECT rs, rd;
+           HWND hw;
+
+           hw = GetDesktopWindow();
+           if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
+               MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
+                           (rs.bottom + rs.top + rd.top - rd.bottom)/2,
+                           rd.right-rd.left, rd.bottom-rd.top, TRUE);
+       }
+
         keylist = hwnd;
        {
-           static int tabs[2] = {25, 175};
-           SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS, 2,
-                               (LPARAM) tabs);
+           static int tabs[] = {35, 60, 210};
+           SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS,
+                               sizeof(tabs)/sizeof(*tabs), (LPARAM) tabs);
        }
         keylist_update();
         return 0;
@@ -496,26 +1014,7 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
           case 101:                    /* add key */
            if (HIWORD(wParam) == BN_CLICKED ||
                HIWORD(wParam) == BN_DOUBLECLICKED) {
-                memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-                of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-                of.lStructSize = sizeof(of);
-#endif
-                of.hwndOwner = hwnd;
-                of.lpstrFilter = "All Files\0*\0\0\0";
-                of.lpstrCustomFilter = NULL;
-                of.nFilterIndex = 1;
-                of.lpstrFile = filename; *filename = '\0';
-                of.nMaxFile = sizeof(filename);
-                of.lpstrFileTitle = NULL;
-                of.lpstrInitialDir = NULL;
-                of.lpstrTitle = "Select Private Key File";
-                of.Flags = 0;
-                if (GetOpenFileName(&of)) {
-                    add_keyfile(filename);
-                    keylist_update();
-                }
+                prompt_add_keyfile();
             }
             return 0;
           case 102:                    /* remove key */
@@ -526,11 +1025,23 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                    MessageBeep(0);
                    break;
                }
-                for (key = first234(rsakeys, &e); key; key = next234(&e))
+                for (rkey = first234(rsakeys, &e); rkey; rkey = next234(&e))
                     if (n-- == 0)
                         break;
-                del234(rsakeys, key);
-                freersakey(key); free(key);
+               if (rkey) {
+                   del234(rsakeys, rkey);
+                   freersakey(rkey);
+                   sfree(rkey);
+               } else {
+                   for (skey = first234(ssh2keys, &e); skey; skey = next234(&e))
+                       if (n-- == 0)
+                           break;
+                   if (skey) {
+                       del234(ssh2keys, skey);
+                       skey->alg->freekey(skey->data);
+                       sfree(skey);
+                   }
+               }
                 keylist_update();
             }
             return 0;
@@ -591,6 +1102,9 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
                               SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
             }
             break;
+          case IDM_ADDKEY:
+            prompt_add_keyfile();
+            break;
           case IDM_ABOUT:
             if (!aboutbox) {
                 aboutbox = CreateDialog (instance, MAKEINTRESOURCE(213),
@@ -635,6 +1149,7 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
 #endif
             if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
                 int rc;
+#ifndef NO_SECURITY
                 if (has_security) {
                     if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
                                             GetCurrentProcessId())) == NULL) {
@@ -678,6 +1193,7 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
                     debug(("security APIs not present\r\n"));
 #endif
                 }
+#endif
                 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
 #ifdef DEBUG_IPC
                 debug(("p is %p\r\n", p));
@@ -695,11 +1211,26 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
     return DefWindowProc (hwnd, message, wParam, lParam);
 }
 
+/*
+ * Fork and Exec the command in cmdline. [DBW]
+ */
+void spawn_cmd(char *cmdline, int show) {
+    if (ShellExecute(NULL, _T("open"), cmdline,
+                    NULL, NULL, show) <= (HINSTANCE) 32) {
+        TCHAR sMsg[140];
+        sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
+               GetLastError());
+        MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
+    }
+}
+
 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
     WNDCLASS wndclass;
     MSG msg;
     OSVERSIONINFO osi;
     HMODULE advapi;
+    char *command = NULL;
+    int added_keys = 0;
 
     /*
      * Determine whether we're an NT system (should have security
@@ -713,8 +1244,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
         has_security = FALSE;
 
     if (has_security) {
+#ifndef NO_SECURITY
         /*
-         * Attempt to ge the security API we need.
+         * Attempt to get the security API we need.
          */
         advapi = LoadLibrary("ADVAPI32.DLL");
         getsecurityinfo = (gsi_fn_t)GetProcAddress(advapi, "GetSecurityInfo");
@@ -725,88 +1257,100 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
                        "Pageant Fatal Error", MB_ICONERROR | MB_OK);
             return 1;
         }
+#else
+       MessageBox(NULL,
+                  "This program has been compiled for Win9X and will\n"
+                  "not run on NT, in case it causes a security breach.",
+                  "Pageant Fatal Error", MB_ICONERROR | MB_OK);
+       return 1;
+#endif
     } else
         advapi = NULL;
 
-    /*
-     * First bomb out totally if we are already running.
-     */
-    if (FindWindow("Pageant", "Pageant")) {
-        MessageBox(NULL, "Pageant is already running", "Pageant Error",
-                   MB_ICONERROR | MB_OK);
-        if (advapi) FreeLibrary(advapi);
-        return 0;
-    }
-
     instance = inst;
 
-    if (!prev) {
-       wndclass.style         = 0;
-       wndclass.lpfnWndProc   = WndProc;
-       wndclass.cbClsExtra    = 0;
-       wndclass.cbWndExtra    = 0;
-       wndclass.hInstance     = inst;
-       wndclass.hIcon         = LoadIcon (inst,
-                                          MAKEINTRESOURCE(IDI_MAINICON));
-       wndclass.hCursor       = LoadCursor (NULL, IDC_IBEAM);
-       wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
-       wndclass.lpszMenuName  = NULL;
-       wndclass.lpszClassName = APPNAME;
-
-       RegisterClass (&wndclass);
-    }
+    /*
+     * Find out if Pageant is already running.
+     */
+    already_running = FALSE;
+    if (FindWindow("Pageant", "Pageant"))
+       already_running = TRUE;
+    else {
+
+       if (!prev) {
+           wndclass.style         = 0;
+           wndclass.lpfnWndProc   = WndProc;
+           wndclass.cbClsExtra    = 0;
+           wndclass.cbWndExtra    = 0;
+           wndclass.hInstance     = inst;
+           wndclass.hIcon         = LoadIcon (inst,
+                                              MAKEINTRESOURCE(IDI_MAINICON));
+           wndclass.hCursor       = LoadCursor (NULL, IDC_IBEAM);
+           wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
+           wndclass.lpszMenuName  = NULL;
+           wndclass.lpszClassName = APPNAME;
+
+           RegisterClass (&wndclass);
+       }
 
-    hwnd = keylist = NULL;
+       hwnd = keylist = NULL;
 
-    hwnd = CreateWindow (APPNAME, APPNAME,
-                         WS_OVERLAPPEDWINDOW | WS_VSCROLL,
-                         CW_USEDEFAULT, CW_USEDEFAULT,
-                         100, 100, NULL, NULL, inst, NULL);
+       hwnd = CreateWindow (APPNAME, APPNAME,
+                            WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+                            CW_USEDEFAULT, CW_USEDEFAULT,
+                            100, 100, NULL, NULL, inst, NULL);
 
-    /* Set up a system tray icon */
-    {
-        BOOL res;
-        NOTIFYICONDATA tnid;
-        HICON hicon;
+       /* Set up a system tray icon */
+       {
+           BOOL res;
+           NOTIFYICONDATA tnid;
+           HICON hicon;
 
 #ifdef NIM_SETVERSION
-        tnid.uVersion = 0;
-        res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
+           tnid.uVersion = 0;
+           res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
 #endif
 
-        tnid.cbSize = sizeof(NOTIFYICONDATA); 
-        tnid.hWnd = hwnd; 
-        tnid.uID = 1;                  /* unique within this systray use */
-        tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; 
-        tnid.uCallbackMessage = WM_SYSTRAY;
-        tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
-        strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
-
-        res = Shell_NotifyIcon(NIM_ADD, &tnid); 
-
-        if (hicon) 
-            DestroyIcon(hicon); 
-
-        systray_menu = CreatePopupMenu();
-        /* accelerators used: vxa */
-        AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
-        AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
-        AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
-    }
+           tnid.cbSize = sizeof(NOTIFYICONDATA);
+           tnid.hWnd = hwnd;
+           tnid.uID = 1;                  /* unique within this systray use */
+           tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+           tnid.uCallbackMessage = WM_SYSTRAY;
+           tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
+           strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
+
+           res = Shell_NotifyIcon(NIM_ADD, &tnid);
+
+           if (hicon)
+               DestroyIcon(hicon);
+
+           systray_menu = CreatePopupMenu();
+           /* accelerators used: vkxa */
+           AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
+           AppendMenu (systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
+           AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+           AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
+       }
 
-    ShowWindow (hwnd, SW_HIDE);
+       ShowWindow (hwnd, SW_HIDE);
 
-    /*
-     * Initialise storage for RSA keys.
-     */
-    rsakeys = newtree234(cmpkeys);
+       /*
+        * Initialise storage for RSA keys.
+        */
+       rsakeys = newtree234(cmpkeys_rsa);
+       ssh2keys = newtree234(cmpkeys_ssh2);
+
+    }
 
     /*
-     * Process the command line and add RSA keys as listed on it.
+     * Process the command line and add keys as listed on it.
+     * If we already determined that we need to spawn a program from above we
+     * need to ignore the first two arguments. [DBW]
      */
     {
         char *p;
         int inquotes = 0;
+        int ignorearg = 0;
         p = cmdline;
         while (*p) {
             while (*p && isspace(*p)) p++;
@@ -825,11 +1369,39 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
                     if (*p) p++;
                     *pp++ = '\0';
                 }
-                add_keyfile(q);
+               if (!strcmp(q, "-c")) {
+                   /*
+                    * If we see `-c', then the rest of the
+                    * command line should be treated as a
+                    * command to be spawned.
+                    */
+                   while (*p && isspace(*p)) p++;
+                   command = p;
+                   break;
+               } else {
+                   add_keyfile(q);
+                   added_keys = TRUE;
+               }
             }
         }
     }
 
+    if (command) spawn_cmd (command, show);
+
+    /*
+     * If Pageant was already running, we leave now. If we haven't
+     * even taken any auxiliary action (spawned a command or added
+     * keys), complain.
+     */
+    if (already_running) {
+       if (!command && !added_keys) {
+           MessageBox(NULL, "Pageant is already running", "Pageant Error",
+                      MB_ICONERROR | MB_OK);
+       }
+        if (advapi) FreeLibrary(advapi);
+        return 0;
+    }
+
     /*
      * Main message loop.
      */