Pageant is now able to avoid asking for the passphrase when asked to
[u/mdw/putty] / pageant.c
index 0114804..12e03a8 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -9,9 +9,11 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
+#include <assert.h>
 #include <tchar.h>
 
 #include "ssh.h"
+#include "misc.h"
 #include "tree234.h"
 
 #define IDI_MAINICON 200
 #define IDM_CLOSE    0x0010
 #define IDM_VIEWKEYS 0x0020
 #define IDM_ADDKEY   0x0030
-#define IDM_ABOUT    0x0040
+#define IDM_HELP     0x0040
+#define IDM_ABOUT    0x0050
 
 #define APPNAME "Pageant"
 
 extern char ver[];
 
 static HINSTANCE instance;
-static HWND hwnd;
+static HWND main_hwnd;
 static HWND keylist;
 static HWND aboutbox;
 static HMENU systray_menu;
 static int already_running;
+static int requested_help;
+
+static char *help_path;
 
 static tree234 *rsakeys, *ssh2keys;
 
@@ -61,6 +67,14 @@ void agent_query(void *in, int inlen, void **out, int *outlen);
 int agent_exists(void);
 
 /*
+ * Forward references
+ */
+static void *make_keylist1(int *length);
+static void *make_keylist2(int *length);
+static void *get_keylist1(void);
+static void *get_keylist2(void);
+
+/*
  * We need this to link with the RSA code, because rsaencrypt()
  * pads its data with random bytes. Since we only use rsadecrypt()
  * and the signing functions, which are deterministic, this should
@@ -72,7 +86,7 @@ int agent_exists(void);
  */
 int random_byte(void)
 {
-    MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
+    MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
     exit(0);
     /* this line can't be reached but it placates MSVC's warnings :-) */
     return 0;
@@ -115,6 +129,22 @@ struct PassphraseProcStruct {
     char *comment;
 };
 
+static tree234 *passphrases = NULL;
+
+/* 
+ * After processing a list of filenames, we want to forget the
+ * passphrases.
+ */
+static void forget_passphrases(void)
+{
+    while (count234(passphrases) > 0) {
+       char *pp = index234(passphrases, 0);
+       memset(pp, 0, strlen(pp));
+       delpos234(passphrases, 0);
+       free(pp);
+    }
+}
+
 /*
  * Dialog-box function for the Licence box.
  */
@@ -327,7 +357,8 @@ static void add_keyfile(char *filename)
     char *comment;
     struct PassphraseProcStruct pps;
     int ver;
-
+    int original_pass;
+       
     ver = keyfile_version(filename);
     if (ver == 0) {
        MessageBox(NULL, "Couldn't load private key.", APPNAME,
@@ -335,6 +366,65 @@ static void add_keyfile(char *filename)
        return;
     }
 
+    /*
+     * 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;
+
+       if (ver == 1) {
+           if (!rsakey_pubblob(filename, &blob, &bloblen)) {
+               MessageBox(NULL, "Couldn't load private key.", APPNAME,
+                          MB_OK | MB_ICONERROR);
+               return;
+           }
+           keylist = get_keylist1();
+       } else {
+           unsigned char *blob2;
+           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen);
+           if (!blob) {
+               MessageBox(NULL, "Couldn't load private key.", APPNAME,
+                          MB_OK | MB_ICONERROR);
+               return;
+           }
+           /* For our purposes we want the blob prefixed with its length */
+           blob2 = smalloc(bloblen+4);
+           PUT_32BIT(blob2, bloblen);
+           memcpy(blob2 + 4, blob, bloblen);
+           sfree(blob);
+           blob = blob2;
+
+           keylist = get_keylist2();
+       }
+       if (keylist) {
+           nkeys = GET_32BIT(keylist);
+           p = keylist + 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;
+               }
+               /* Now skip over public blob */
+               if (ver == 1)
+                   p += rsa_public_blob_len(p);
+               else
+                   p += 4 + GET_32BIT(p);
+               /* Now skip over comment field */
+               p += 4 + GET_32BIT(p);
+           }
+
+           sfree(keylist);
+       }
+
+       sfree(blob);
+    }
+
     if (ver == 1)
        needs_pass = rsakey_encrypted(filename, &comment);
     else
@@ -344,18 +434,26 @@ static void add_keyfile(char *filename)
        rkey = smalloc(sizeof(*rkey));
     pps.passphrase = passphrase;
     pps.comment = comment;
