]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - windows/winpgnt.c
Turn off Windows process ACL restriction by default.
[PuTTY.git] / windows / winpgnt.c
index 209c7c6936c7ac085d2e06fff5b4d4bd80408a57..fe0822e4889f4f10706cbe7b63cae9d53e783252 100644 (file)
@@ -8,31 +8,34 @@
 #include <assert.h>
 #include <tchar.h>
 
+#define PUTTY_DO_GLOBALS
+
 #include "putty.h"
 #include "ssh.h"
 #include "misc.h"
 #include "tree234.h"
+#include "winsecur.h"
+#include "pageant.h"
+#include "licence.h"
 
 #include <shellapi.h>
 
 #ifndef NO_SECURITY
 #include <aclapi.h>
+#ifdef DEBUG_IPC
+#define _WIN32_WINNT 0x0500            /* for ConvertSidToStringSid */
+#include <sddl.h>
+#endif
 #endif
 
 #define IDI_MAINICON 200
 #define IDI_TRAYICON 201
 
-#define WM_XUSER     (WM_USER + 0x2000)
-#define WM_SYSTRAY   (WM_XUSER + 6)
-#define WM_SYSTRAY2  (WM_XUSER + 7)
+#define WM_SYSTRAY   (WM_APP + 6)
+#define WM_SYSTRAY2  (WM_APP + 7)
 
 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
 
-/*
- * FIXME: maybe some day we can sort this out ...
- */
-#define AGENT_MAX_MSGLEN  8192
-
 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
  * wParam are used by Windows, and should be masked off, so we shouldn't
  * attempt to store information in them. Hence all these identifiers have
 
 #define APPNAME "Pageant"
 
-extern char ver[];
+extern const char ver[];
 
-static HINSTANCE instance;
-static HWND main_hwnd;
 static HWND keylist;
 static HWND aboutbox;
 static HMENU systray_menu, session_menu;
 static int already_running;
-static int requested_help;
 
-char *help_path;
 static char *putty_path;
 
+/* CWD for "add key" file requester. */
+static filereq *keypath = NULL;
+
 #define IDM_PUTTY         0x0060
 #define IDM_SESSIONS_BASE 0x1000
 #define IDM_SESSIONS_MAX  0x2000
@@ -69,7 +71,7 @@ static int initial_menuitems_count;
 /*
  * Print a modal (Really Bad) message box and perform a fatal exit.
  */
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *buf;
@@ -77,7 +79,7 @@ void modalfatalbox(char *fmt, ...)
     va_start(ap, fmt);
     buf = dupvprintf(fmt, ap);
     va_end(ap);
-    MessageBox(main_hwnd, buf, "Pageant Fatal Error",
+    MessageBox(hwnd, buf, "Pageant Fatal Error",
               MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
     sfree(buf);
     exit(1);
@@ -109,95 +111,22 @@ static void unmungestr(char *in, char *out, int outlen)
     return;
 }
 
-static tree234 *rsakeys, *ssh2keys;
-
 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 *);
-static gsi_fn_t getsecurityinfo;
-#endif
-
-/*
- * Forward references
- */
-static void *make_keylist1(int *length);
-static void *make_keylist2(int *length);
-static void *get_keylist1(int *length);
-static void *get_keylist2(int *length);
-
-/*
- * 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
- * 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.
- */
-int random_byte(void)
-{
-    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;
-}
-
-/*
- * 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);
-
-#define GET_32BIT(cp) \
-    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
-    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
-    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
-    ((unsigned long)(unsigned char)(cp)[3]))
-
-#define PUT_32BIT(cp, value) { \
-    (cp)[0] = (unsigned char)((value) >> 24); \
-    (cp)[1] = (unsigned char)((value) >> 16); \
-    (cp)[2] = (unsigned char)((value) >> 8); \
-    (cp)[3] = (unsigned char)(value); }
-
-#define PASSPHRASE_MAXLEN 512
 
 struct PassphraseProcStruct {
-    char *passphrase;
+    char **passphrase;
     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.
  */
-static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
       case WM_INITDIALOG:
+        SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n"));
        return 1;
       case WM_COMMAND:
        switch (LOWORD(wParam)) {
@@ -217,12 +146,21 @@ static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
 /*
  * Dialog-box function for the About box.
  */
-static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
       case WM_INITDIALOG:
-       SetDlgItemText(hwnd, 100, ver);
+        {
+            char *buildinfo_text = buildinfo("\r\n");
+            char *text = dupprintf
+                ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
+                 ver, buildinfo_text,
+                 "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
+            sfree(buildinfo_text);
+            SetDlgItemText(hwnd, 1000, text);
+            sfree(text);
+        }
        return 1;
       case WM_COMMAND:
        switch (LOWORD(wParam)) {
@@ -233,7 +171,7 @@ static int CALLBACK AboutProc(HWND hwnd, UINT msg,
            return 0;
          case 101:
            EnableWindow(hwnd, 0);
-           DialogBox(instance, MAKEINTRESOURCE(214), hwnd, LicenceProc);
+           DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
            EnableWindow(hwnd, 1);
            SetActiveWindow(hwnd);
            return 0;
@@ -252,10 +190,10 @@ static HWND passphrase_box;
 /*
  * Dialog-box function for the passphrase box.
  */
-static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
                                   WPARAM wParam, LPARAM lParam)
 {
-    static char *passphrase = NULL;
+    static char **passphrase = NULL;
     struct PassphraseProcStruct *p;
 
     switch (msg) {
@@ -283,8 +221,9 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
        passphrase = p->passphrase;
        if (p->comment)
            SetDlgItemText(hwnd, 101, p->comment);
-       *passphrase = 0;
-       SetDlgItemText(hwnd, 102, passphrase);
+        burnstr(*passphrase);
+        *passphrase = dupstr("");
+       SetDlgItemText(hwnd, 102, *passphrase);
        return 0;
       case WM_COMMAND:
        switch (LOWORD(wParam)) {
@@ -299,9 +238,8 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
            return 0;
          case 102:                    /* edit box */
            if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
-               GetDlgItemText(hwnd, 102, passphrase,
-                              PASSPHRASE_MAXLEN - 1);
-               passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
+                burnstr(*passphrase);
+                *passphrase = GetDlgItemText_alloc(hwnd, 102);
            }
            return 0;
        }
@@ -320,7 +258,7 @@ void old_keyfile_warning(void)
 {
     static const char mbtitle[] = "PuTTY Key File Warning";
     static const char message[] =
-       "You are loading an SSH 2 private key which has an\n"
+       "You are loading an SSH-2 private key which has an\n"
        "old version of the file format. This means your key\n"
        "file is not fully tamperproof. Future versions of\n"
        "PuTTY may stop supporting this private key format,\n"
@@ -336,7 +274,7 @@ void old_keyfile_warning(void)
 /*
  * Update the visible key list.
  */
-static void keylist_update(void)
+void keylist_update(void)
 {
     struct RSAKey *rkey;
     struct ssh2_userkey *skey;
@@ -344,7 +282,7 @@ static void keylist_update(void)
 
     if (keylist) {
        SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
-       for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
+       for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
            char listentry[512], *p;
            /*
             * Replace two spaces in the fingerprint with tabs, for
@@ -362,1041 +300,164 @@ static void keylist_update(void)
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
                               0, (LPARAM) listentry);
        }
-       for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
-           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);
-           }
+       for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
+           char *listentry, *p;
+           int pos;
+
+            /*
+             * For nice alignment in the list box, we would ideally
+             * want every entry to align to the tab stop settings, and
+             * have a column for algorithm name, one for bit count,
+             * one for hex fingerprint, and one for key comment.
+             *
+             * Unfortunately, some of the algorithm names are so long
+             * that they overflow into the bit-count field.
+             * Fortunately, at the moment, those are _precisely_ the
+             * algorithm names that don't need a bit count displayed
+             * anyway (because for NIST-style ECDSA the bit count is
+             * mentioned in the algorithm name, and for ssh-ed25519
+             * there is only one possible value anyway). So we fudge
+             * this by simply omitting the bit count field in that
+             * situation.
+             *
+             * This is fragile not only in the face of further key
+             * types that don't follow this pattern, but also in the
+             * face of font metrics changes - the Windows semantics
+             * for list box tab stops is that \t aligns to the next
+             * one you haven't already exceeded, so I have to guess
+             * when the key type will overflow past the bit-count tab
+             * stop and leave out a tab character. Urgh.
+             */
+
+           p = ssh2_fingerprint(skey->alg, skey->data);
+            listentry = dupprintf("%s\t%s", p, skey->comment);
+            sfree(p);
+
+            pos = 0;
+            while (1) {
+                pos += strcspn(listentry + pos, " :");
+                if (listentry[pos] == ':' || !listentry[pos])
+                    break;
+                listentry[pos++] = '\t';
+            }
+            if (skey->alg != &ssh_dss && skey->alg != &ssh_rsa) {
+                /*
+                 * Remove the bit-count field, which is between the
+                 * first and second \t.
+                 */
+                int outpos;
+                pos = 0;
+                while (listentry[pos] && listentry[pos] != '\t')
+                    pos++;
+                outpos = pos;
+                pos++;
+                while (listentry[pos] && listentry[pos] != '\t')
+                    pos++;
+                while (1) {
+                    if ((listentry[outpos] = listentry[pos]) == '\0')
+                        break;
+                    outpos++;
+                    pos++;
+                }
+            }
+
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
                               (LPARAM) listentry);
+            sfree(listentry);
        }
        SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
     }
 }
 
-/*
- * This function loads a key from a file and adds it.
- */
-static void add_keyfile(Filename filename)
+static void answer_msg(void *msgv)
 {
-    char passphrase[PASSPHRASE_MAXLEN];
-    struct RSAKey *rkey = NULL;
-    struct ssh2_userkey *skey = NULL;
-    int needs_pass;
-    int ret;
-    int attempts;
-    char *comment;
-    struct PassphraseProcStruct pps;
-    int type;
-    int original_pass;
-       
-    type = key_type(&filename);
-    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
-       char msg[256];
-       sprintf(msg, "Couldn't load this key (%s)", key_type_to_str(type));
-       MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONERROR);
-       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, keylistlen;
-
-       if (type == SSH_KEYTYPE_SSH1) {
-           if (!rsakey_pubblob(&filename, &blob, &bloblen, NULL)) {
-               MessageBox(NULL, "Couldn't load private key.", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           keylist = get_keylist1(&keylistlen);
-       } else {
-           unsigned char *blob2;
-           blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, NULL);
-           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 = snewn(bloblen+4, unsigned char);
-           PUT_32BIT(blob2, bloblen);
-           memcpy(blob2 + 4, blob, bloblen);
-           sfree(blob);
-           blob = blob2;
-
-           keylist = get_keylist2(&keylistlen);
-       }
-       if (keylist) {
-           if (keylistlen < 4) {
-               MessageBox(NULL, "Received broken key list?!", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           nkeys = GET_32BIT(keylist);
-           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;
-               }
-               /* Now skip over public blob */
-               if (type == SSH_KEYTYPE_SSH1) {
-                   int n = rsa_public_blob_len(p, keylistlen);
-                   if (n < 0) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               } else {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = 4 + GET_32BIT(p);
-                   if (keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-               /* Now skip over comment field */
-               {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = 4 + GET_32BIT(p);
-                   if (keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-           }
-
-           sfree(keylist);
-       }
-
-       sfree(blob);
-    }
-
-    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);
-    pps.passphrase = passphrase;
-    pps.comment = comment;
-    original_pass = 0;
-    do {
-       if (needs_pass) {
-           /* 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 (type == SSH_KEYTYPE_SSH1)
-                       sfree(rkey);
-                   return;                    /* operation cancelled */
-               }
-           }
-       } else
-           *passphrase = '\0';
-       if (type == SSH_KEYTYPE_SSH1)
-           ret = loadrsakey(&filename, rkey, passphrase, NULL);
-       else {
-           skey = ssh2_load_userkey(&filename, passphrase, NULL);
-           if (skey == SSH2_WRONG_PASSPHRASE)
-               ret = -1;
-           else if (!skey)
-               ret = 0;
-           else
-               ret = 1;
-       }
-       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) {
-       MessageBox(NULL, "Couldn't load private key.", APPNAME,
-                  MB_OK | MB_ICONERROR);
-       if (type == SSH_KEYTYPE_SSH1)
-           sfree(rkey);
-       return;
-    }
-    if (type == SSH_KEYTYPE_SSH1) {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, clen, resplen, ret;
-
-           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);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               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 */
-       }
+    unsigned char *msg = (unsigned char *)msgv;
+    unsigned msglen;
+    void *reply;
+    int replylen;
+
+    msglen = GET_32BIT(msg);
+    if (msglen > AGENT_MAX_MSGLEN) {
+        reply = pageant_failure_msg(&replylen);
     } else {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, alglen, clen, keybloblen, resplen, ret;
-           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);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               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);
-               sfree(skey);           /* already present, don't waste RAM */
-           }
-       }
+        reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
+        if (replylen > AGENT_MAX_MSGLEN) {
+            smemclr(reply, replylen);
+            sfree(reply);
+            reply = pageant_failure_msg(&replylen);
+        }
     }