+    original_pass = 0;
     do {
        if (needs_pass) {
-           int dlgret;
-           dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
-                                   NULL, PassphraseProc, (LPARAM) & pps);
-           passphrase_box = NULL;
-           if (!dlgret) {
-               if (comment)
-                   sfree(comment);
-               if (ver == 1)
-                   sfree(rkey);
-               return;                /* operation cancelled */
+           /* try all the remembered passphrases first */
+           char *pp = index234(passphrases, attempts);
+           if(pp) {
+               strcpy(passphrase, pp);
+           } else {
+               int dlgret;
+               original_pass = 1;
+               dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
+                                       NULL, PassphraseProc, (LPARAM) & pps);
+               passphrase_box = NULL;
+               if (!dlgret) {
+                   if (comment)
+                       sfree(comment);
+                   if (ver == 1)
+                       sfree(rkey);
+                   return;                    /* operation cancelled */
+               }
            }
        } else
            *passphrase = '\0';
@@ -372,6 +470,13 @@ static void add_keyfile(char *filename)
        }
        attempts++;
     } while (ret == -1);
+
+    /* if they typed in an ok passphrase, remember it */
+    if(original_pass && ret) {
+       char *pp = dupstr(passphrase);
+       addpos234(passphrases, pp, 0);
+    }
+
     if (comment)
        sfree(comment);
     if (ret == 0) {
@@ -424,6 +529,9 @@ static void add_keyfile(char *filename)
                MessageBox(NULL, "The already running Pageant "
                           "refused to add the key.", APPNAME,
                           MB_OK | MB_ICONERROR);
+
+           sfree(request);
+           sfree(response);
        } else {
            if (add234(rsakeys, rkey) != rkey)
                sfree(rkey);           /* already present, don't waste RAM */
@@ -466,6 +574,9 @@ static void add_keyfile(char *filename)
                MessageBox(NULL, "The already running Pageant"
                           "refused to add the key.", APPNAME,
                           MB_OK | MB_ICONERROR);
+
+           sfree(request);
+           sfree(response);
        } else {
            if (add234(ssh2keys, skey) != skey) {
                skey->alg->freekey(skey->data);
@@ -476,6 +587,162 @@ static void add_keyfile(char *filename)
 }
 
 /*
+ * Create an SSH1 key list in a malloc'ed buffer; return its
+ * length.
+ */
+static void *make_keylist1(int *length)
+{
+    int i, nkeys, len;
+    struct RSAKey *key;
+    unsigned char *blob, *p, *ret;
+    int bloblen;
+
+    /*
+     * Count up the number and length of keys we hold.
+     */
+    len = 4;
+    nkeys = 0;
+    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+       nkeys++;
+       blob = rsa_public_blob(key, &bloblen);
+       len += bloblen;
+       sfree(blob);
+       len += 4 + strlen(key->comment);
+    }
+
+    /* Allocate the buffer. */
+    p = ret = smalloc(len);
+    if (length) *length = len;
+
+    PUT_32BIT(p, nkeys);
+    p += 4;
+    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+       blob = rsa_public_blob(key, &bloblen);
+       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);
+    }
+
+    assert(p - ret == len);
+    return ret;
+}
+
+/*
+ * Create an SSH2 key list in a malloc'ed buffer; return its
+ * length.
+ */
+static void *make_keylist2(int *length)
+{
+    struct ssh2_userkey *key;
+    int i, len, nkeys;
+    unsigned char *blob, *p, *ret;
+    int bloblen;
+
+    /*
+     * Count up the number and length of keys we hold.
+     */
+    len = 4;
+    nkeys = 0;
+    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+       nkeys++;
+       len += 4;              /* length field */
+       blob = key->alg->public_blob(key->data, &bloblen);
+       len += bloblen;
+       sfree(blob);
+       len += 4 + strlen(key->comment);
+    }
+
+    /* Allocate the buffer. */
+    p = ret = smalloc(len);
+    if (length) *length = len;
+
+    /*
+     * Packet header is the obvious five bytes, plus four
+     * bytes for the key count.
+     */
+    PUT_32BIT(p, nkeys);
+    p += 4;
+    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+       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);
+    }
+
+    assert(p - ret == len);
+    return ret;
+}
+
+/*
+ * Acquire a keylist1 from the primary Pageant; this means either
+ * calling make_keylist1 (if that's us) or sending a message to the
+ * primary Pageant (if it's not).
+ */
+static void *get_keylist1(void)
+{
+    void *ret;
+
+    if (already_running) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen;
+       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+       PUT_32BIT(request, 4);
+
+       agent_query(request, 5, &vresponse, &resplen);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
+           return NULL;
+
+       ret = smalloc(resplen-5);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+    } else {
+       ret = make_keylist1(NULL);
+    }
+    return ret;
+}
+
+/*
+ * Acquire a keylist2 from the primary Pageant; this means either
+ * calling make_keylist2 (if that's us) or sending a message to the
+ * primary Pageant (if it's not).
+ */
+static void *get_keylist2(void)
+{
+    void *ret;
+
+    if (already_running) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen;
+
+       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+       PUT_32BIT(request, 4);
+
+       agent_query(request, 5, &vresponse, &resplen);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
+           return NULL;
+
+       ret = smalloc(resplen-5);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+    } else {
+       ret = make_keylist2(NULL);
+    }
+    return ret;
+}
+
+/*
  * This is the main agent function that answers messages.
  */
 static void answer_msg(void *msg)