-}
-
-/*
- * 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.
+     * Windows Pageant answers messages in place, by overwriting the
+     * input message buffer.
      */
-    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 = snewn(len, unsigned char);
-    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;
+    memcpy(msg, reply, replylen);
+    smemclr(reply, replylen);
+    sfree(reply);
 }
 
-/*
- * Create an SSH2 key list in a malloc'ed buffer; return its
- * length.
- */
-static void *make_keylist2(int *length)
+static void win_add_keyfile(Filename *filename)
 {
-    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 = snewn(len, unsigned char);
-    if (length) *length = len;
+    char *err;
+    int ret;
+    char *passphrase = NULL;
 
     /*
-     * Packet header is the obvious five bytes, plus four
-     * bytes for the key count.
+     * Try loading the key without a passphrase. (Or rather, without a
+     * _new_ passphrase; pageant_add_keyfile will take care of trying
+     * all the passphrases we've already stored.)
      */
-    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(int *length)
-{
-    void *ret;
-
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
-           return NULL;
-
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
-
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = make_keylist1(length);
-    }
-    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(int *length)
-{
-    void *ret;
-
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-
-       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
-           return NULL;
-
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
-
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = make_keylist2(length);
+    ret = pageant_add_keyfile(filename, NULL, &err);
+    if (ret == PAGEANT_ACTION_OK) {
+        goto done;
+    } else if (ret == PAGEANT_ACTION_FAILURE) {
+        goto error;
     }
-    return ret;
-}
-
-/*
- * This is the main agent function that answers messages.
- */
-static void answer_msg(void *msg)
-{
-    unsigned char *p = msg;
-    unsigned char *ret = msg;
-    unsigned char *msgend;
-    int type;
-
-    /*
-     * Get the message length.
-     */
-    msgend = p + 4 + GET_32BIT(p);
 
     /*
-     * Get the message type.
+     * OK, a passphrase is needed, and we've been given the key
+     * comment to use in the passphrase prompt.
      */
-    if (msgend < p+5)
-       goto failure;
-    type = p[4];
-
-    p += 5;
-    switch (type) {
-      case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
-       /*
-        * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
-        */
-       {
-           int len;
-           void *keylist;
-
-           ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
-           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:
-       /*
-        * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
-        */
-       {
-           int len;
-           void *keylist;
-
-           ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
-           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:
-       /*
-        * Reply with either SSH1_AGENT_RSA_RESPONSE or
-        * SSH_AGENT_FAILURE, depending on whether we have that key
-        * or not.
-        */
-       {
-           struct RSAKey reqkey, *key;
-           Bignum challenge, response;
-           unsigned char response_source[48], response_md5[16];
-           struct MD5Context md5c;
-           int i, len;
-
-           p += 4;
-           i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
-           if (i < 0)
-               goto failure;
-           p += i;
-           i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
-           if (i < 0)
-               goto failure;
-           p += i;
-           i = ssh1_read_bignum(p, msgend - p, &challenge);
-           if (i < 0)
-               goto failure;
-           p += i;
-           if (msgend < p+16) {
-               freebn(reqkey.exponent);
-               freebn(reqkey.modulus);
-               freebn(challenge);
-               goto failure;
-           }
-           memcpy(response_source + 32, p, 16);
-           p += 16;
-           if (msgend < p+4 ||
-               GET_32BIT(p) != 1 ||
-               (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
-               freebn(reqkey.exponent);
-               freebn(reqkey.modulus);
-               freebn(challenge);
-               goto failure;
-           }
-           response = rsadecrypt(challenge, key);
-           for (i = 0; i < 32; i++)
-               response_source[i] = bignum_byte(response, 31 - i);
-
-           MD5Init(&md5c);
-           MD5Update(&md5c, response_source, 48);
-           MD5Final(response_md5, &md5c);
-           memset(response_source, 0, 48);     /* burn the evidence */
-           freebn(response);          /* and that evidence */
-           freebn(challenge);         /* yes, and that evidence */
-           freebn(reqkey.exponent);   /* and free some memory ... */
-           freebn(reqkey.modulus);    /* ... while we're at it. */
-
-           /*
-            * Packet is the obvious five byte header, plus sixteen
-            * bytes of MD5.
-            */
-           len = 5 + 16;
-           PUT_32BIT(ret, len - 4);
-           ret[4] = SSH1_AGENT_RSA_RESPONSE;
-           memcpy(ret + 5, response_md5, 16);
-       }
-       break;
-      case SSH2_AGENTC_SIGN_REQUEST:
-       /*
-        * Reply with either SSH2_AGENT_SIGN_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;
-
-           if (msgend < p+4)
-               goto failure;
-           b.len = GET_32BIT(p);
-           p += 4;
-           if (msgend < p+b.len)
-               goto failure;
-           b.blob = p;
-           p += b.len;
-           if (msgend < p+4)
-               goto failure;
-           datalen = GET_32BIT(p);
-           p += 4;
-           if (msgend < p+datalen)
-               goto failure;
-           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.
-        */
-       {
-           struct RSAKey *key;
-           char *comment;
-            int n, commentlen;
-
-           key = snew(struct RSAKey);
-           memset(key, 0, sizeof(struct RSAKey));
-
-           n = makekey(p, msgend - p, key, NULL, 1);
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = makeprivate(p, msgend - p, key);
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->p);  /* p */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->q);  /* q */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           if (msgend < p+4) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-            commentlen = GET_32BIT(p);
-
-           if (msgend < p+commentlen) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-
-           comment = snewn(commentlen+1, char);
-           if (comment) {
-               memcpy(comment, p + 4, commentlen);
-                comment[commentlen] = '\0';
-               key->comment = comment;
-           }
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (add234(rsakeys, key) == key) {
-               keylist_update();
-               ret[4] = SSH_AGENT_SUCCESS;
-           } else {
-               freersakey(key);
-               sfree(key);
-           }
-       }
-       break;
-      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;
-
-
-           if (msgend < p+4)
-               goto failure;
-           alglen = GET_32BIT(p);
-           p += 4;
-           if (msgend < p+alglen)
-               goto failure;
-           alg = 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 {
-               sfree(key);
-               goto failure;
-           }
+    while (1) {
+        INT_PTR dlgret;
+        struct PassphraseProcStruct pps;
 
-           bloblen = msgend - p;
-           key->data = key->alg->openssh_createkey(&p, &bloblen);
-           if (!key->data) {
-               sfree(key);
-               goto failure;
-           }
+        pps.passphrase = &passphrase;
+        pps.comment = err;
+        dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
+                                NULL, PassphraseProc, (LPARAM) &pps);
+        passphrase_box = NULL;
 
-           /*
-            * p has been advanced by openssh_createkey, but
-            * certainly not _beyond_ the end of the buffer.
-            */
-           assert(p <= msgend);
+        if (!dlgret)
+            goto done;                /* operation cancelled */
 
-           if (msgend < p+4) {
-               key->alg->freekey(key->data);
-               sfree(key);
-               goto failure;
-           }
-           commlen = GET_32BIT(p);
-           p += 4;
+        sfree(err);
 
-           if (msgend < p+commlen) {
-               key->alg->freekey(key->data);
-               sfree(key);
-               goto failure;
-           }
-           comment = snewn(commlen + 1, char);
-           if (comment) {
-               memcpy(comment, p, commlen);
-               comment[commlen] = '\0';
-           }
-           key->comment = comment;
+        assert(passphrase != NULL);
 
-           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
-        * start with.
-        */
-       {
-           struct RSAKey reqkey, *key;
-           int n;
-
-           n = makekey(p, msgend - p, &reqkey, NULL, 0);
-           if (n < 0)
-               goto failure;
-
-           key = find234(rsakeys, &reqkey, NULL);
-           freebn(reqkey.exponent);
-           freebn(reqkey.modulus);
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (key) {
-               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;
-
-           if (msgend < p+4)
-               goto failure;
-           b.len = GET_32BIT(p);
-           p += 4;
-
-           if (msgend < p+b.len)
-               goto failure;
-           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;
-
-           while ((rkey = index234(rsakeys, 0)) != 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;
-
-           while ((skey = index234(ssh2keys, 0)) != 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:
-       /*
-        * Unrecognised message. Return SSH_AGENT_FAILURE.
-        */
-       PUT_32BIT(ret, 1);
-       ret[4] = SSH_AGENT_FAILURE;
-       break;
-    }
-}
-
-/*
- * Key comparison function for the 2-3-4 tree of RSA keys.
- */
-static int cmpkeys_rsa(void *av, void *bv)
-{
-    struct RSAKey *a = (struct RSAKey *) av;
-    struct RSAKey *b = (struct RSAKey *) bv;
-    Bignum am, bm;
-    int alen, blen;
+        ret = pageant_add_keyfile(filename, passphrase, &err);
+        if (ret == PAGEANT_ACTION_OK) {
+            goto done;
+        } else if (ret == PAGEANT_ACTION_FAILURE) {
+            goto error;
+        }
 
-    am = a->modulus;
-    bm = b->modulus;
-    /*
-     * Compare by length of moduli.
-     */
-    alen = bignum_bitcount(am);
-    blen = bignum_bitcount(bm);
-    if (alen > blen)
-       return +1;
-    else if (alen < blen)
-       return -1;
-    /*
-     * Now compare by moduli themselves.
-     */
-    alen = (alen + 7) / 8;            /* byte count */
-    while (alen-- > 0) {
-       int abyte, bbyte;
-       abyte = bignum_byte(am, alen);
-       bbyte = bignum_byte(bm, alen);
-       if (abyte > bbyte)
-           return +1;
-       else if (abyte < bbyte)
-           return -1;
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
+        passphrase = NULL;
     }
-    /*
-     * Give up.
-     */
-    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;
-       }
+  error:
+    message_box(err, APPNAME, MB_OK | MB_ICONERROR,
+                HELPCTXID(errors_cantloadkey));
+  done:
+    if (passphrase) {
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
     }
-    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;
+    sfree(err);
+    return;
 }
 
 /*
@@ -1405,66 +466,46 @@ static int cmpkeys_ssh2_asymm(void *av, void *bv)
 static void prompt_add_keyfile(void)
 {
     OPENFILENAME of;
-    char filename[FILENAME_MAX];
     char *filelist = snewn(8192, char);
-    char *filewalker;
-    int n, dirlen;
        
+    if (!keypath) keypath = filereq_new();
     memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-    of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-    of.lStructSize = sizeof(of);
-#endif
-    of.hwndOwner = main_hwnd;
-    of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
-       "All Files (*.*)\0*\0\0\0";
+    of.hwndOwner = hwnd;
+    of.lpstrFilter = FILTER_KEY_FILES;
     of.lpstrCustomFilter = NULL;
     of.nFilterIndex = 1;
     of.lpstrFile = filelist;
     *filelist = '\0';
-    of.nMaxFile = FILENAME_MAX;
+    of.nMaxFile = 8192;
     of.lpstrFileTitle = NULL;
-    of.lpstrInitialDir = NULL;
     of.lpstrTitle = "Select Private Key File";
     of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
-    if (GetOpenFileName(&of)) {
-       if(strlen(filelist) > of.nFileOffset)
+    if (request_file(keypath, &of, TRUE, FALSE)) {
+       if(strlen(filelist) > of.nFileOffset) {
            /* Only one filename returned? */
-           add_keyfile(filename_from_str(filelist));
-       else {
+            Filename *fn = filename_from_str(filelist);
+           win_add_keyfile(fn);
+            filename_free(fn);
+        } 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_from_str(filename));
+           char *dir = filelist;
+           char *filewalker = filelist + strlen(dir) + 1;
+           while (*filewalker != '\0') {
+               char *filename = dupcat(dir, "\\", filewalker, NULL);
+                Filename *fn = filename_from_str(filename);
+               win_add_keyfile(fn);
+                filename_free(fn);
+               sfree(filename);
+               filewalker += strlen(filewalker) + 1;
            }
        }
 
        keylist_update();
-       forget_passphrases();
+       pageant_forget_passphrases();
     }
     sfree(filelist);
 }
@@ -1472,7 +513,7 @@ static void prompt_add_keyfile(void)
 /*
  * Dialog-box function for the key list box.
  */
-static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     struct RSAKey *rkey;
@@ -1495,19 +536,19 @@ 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);
+        if (has_help())
+            SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+                            GetWindowLongPtr(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 };
+           static int tabs[] = { 35, 75, 250 };
            SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
                               sizeof(tabs) / sizeof(*tabs),
                               (LPARAM) tabs);
@@ -1558,35 +599,35 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                numSelected, (WPARAM)selectedArray);
                
                itemNum = numSelected - 1;
-               rCount = count234(rsakeys);
-               sCount = count234(ssh2keys);
+               rCount = pageant_count_ssh1_keys();
+               sCount = pageant_count_ssh2_keys();
                
                /* 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);
+                for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+                    skey = pageant_nth_ssh2_key(i);
                        
-                       if (selectedArray[itemNum] == rCount + i) {
-                               del234(ssh2keys, skey);
-                               skey->alg->freekey(skey->data);
-                               sfree(skey);
-                               itemNum--; 
-                       }
+                    if (selectedArray[itemNum] == rCount + i) {
+                        pageant_delete_ssh2_key(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--;
-                       }
+                    rkey = pageant_nth_ssh1_key(i);
+
+                    if(selectedArray[itemNum] == i) {
+                        pageant_delete_ssh1_key(rkey);
+                        freersakey(rkey);
+                        sfree(rkey);
+                        itemNum--;
+                    }
                }
 
                sfree(selectedArray); 
@@ -1596,27 +637,22 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
          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;
-                }
+               launch_help(hwnd, WINHELP_CTX_pageant_general);
             }
            return 0;
        }
        return 0;
       case WM_HELP:
-        if (help_path) {
+        {
             int id = ((LPHELPINFO)lParam)->iCtrlId;
-            char *cmd = NULL;
+            const char *topic = NULL;
             switch (id) {
-              case 100: cmd = "JI(`',`pageant.keylist')"; break;
-              case 101: cmd = "JI(`',`pageant.addkey')"; break;
-              case 102: cmd = "JI(`',`pageant.remkey')"; break;
+              case 100: topic = WINHELP_CTX_pageant_keylist; break;
+              case 101: topic = WINHELP_CTX_pageant_addkey; break;
+              case 102: topic = WINHELP_CTX_pageant_remkey; break;
             }
-            if (cmd) {
-                WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
-                requested_help = TRUE;
+            if (topic) {
+               launch_help(hwnd, topic);
             } else {
                 MessageBeep(0);
             }
@@ -1647,7 +683,7 @@ static BOOL AddTrayIcon(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));
+    tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
     strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
 
     res = Shell_NotifyIcon(NIM_ADD, &tnid);
@@ -1710,10 +746,56 @@ static void update_sessions(void)
     }
 }
 
+#ifndef NO_SECURITY
+/*
+ * Versions of Pageant prior to 0.61 expected this SID on incoming
+ * communications. For backwards compatibility, and more particularly
+ * for compatibility with derived works of PuTTY still using the old
+ * Pageant client code, we accept it as an alternative to the one
+ * returned from get_user_sid() in winpgntc.c.
+ */
+PSID get_default_sid(void)
+{
+    HANDLE proc = NULL;
+    DWORD sidlen;
+    PSECURITY_DESCRIPTOR psd = NULL;
+    PSID sid = NULL, copy = NULL, ret = NULL;
+
+    if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
+                            GetCurrentProcessId())) == NULL)
+        goto cleanup;
+
+    if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
+                          &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
+        goto cleanup;
+
+    sidlen = GetLengthSid(sid);
+
+    copy = (PSID)smalloc(sidlen);
+
+    if (!CopySid(sidlen, copy, sid))
+        goto cleanup;
+
+    /* Success. Move sid into the return value slot, and null it out
+     * to stop the cleanup code freeing it. */
+    ret = copy;
+    copy = NULL;
+
+  cleanup:
+    if (proc != NULL)
+        CloseHandle(proc);
+    if (psd != NULL)
+        LocalFree(psd);
+    if (copy != NULL)
+        sfree(copy);
+
+    return ret;
+}
+#endif
+
 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                WPARAM wParam, LPARAM lParam)
 {
-    int ret;
     static int menuinprogress;
     static UINT msgTaskbarCreated = 0;
 
@@ -1748,10 +830,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            menuinprogress = 1;
            update_sessions();
            SetForegroundWindow(hwnd);
-           ret = TrackPopupMenu(systray_menu,
-                                TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
-                                TPM_RIGHTBUTTON,
-                                wParam, lParam, 0, hwnd, NULL);
+           TrackPopupMenu(systray_menu,
+                          TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
+                          TPM_RIGHTBUTTON,
+                          wParam, lParam, 0, hwnd, NULL);
            menuinprogress = 0;
        }
        break;