@@ -496,42 +763,18 @@ static void answer_msg(void *msg)
         * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
         */
        {
-           struct RSAKey *key;
-           int len, nkeys;
-           int i;
-
-           /*
-            * Count up the number and length of keys we hold.
-            */
-           len = nkeys = 0;
-           for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
-               nkeys++;
-               len += 4;              /* length field */
-               len += ssh1_bignum_length(key->exponent);
-               len += ssh1_bignum_length(key->modulus);
-               len += 4 + strlen(key->comment);
-           }
+           int len;
+           void *keylist;
 
-           /*
-            * 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] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
-           PUT_32BIT(ret + 5, nkeys);
-           p = ret + 5 + 4;
-           for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
-               PUT_32BIT(p, bignum_bitcount(key->modulus));
-               p += 4;
-               p += ssh1_write_bignum(p, key->exponent);
-               p += ssh1_write_bignum(p, key->modulus);
-               PUT_32BIT(p, strlen(key->comment));
-               memcpy(p + 4, key->comment, strlen(key->comment));
-               p += 4 + strlen(key->comment);
+           keylist = make_keylist1(&len);
+           if (len + 5 > AGENT_MAX_MSGLEN) {
+               sfree(keylist);
+               goto failure;
            }
+           PUT_32BIT(ret, len + 1);
+           memcpy(ret + 5, keylist, len);
+           sfree(keylist);
        }
        break;
       case SSH2_AGENTC_REQUEST_IDENTITIES:
@@ -539,47 +782,18 @@ static void answer_msg(void *msg)
         * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
         */
        {
-           struct ssh2_userkey *key;
-           int len, nkeys;
-           unsigned char *blob;
-           int bloblen;
-           int i;
-
-           /*
-            * Count up the number and length of keys we hold.
-            */
-           len = nkeys = 0;
-           for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
-               nkeys++;
-               len += 4;              /* length field */
-               blob = key->alg->public_blob(key->data, &bloblen);
-               len += bloblen;
-               sfree(blob);
-               len += 4 + strlen(key->comment);
-           }
+           int len;
+           void *keylist;
 
-           /*
-            * 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 (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
-               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);
+           keylist = make_keylist2(&len);
+           if (len + 5 > AGENT_MAX_MSGLEN) {
+               sfree(keylist);
+               goto failure;
            }
+           PUT_32BIT(ret, len + 1);
+           memcpy(ret + 5, keylist, len);
+           sfree(keylist);
        }
        break;
       case SSH1_AGENTC_RSA_CHALLENGE:
@@ -980,27 +1194,66 @@ static void prompt_add_keyfile(void)
 {
     OPENFILENAME of;
     char filename[FILENAME_MAX];
+    char *filelist = smalloc(8192);
+    char *filewalker;
+    int n, dirlen;
+       
     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.hwndOwner = main_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.lpstrFile = filelist;
+    *filelist = '\0';
+    of.nMaxFile = FILENAME_MAX;
     of.lpstrFileTitle = NULL;
     of.lpstrInitialDir = NULL;
     of.lpstrTitle = "Select Private Key File";
-    of.Flags = 0;
+    of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
     if (GetOpenFileName(&of)) {
-       add_keyfile(filename);
+       if(strlen(filelist) > of.nFileOffset)
+           /* Only one filename returned? */
+           add_keyfile(filelist);
+       else {
+           /* we are returned a bunch of strings, end to
+            * end. first string is the directory, the
+            * rest the filenames. terminated with an
+            * empty string.
+            */
+           filewalker = filelist;
+           dirlen = strlen(filewalker);
+           if(dirlen > FILENAME_MAX - 8) return;
+           memcpy(filename, filewalker, dirlen);
+
+           filewalker += dirlen + 1;
+           filename[dirlen++] = '\\';
+
+           /* then go over names one by one */
+           for(;;) {
+               n = strlen(filewalker) + 1;
+               /* end of the list */
+               if(n == 1)
+                   break;
+               /* too big, shouldn't happen */
+               if(n + dirlen > FILENAME_MAX)
+                   break;
+
+               memcpy(filename + dirlen, filewalker, n);
+               filewalker += n;
+
+               add_keyfile(filename);
+           }
+       }
+
        keylist_update();