@@ -1759,7 +841,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_SYSCOMMAND:
        switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
          case IDM_PUTTY:
-           if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
+           if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
                                 SW_SHOW) <= 32) {
                MessageBox(NULL, "Unable to execute PuTTY!",
                           "Error", MB_OK | MB_ICONERROR);
@@ -1772,7 +854,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            break;
          case IDM_VIEWKEYS:
            if (!keylist) {
-               keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
+               keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
                                       NULL, KeyListProc);
                ShowWindow(keylist, SW_SHOWNORMAL);
            }
@@ -1797,7 +879,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            break;
          case IDM_ABOUT:
            if (!aboutbox) {
-               aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
+               aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
                                        NULL, AboutProc);
                ShowWindow(aboutbox, SW_SHOWNORMAL);
                /* 
@@ -1810,11 +892,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            }
            break;
          case IDM_HELP:
-            if (help_path) {
-                WinHelp(main_hwnd, help_path, HELP_COMMAND,
-                        (DWORD)"JI(`',`pageant.general')");
-                requested_help = TRUE;
-            }
+           launch_help(hwnd, WINHELP_CTX_pageant_general);
            break;
          default:
            {
@@ -1830,7 +908,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                    GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
                    strcpy(param, "@");
                    strcat(param, mii.dwTypeData);
-                   if((int)ShellExecute(hwnd, NULL, putty_path, param,
+                   if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
                                         _T(""), SW_SHOW) <= 32) {
                        MessageBox(NULL, "Unable to execute PuTTY!", "Error",
                                   MB_OK | MB_ICONERROR);
@@ -1841,10 +919,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
        }
        break;
       case WM_DESTROY:
-        if (requested_help) {
-            WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
-            requested_help = FALSE;
-        }
+       quit_help(hwnd);
        PostQuitMessage(0);
        return 0;
       case WM_COPYDATA:
@@ -1854,10 +929,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            void *p;
            HANDLE filemap;
 #ifndef NO_SECURITY
-           HANDLE proc;
-           PSID mapowner, procowner;
-           PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
+           PSID mapowner, ourself, ourself2;
 #endif
+            PSECURITY_DESCRIPTOR psd = NULL;
            int ret = 0;
 
            cds = (COPYDATASTRUCT *) lParam;
@@ -1877,46 +951,59 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 #ifndef NO_SECURITY
                int rc;
                if (has_security) {
-                   if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
-                                           GetCurrentProcessId())) ==
-                       NULL) {
+                    if ((ourself = get_user_sid()) == NULL) {
 #ifdef DEBUG_IPC
-                       debug(("couldn't get handle for process\n"));
+                       debug(("couldn't get user SID\n"));
 #endif
+                        CloseHandle(filemap);
                        return 0;
-                   }
-                   if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
-                                       OWNER_SECURITY_INFORMATION,
-                                       &procowner, NULL, NULL, NULL,
-                                       &psd2) != ERROR_SUCCESS) {
+                    }
+
+                    if ((ourself2 = get_default_sid()) == NULL) {
 #ifdef DEBUG_IPC
-                       debug(("couldn't get owner info for process\n"));
+                       debug(("couldn't get default SID\n"));
 #endif
-                       CloseHandle(proc);
-                       return 0;      /* unable to get security info */
-                   }
-                   CloseHandle(proc);
-                   if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
-                                             OWNER_SECURITY_INFORMATION,
-                                             &mapowner, NULL, NULL, NULL,
-                                             &psd1) != ERROR_SUCCESS)) {
+                        CloseHandle(filemap);
+                       return 0;
+                    }
+
+                   if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
+                                               OWNER_SECURITY_INFORMATION,
+                                               &mapowner, NULL, NULL, NULL,
+                                               &psd) != ERROR_SUCCESS)) {
 #ifdef DEBUG_IPC
-                       debug(
-                             ("couldn't get owner info for filemap: %d\n",
-                              rc));
+                       debug(("couldn't get owner info for filemap: %d\n",
+                               rc));
 #endif
+                        CloseHandle(filemap);
+                        sfree(ourself2);
                        return 0;
                    }
 #ifdef DEBUG_IPC