+       forget_passphrases();
     }
+    sfree(filelist);
 }
 
 /*
@@ -1029,6 +1282,16 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                           rd.right - rd.left, rd.bottom - rd.top, TRUE);
        }
 
+        if (help_path)
+            SetWindowLong(hwnd, GWL_EXSTYLE,
+                          GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
+        else {
+            HWND item = GetDlgItem(hwnd, 103);   /* the Help button */
+            if (item)
+                DestroyWindow(item);
+        }
+        requested_help = FALSE;
+
        keylist = hwnd;
        {
            static int tabs[] = { 35, 60, 210 };
@@ -1059,34 +1322,93 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
          case 102:                    /* remove key */
            if (HIWORD(wParam) == BN_CLICKED ||
                HIWORD(wParam) == BN_DOUBLECLICKED) {
-               int n = SendDlgItemMessage(hwnd, 100, LB_GETCURSEL, 0, 0);
                int i;
-               if (n == LB_ERR) {
+               int rCount, sCount;
+               int *selectedArray;
+               
+               /* our counter within the array of selected items */
+               int itemNum;
+               
+               /* get the number of items selected in the list */
+               int numSelected = 
+                       SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
+               
+               /* none selected? that was silly */
+               if (numSelected == 0) {
                    MessageBeep(0);
                    break;
                }
-               for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++)
-                   if (n-- == 0)
-                       break;
-               if (rkey) {
-                   del234(rsakeys, rkey);
-                   freersakey(rkey);
-                   sfree(rkey);
-               } else {
-                   for (i = 0; NULL != (skey = index234(ssh2keys, i));
-                        i++) if (n-- == 0)
-                           break;
-                   if (skey) {
-                       del234(ssh2keys, skey);
-                       skey->alg->freekey(skey->data);
-                       sfree(skey);
-                   }
+
+               /* get item indices in an array */
+               selectedArray = smalloc(numSelected * sizeof(int));
+               SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
+                               numSelected, (WPARAM)selectedArray);
+               
+               itemNum = numSelected - 1;
+               rCount = count234(rsakeys);
+               sCount = count234(ssh2keys);
+               
+               /* go through the non-rsakeys until we've covered them all, 
+                * and/or we're out of selected items to check. note that
+                * we go *backwards*, to avoid complications from deleting
+                * things hence altering the offset of subsequent items
+                */
+           for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+                       skey = index234(ssh2keys, i);
+                       
+                       if (selectedArray[itemNum] == rCount + i) {
+                               del234(ssh2keys, skey);
+                               skey->alg->freekey(skey->data);
+                               sfree(skey);
+                               itemNum--; 
+                       }
                }
+               
+               /* do the same for the rsa keys */
+               for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+                       rkey = index234(rsakeys, i);
+
+                       if(selectedArray[itemNum] == i) {
+                               del234(rsakeys, rkey);
+                               freersakey(rkey);
+                               sfree(rkey);
+                               itemNum--;
+                       }
+               }
+
+               sfree(selectedArray); 
                keylist_update();
            }
            return 0;
+         case 103:                    /* help */
+            if (HIWORD(wParam) == BN_CLICKED ||
+                HIWORD(wParam) == BN_DOUBLECLICKED) {
+                if (help_path) {
+                    WinHelp(main_hwnd, help_path, HELP_COMMAND,
+                            (DWORD)"JI(`',`pageant.general')");
+                    requested_help = TRUE;
+                }
+            }
+           return 0;
        }
        return 0;
+      case WM_HELP:
+        if (help_path) {
+            int id = ((LPHELPINFO)lParam)->iCtrlId;
+            char *cmd = NULL;
+            switch (id) {
+              case 100: cmd = "JI(`',`pageant.keylist')"; break;
+              case 101: cmd = "JI(`',`pageant.addkey')"; break;
+              case 102: cmd = "JI(`',`pageant.remkey')"; break;
+            }
+            if (cmd) {
+                WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
+                requested_help = TRUE;
+            } else {
+                MessageBeep(0);
+            }
+        }
+        break;
       case WM_CLOSE:
        keylist = NULL;
        DestroyWindow(hwnd);