-                   debug(("got security stuff\n"));
+                    {
+                        LPTSTR ours, ours2, theirs;
+                        ConvertSidToStringSid(mapowner, &theirs);
+                        ConvertSidToStringSid(ourself, &ours);
+                        ConvertSidToStringSid(ourself2, &ours2);
+                        debug(("got sids:\n  oursnew=%s\n  oursold=%s\n"
+                               "  theirs=%s\n", ours, ours2, theirs));
+                        LocalFree(ours);
+                        LocalFree(ours2);
+                        LocalFree(theirs);
+                    }
 #endif
-                   if (!EqualSid(mapowner, procowner))
+                   if (!EqualSid(mapowner, ourself) &&
+                        !EqualSid(mapowner, ourself2)) {
+                        CloseHandle(filemap);
+                        LocalFree(psd);
+                        sfree(ourself2);
                        return 0;      /* security ID mismatch! */
+                    }
 #ifdef DEBUG_IPC
                    debug(("security stuff matched\n"));
 #endif
-                   LocalFree(psd1);
-                   LocalFree(psd2);
+                    LocalFree(psd);
+                    sfree(ourself2);
                } else {
 #ifdef DEBUG_IPC
                    debug(("security APIs not present\n"));
@@ -1929,9 +1016,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                {
                    int i;
                    for (i = 0; i < 5; i++)
-                       debug(
-                             ("p[%d]=%02x\n", i,
-                              ((unsigned char *) p)[i]));}
+                       debug(("p[%d]=%02x\n", i,
+                              ((unsigned char *) p)[i]));
+                }
 #endif
                answer_msg(p);
                ret = 1;