@@ -1208,9 +1530,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                             SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
            }
            break;
+         case IDM_HELP:
+            if (help_path) {
+                WinHelp(main_hwnd, help_path, HELP_COMMAND,
+                        (DWORD)"JI(`',`pageant.general')");
+                requested_help = TRUE;
+            }
+           break;
        }
        break;
       case WM_DESTROY:
+        if (requested_help) {
+            WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
+            requested_help = FALSE;
+        }
        PostQuitMessage(0);
        return 0;
       case WM_COPYDATA:
@@ -1314,10 +1647,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 /*
  * Fork and Exec the command in cmdline. [DBW]
  */
-void spawn_cmd(char *cmdline, int show)
+void spawn_cmd(char *cmdline, char * args, int show)
 {
     if (ShellExecute(NULL, _T("open"), cmdline,
-                    NULL, NULL, show) <= (HINSTANCE) 32) {
+                    args, NULL, show) <= (HINSTANCE) 32) {
        TCHAR sMsg[140];
        sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
                (int)GetLastError());
@@ -1373,6 +1706,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     instance = inst;
 
     /*
+     * See if we can find our Help file.
+     */
+    {
+        char b[2048], *p, *q, *r;
+        FILE *fp;
+        GetModuleFileName(NULL, b, sizeof(b) - 1);
+        r = b;
+        p = strrchr(b, '\\');
+        if (p && p >= r) r = p+1;
+        q = strrchr(b, ':');
+        if (q && q >= r) r = q+1;
+        strcpy(r, "putty.hlp");
+        if ( (fp = fopen(b, "r")) != NULL) {
+            help_path = dupstr(b);
+            fclose(fp);
+        } else
+            help_path = NULL;
+    }
+
+    /*
      * Find out if Pageant is already running.
      */
     already_running = FALSE;
@@ -1395,25 +1748,29 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            RegisterClass(&wndclass);
        }
 
-       hwnd = keylist = NULL;
+       main_hwnd = keylist = NULL;
 
-       hwnd = CreateWindow(APPNAME, APPNAME,
-                           WS_OVERLAPPEDWINDOW | WS_VSCROLL,
-                           CW_USEDEFAULT, CW_USEDEFAULT,
-                           100, 100, NULL, NULL, inst, NULL);
+       main_hwnd = CreateWindow(APPNAME, APPNAME,
+                                 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+                                 CW_USEDEFAULT, CW_USEDEFAULT,
+                                 100, 100, NULL, NULL, inst, NULL);
 
        /* Set up a system tray icon */
-       AddTrayIcon(hwnd);
+       AddTrayIcon(main_hwnd);
 
         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_SEPARATOR, 0, 0);
+        if (help_path)
+            AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
         AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+       AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
         AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
 
-       ShowWindow(hwnd, SW_HIDE);
+       ShowWindow(main_hwnd, SW_HIDE);
 
        /*
         * Initialise storage for RSA keys.
@@ -1424,6 +1781,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     }
 
     /*
+     * Initialise storage for short-term passphrase cache.
+     */
+    passphrases = newtree234(NULL);
+
+    /*
      * 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]
@@ -1435,10 +1797,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        while (*p) {
            while (*p && isspace(*p))
                p++;
-           if (*p && !isspace(*p)) {
+               if (*p && !isspace(*p)) {
                char *q = p, *pp = p;
                while (*p && (inquotes || !isspace(*p))) {
-                   if (*p == '"') {
+                       if (*p == '"') {
                        inquotes = !inquotes;
                        p++;
                        continue;
@@ -1468,8 +1830,24 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        }
     }
 
-    if (command)
-       spawn_cmd(command, show);
+    /*
+     * Forget any passphrase that we retained while going over
+     * command line keyfiles.
+     */
+    forget_passphrases();
+
+    if (command) {
+       char *args;
+       if (command[0] == '"')
+           args = strchr(++command, '"');
+       else
+           args = strchr(command, ' ');
+       if (args) {
+           *args++ = 0;
+           while(*args && isspace(*args)) args++;
+       }
+       spawn_cmd(command, args, show);
+    }
 
     /*
      * If Pageant was already running, we leave now. If we haven't
@@ -1499,7 +1877,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        NOTIFYICONDATA tnid;
 
        tnid.cbSize = sizeof(NOTIFYICONDATA);
-       tnid.hWnd = hwnd;
+       tnid.hWnd = main_hwnd;
        tnid.uID = 1;
 
        Shell_NotifyIcon(NIM_DELETE, &tnid);