@@ -1948,7 +1035,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 /*
  * Fork and Exec the command in cmdline. [DBW]
  */
-void spawn_cmd(char *cmdline, char * args, int show)
+void spawn_cmd(const char *cmdline, const char *args, int show)
 {
     if (ShellExecute(NULL, _T("open"), cmdline,
                     args, NULL, show) <= (HINSTANCE) 32) {
@@ -1970,7 +1057,11 @@ void agent_schedule_callback(void (*callback)(void *, void *, int),
     assert(!"We shouldn't get here");
 }
 
-void cleanup_exit(int code) { exit(code); }
+void cleanup_exit(int code)
+{
+    shutdown_help();
+    exit(code);
+}
 
 int flags = FLAG_SYNCAGENT;
 
@@ -1978,12 +1069,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
     WNDCLASS wndclass;
     MSG msg;
-    HMODULE advapi;
-    char *command = NULL;
+    const char *command = NULL;
     int added_keys = 0;
     int argc, i;
     char **argv, **argstart;
 
+    dll_hijacking_protection();
+
+    hinst = inst;
+    hwnd = NULL;
+
     /*
      * Determine whether we're an NT system (should have security
      * APIs) or a non-NT system (don't do security).
@@ -2002,10 +1097,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        /*
         * Attempt to get the security API we need.
         */
-       advapi = LoadLibrary("ADVAPI32.DLL");
-       getsecurityinfo =
-           (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
-       if (!getsecurityinfo) {
+        if (!got_advapi()) {
            MessageBox(NULL,
                       "Unable to access security APIs. Pageant will\n"
                       "not run, in case it causes a security breach.",
@@ -2019,30 +1111,12 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                   "Pageant Fatal Error", MB_ICONERROR | MB_OK);
        return 1;
 #endif
-    } else
-       advapi = NULL;
-
-    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;
-    }
+    init_help();
 
     /*
      * Look for the PuTTY binary (we will enable the saved session
@@ -2051,7 +1125,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     {
         char b[2048], *p, *q, *r;
         FILE *fp;
-        GetModuleFileName(NULL, b, sizeof(b) - 1);
+        GetModuleFileName(NULL, b, sizeof(b) - 16);
         r = b;
         p = strrchr(b, '\\');
         if (p && p >= r) r = p+1;
@@ -2068,80 +1142,28 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     /*
      * Find out if Pageant is already running.
      */
-    already_running = FALSE;
-    if (agent_exists())
-       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);
-       }
-
-       main_hwnd = keylist = 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(main_hwnd);
-
-        /* Accelerators used: nsvkxa */
-        systray_menu = CreatePopupMenu();
-       if (putty_path) {
-           session_menu = CreateMenu();
-           AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
-           AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
-                      (UINT) session_menu, "&Saved Sessions");
-           AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
-       }
-        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");
-       initial_menuitems_count = GetMenuItemCount(session_menu);
-
-       /* Set the default menu item. */
-       SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
-
-       ShowWindow(main_hwnd, SW_HIDE);
-
-       /*
-        * Initialise storage for RSA keys.
-        */
-       rsakeys = newtree234(cmpkeys_rsa);
-       ssh2keys = newtree234(cmpkeys_ssh2);
-
-    }
+    already_running = agent_exists();
 
     /*
-     * Initialise storage for short-term passphrase cache.
+     * Initialise the cross-platform Pageant code.
      */
-    passphrases = newtree234(NULL);
+    if (!already_running) {
+        pageant_init();
+    }
 
     /*
      * Process the command line and add keys as listed on it.
      */
     split_into_argv(cmdline, &argc, &argv, &argstart);
     for (i = 0; i < argc; i++) {
-       if (!strcmp(argv[i], "-c")) {
+       if (!strcmp(argv[i], "-pgpfp")) {
+           pgp_fingerprints();
+           return 1;
+        } else if (!strcmp(argv[i], "-restrict-acl") ||
+                   !strcmp(argv[i], "-restrict_acl") ||
+                   !strcmp(argv[i], "-restrictacl")) {
+            restrict_process_acl();
+       } else if (!strcmp(argv[i], "-c")) {
            /*
             * If we see `-c', then the rest of the
             * command line should be treated as a
@@ -2153,7 +1175,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                command = "";
            break;
        } else {
-           add_keyfile(filename_from_str(argv[i]));
+            Filename *fn = filename_from_str(argv[i]);
+           win_add_keyfile(fn);
+            filename_free(fn);
            added_keys = TRUE;
        }
     }
@@ -2162,7 +1186,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * Forget any passphrase that we retained while going over
      * command line keyfiles.
      */
-    forget_passphrases();
+    pageant_forget_passphrases();
 
     if (command) {
        char *args;
@@ -2187,11 +1211,59 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            MessageBox(NULL, "Pageant is already running", "Pageant Error",
                       MB_ICONERROR | MB_OK);
        }
-       if (advapi)
-           FreeLibrary(advapi);
        return 0;
     }
 
+    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);
+    }
+
+    keylist = 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 */
+    AddTrayIcon(hwnd);
+
+    /* Accelerators used: nsvkxa */
+    systray_menu = CreatePopupMenu();
+    if (putty_path) {
+       session_menu = CreateMenu();
+       AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
+       AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
+                  (UINT_PTR) session_menu, "&Saved Sessions");
+       AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+    }
+    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 (has_help())
+       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");
+    initial_menuitems_count = GetMenuItemCount(session_menu);
+
+    /* Set the default menu item. */
+    SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
+
+    ShowWindow(hwnd, SW_HIDE);
+
     /*
      * Main message loop.
      */
@@ -2208,7 +1280,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        NOTIFYICONDATA tnid;
 
        tnid.cbSize = sizeof(NOTIFYICONDATA);
-       tnid.hWnd = main_hwnd;
+       tnid.hWnd = hwnd;
        tnid.uID = 1;
 
        Shell_NotifyIcon(NIM_DELETE, &tnid);
@@ -2216,7 +1288,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        DestroyMenu(systray_menu);
     }
 
-    if (advapi)
-       FreeLibrary(advapi);
-    return msg.wParam;
+    if (keypath) filereq_free(keypath);
+
+    cleanup_exit(msg.wParam);
+    return msg.wParam;                /* just in case optimiser complains */
 }