Preliminary support for RSA user authentication in SSH2! Most of the
[u/mdw/putty] / pageant.c
index d7bd94c..f4d5aa5 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -3,8 +3,12 @@
  */
 
 #include <windows.h>
-#include <stdio.h> /* FIXME */
-#include "putty.h" /* FIXME */
+#ifndef NO_SECURITY
+#include <aclapi.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
 #include "ssh.h"
 #include "tree234.h"
 
 #define WM_XUSER     (WM_USER + 0x2000)
 #define WM_SYSTRAY   (WM_XUSER + 6)
 #define WM_SYSTRAY2  (WM_XUSER + 7)
-#define WM_CLOSEMEM  (WM_XUSER + 10)
+
+#define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
+
+/*
+ * FIXME: maybe some day we can sort this out ...
+ */
+#define AGENT_MAX_MSGLEN  8192
 
 #define IDM_CLOSE    0x0010
 #define IDM_VIEWKEYS 0x0020
+#define IDM_ADDKEY   0x0030
+#define IDM_ABOUT    0x0040
 
 #define APPNAME "Pageant"
 
-#define MAILSLOTNAME "\\\\.\\mailslot\\pageant_listener"
-
 #define SSH_AGENTC_REQUEST_RSA_IDENTITIES    1
 #define SSH_AGENT_RSA_IDENTITIES_ANSWER      2
 #define SSH_AGENTC_RSA_CHALLENGE             3
 #define SSH_AGENTC_ADD_RSA_IDENTITY          7
 #define SSH_AGENTC_REMOVE_RSA_IDENTITY       8
 
-HINSTANCE instance;
-HWND hwnd;
-HWND keylist;
-HMENU systray_menu;
+extern char ver[];
+
+static HINSTANCE instance;
+static HWND hwnd;
+static HWND keylist;
+static HWND aboutbox;
+static HMENU systray_menu;
 
-tree234 *rsakeys;
+static tree234 *rsakeys;
+
+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
 
 /*
  * We need this to link with the RSA code, because rsaencrypt()
@@ -74,16 +96,81 @@ void logevent(char *msg) {
 
 #define PASSPHRASE_MAXLEN 512
 
+struct PassphraseProcStruct {
+    char *passphrase;
+    char *comment;
+};
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static int CALLBACK LicenceProc (HWND hwnd, UINT msg,
+                                WPARAM wParam, LPARAM lParam) {
+    switch (msg) {
+      case WM_INITDIALOG:
+       return 1;
+      case WM_COMMAND:
+       switch (LOWORD(wParam)) {
+         case IDOK:
+            EndDialog(hwnd, 1);
+           return 0;
+       }
+       return 0;
+      case WM_CLOSE:
+       EndDialog(hwnd, 1);
+       return 0;
+    }
+    return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static int CALLBACK AboutProc (HWND hwnd, UINT msg,
+                              WPARAM wParam, LPARAM lParam) {
+    switch (msg) {
+      case WM_INITDIALOG:
+        SetDlgItemText (hwnd, 100, ver);
+       return 1;
+      case WM_COMMAND:
+       switch (LOWORD(wParam)) {
+         case IDOK:
+           aboutbox = NULL;
+           DestroyWindow (hwnd);
+           return 0;
+         case 101:
+           EnableWindow(hwnd, 0);
+           DialogBox (instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
+           EnableWindow(hwnd, 1);
+            SetActiveWindow(hwnd);
+           return 0;
+       }
+       return 0;
+      case WM_CLOSE:
+       aboutbox = NULL;
+       DestroyWindow (hwnd);
+       return 0;
+    }
+    return 0;
+}
+
 /*
  * Dialog-box function for the passphrase box.
  */
 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
                                    WPARAM wParam, LPARAM lParam) {
     static char *passphrase;
+    struct PassphraseProcStruct *p;
 
     switch (msg) {
       case WM_INITDIALOG:
-        passphrase = (char *)lParam;
+        SetForegroundWindow(hwnd);
+        SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
+                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+        p = (struct PassphraseProcStruct *)lParam;
+        passphrase = p->passphrase;
+        if (p->comment)
+            SetDlgItemText(hwnd, 101, p->comment);
         *passphrase = 0;
         return 0;
       case WM_COMMAND:
@@ -113,26 +200,56 @@ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
 }
 
 /*
+ * Update the visible key list.
+ */
+static void keylist_update(void) {
+    struct RSAKey *key;
+    enum234 e;
+
+    if (keylist) {
+        SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
+        for (key = first234(rsakeys, &e); key; key = next234(&e)) {
+            char listentry[512], *p;
+            /*
+             * Replace two spaces in the fingerprint with tabs, for
+             * nice alignment in the box.
+             */
+            rsa_fingerprint(listentry, sizeof(listentry), key);
+            p = strchr(listentry, ' '); if (p) *p = '\t';
+            p = strchr(listentry, ' '); if (p) *p = '\t';
+            SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
+                                0, (LPARAM)listentry);
+        }
+        SendDlgItemMessage (keylist, 100, LB_SETCURSEL, (WPARAM) -1, 0);
+    }
+}
+
+/*
  * This function loads a key from a file and adds it.
  */
-void add_keyfile(char *filename) {
+static void add_keyfile(char *filename) {
     char passphrase[PASSPHRASE_MAXLEN];
     struct RSAKey *key;
     int needs_pass;
     int ret;
     int attempts;
+    char *comment;
+    struct PassphraseProcStruct pps;
 
-    needs_pass = rsakey_encrypted(filename);
+    needs_pass = rsakey_encrypted(filename, &comment);
     attempts = 0;
-    key = malloc(sizeof(*key));
+    key = smalloc(sizeof(*key));
+    pps.passphrase = passphrase;
+    pps.comment = comment;
     do {
         if (needs_pass) {
             int dlgret;
             dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
                                     NULL, PassphraseProc,
-                                    (LPARAM)passphrase);
+                                    (LPARAM)&pps);
             if (!dlgret) {
-                free(key);
+                if (comment) sfree(comment);
+                sfree(key);
                 return;                /* operation cancelled */
             }
         } else
@@ -140,39 +257,31 @@ void add_keyfile(char *filename) {
         ret = loadrsakey(filename, key, passphrase);
         attempts++;
     } while (ret == -1);
+    if (comment) sfree(comment);
     if (ret == 0) {
-        MessageBox(NULL, "Couldn't load public key.", APPNAME,
+        MessageBox(NULL, "Couldn't load private key.", APPNAME,
                    MB_OK | MB_ICONERROR);
-        free(key);
+        sfree(key);
         return;
     }
     if (add234(rsakeys, key) != key)
-        free(key);                     /* already present, don't waste RAM */
+        sfree(key);                     /* already present, don't waste RAM */
 }
 
 /*
  * This is the main agent function that answers messages.
  */
-void answer_msg(void *in, int inlen, void **out, int *outlen) {
-    unsigned char *ret;
-    unsigned char *p = in;
+static void answer_msg(void *msg) {
+    unsigned char *p = msg;
+    unsigned char *ret = msg;
     int type;
 
-    *out = NULL;                       /* default `no go' response */
-
-    /*
-     * Basic sanity checks. len >= 5, and len[0:4] holds len-4.
-     */
-    if (inlen < 5 || GET_32BIT(p) != (unsigned long)(inlen-4))
-        return;
-
     /*
      * Get the message type.
      */
     type = p[4];
 
     p += 5;
-
     switch (type) {
       case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
         /*
@@ -200,20 +309,20 @@ void answer_msg(void *in, int inlen, void **out, int *outlen) {
              * bytes for the key count.
              */
             len += 5 + 4;
-            if ((ret = malloc(len)) != NULL) {
-                PUT_32BIT(ret, len-4);
-                ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER;
-                PUT_32BIT(ret+5, nkeys);
-                p = ret + 5 + 4;
-                for (key = first234(rsakeys, &e); key; key = next234(&e)) {
-                    PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
-                    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);
-                }
+            if (len > AGENT_MAX_MSGLEN)
+                goto failure;          /* aaargh! too much stuff! */
+            PUT_32BIT(ret, len-4);
+            ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER;
+            PUT_32BIT(ret+5, nkeys);
+            p = ret + 5 + 4;
+            for (key = first234(rsakeys, &e); key; key = next234(&e)) {
+                PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
+                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);
             }
         }
         break;
@@ -260,19 +369,41 @@ void answer_msg(void *in, int inlen, void **out, int *outlen) {
              * bytes of MD5.
              */
             len = 5 + 16;
-            if ((ret = malloc(len)) != NULL) {
-                PUT_32BIT(ret, len-4);
-                ret[4] = SSH_AGENT_RSA_RESPONSE;
-                memcpy(ret+5, response_md5, 16);
-            }
+            PUT_32BIT(ret, len-4);
+            ret[4] = SSH_AGENT_RSA_RESPONSE;
+            memcpy(ret+5, response_md5, 16);
         }
         break;
-#if 0 /* FIXME: implement these */
       case SSH_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;
+            key = smalloc(sizeof(struct RSAKey));
+            memset(key, 0, sizeof(key));
+            p += makekey(p, key, NULL, 1);
+            p += makeprivate(p, key);
+            p += ssh1_read_bignum(p, NULL);    /* p^-1 mod q */
+            p += ssh1_read_bignum(p, NULL);    /* p */
+            p += ssh1_read_bignum(p, NULL);    /* q */
+            comment = smalloc(GET_32BIT(p));
+            if (comment) {
+                memcpy(comment, p+4, GET_32BIT(p));
+                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 SSH_AGENTC_REMOVE_RSA_IDENTITY:
         /*
@@ -280,30 +411,38 @@ void answer_msg(void *in, int inlen, void **out, int *outlen) {
          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
          * start with.
          */
+        {
+            struct RSAKey reqkey, *key;
+
+            p += makekey(p, &reqkey, NULL, 0);
+            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);
+                ret[4] = SSH_AGENT_SUCCESS;
+            }
+        }
         break;
-#endif
       default:
         failure:
         /*
          * Unrecognised message. Return SSH_AGENT_FAILURE.
          */
-        if ((ret = malloc(5)) != NULL) {
-            PUT_32BIT(ret, 1);
-            ret[4] = SSH_AGENT_FAILURE;
-        }
+        PUT_32BIT(ret, 1);
+        ret[4] = SSH_AGENT_FAILURE;
         break;
     }
-
-    if (ret) {
-        *out = ret;
-        *outlen = 4 + GET_32BIT(ret);
-    }
 }
 
 /*
  * Key comparison function for the 2-3-4 tree of RSA keys.
  */
-int cmpkeys(void *av, void *bv) {
+static int cmpkeys(void *av, void *bv) {
     struct RSAKey *a = (struct RSAKey *)av;
     struct RSAKey *b = (struct RSAKey *)bv;
     Bignum am, bm;
@@ -338,21 +477,50 @@ static void error(char *s) {
 }
 
 /*
+ * Prompt for a key file to add, and add it.
+ */
+static void prompt_add_keyfile(void) {
+    OPENFILENAME of;
+    char filename[FILENAME_MAX];
+    memset(&of, 0, sizeof(of));
+#ifdef OPENFILENAME_SIZE_VERSION_400
+    of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+    of.lStructSize = sizeof(of);
+#endif
+    of.hwndOwner = hwnd;
+    of.lpstrFilter = "All Files\0*\0\0\0";
+    of.lpstrCustomFilter = NULL;
+    of.nFilterIndex = 1;
+    of.lpstrFile = filename; *filename = '\0';
+    of.nMaxFile = sizeof(filename);
+    of.lpstrFileTitle = NULL;
+    of.lpstrInitialDir = NULL;
+    of.lpstrTitle = "Select Private Key File";
+    of.Flags = 0;
+    if (GetOpenFileName(&of)) {
+        add_keyfile(filename);
+        keylist_update();
+    }
+}
+
+/*
  * Dialog-box function for the key list box.
  */
 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                 WPARAM wParam, LPARAM lParam) {
     enum234 e;
     struct RSAKey *key;
-    OPENFILENAME of;
-    char filename[FILENAME_MAX];
 
     switch (msg) {
       case WM_INITDIALOG:
-        for (key = first234(rsakeys, &e); key; key = next234(&e)) {
-            SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
-                                0, (LPARAM) key->comment);
-        }
+        keylist = hwnd;
+       {
+           static int tabs[2] = {25, 175};
+           SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS, 2,
+                               (LPARAM) tabs);
+       }
+        keylist_update();
         return 0;
       case WM_COMMAND:
        switch (LOWORD(wParam)) {
@@ -364,38 +532,14 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
           case 101:                    /* add key */
            if (HIWORD(wParam) == BN_CLICKED ||
                HIWORD(wParam) == BN_DOUBLECLICKED) {
-                memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-                of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-                of.lStructSize = sizeof(of);
-#endif
-                of.hwndOwner = hwnd;
-                of.lpstrFilter = "All Files\0*\0\0\0";
-                of.lpstrCustomFilter = NULL;
-                of.nFilterIndex = 1;
-                of.lpstrFile = filename; *filename = '\0';
-                of.nMaxFile = sizeof(filename);
-                of.lpstrFileTitle = NULL;
-                of.lpstrInitialDir = NULL;
-                of.lpstrTitle = "Select Public Key File";
-                of.Flags = 0;
-                if (GetOpenFileName(&of)) {
-                    add_keyfile(filename);
-                }
-                SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0);
-                for (key = first234(rsakeys, &e); key; key = next234(&e)) {
-                    SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
-                                        0, (LPARAM) key->comment);
-                }
-               SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0);
+                prompt_add_keyfile();
             }
             return 0;
           case 102:                    /* remove key */
            if (HIWORD(wParam) == BN_CLICKED ||
                HIWORD(wParam) == BN_DOUBLECLICKED) {
                int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0);
-               if (n == LB_ERR || n == 0) {
+               if (n == LB_ERR) {
                    MessageBeep(0);
                    break;
                }
@@ -404,12 +548,7 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                         break;
                 del234(rsakeys, key);
                 freersakey(key); free(key);
-                SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0);
-                for (key = first234(rsakeys, &e); key; key = next234(&e)) {
-                    SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
-                                        0, (LPARAM) key->comment);
-                }
-               SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0);
+                keylist_update();
             }
             return 0;
        }
@@ -433,6 +572,9 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
             POINT cursorpos;
             GetCursorPos(&cursorpos);
             PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
+        } else if (lParam == WM_LBUTTONDBLCLK) {
+            /* Equivalent to IDM_VIEWKEYS. */
+            PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
         }
         break;
       case WM_SYSTRAY2:
@@ -457,6 +599,30 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
                 keylist = CreateDialog (instance, MAKEINTRESOURCE(211),
                                         NULL, KeyListProc);
                 ShowWindow (keylist, SW_SHOWNORMAL);
+                /* 
+                 * Sometimes the window comes up minimised / hidden
+                 * for no obvious reason. Prevent this.
+                 */
+                SetForegroundWindow(keylist);
+                SetWindowPos (keylist, HWND_TOP, 0, 0, 0, 0,
+                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+            }
+            break;
+          case IDM_ADDKEY:
+            prompt_add_keyfile();
+            break;
+          case IDM_ABOUT:
+            if (!aboutbox) {
+                aboutbox = CreateDialog (instance, MAKEINTRESOURCE(213),
+                                         NULL, AboutProc);
+                ShowWindow (aboutbox, SW_SHOWNORMAL);
+                /* 
+                 * Sometimes the window comes up minimised / hidden
+                 * for no obvious reason. Prevent this.
+                 */
+                SetForegroundWindow(aboutbox);
+                SetWindowPos (aboutbox, HWND_TOP, 0, 0, 0, 0,
+                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
             }
             break;
         }
@@ -467,45 +633,85 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
       case WM_COPYDATA:
         {
             COPYDATASTRUCT *cds;
-            void *in, *out, *ret;
-            int inlen, outlen;
-            HANDLE filemap;
-            char mapname[64];
-            int id;
+            char *mapname;
+            void *p;
+            HANDLE filemap, proc;
+            PSID mapowner, procowner;
+            PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
+            int ret = 0;
 
             cds = (COPYDATASTRUCT *)lParam;
-            /*
-             * FIXME: use dwData somehow.
-             */
-            in = cds->lpData;
-            inlen = cds->cbData;
-            answer_msg(in, inlen, &out, &outlen);
-            if (out) {
-                id = 0;
-                do {
-                    sprintf(mapname, "PageantReply%08x", ++id);
-                   filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
-                                                NULL, PAGE_READWRITE,
-                                               0, outlen+sizeof(int),
-                                                mapname);
-                } while (filemap == INVALID_HANDLE_VALUE);
-                ret = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0,
-                                    outlen+sizeof(int));
-                if (ret) {
-                    *((int *)ret) = outlen;
-                    memcpy(((int *)ret)+1, out, outlen);
-                    UnmapViewOfFile(ret);
-                    return id;
+            if (cds->dwData != AGENT_COPYDATA_ID)
+                return 0;              /* not our message, mate */
+            mapname = (char *)cds->lpData;
+            if (mapname[cds->cbData - 1] != '\0')
+                return 0;              /* failure to be ASCIZ! */
+#ifdef DEBUG_IPC
+            debug(("mapname is :%s:\r\n", mapname));
+#endif
+            filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
+#ifdef DEBUG_IPC
+            debug(("filemap is %p\r\n", filemap));
+#endif
+            if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
+                int rc;
+#ifndef NO_SECURITY
+                if (has_security) {
+                    if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
+                                            GetCurrentProcessId())) == NULL) {
+#ifdef DEBUG_IPC
+                        debug(("couldn't get handle for process\r\n"));
+#endif
+                        return 0;
+                    }
+                    if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
+                                        OWNER_SECURITY_INFORMATION,
+                                        &procowner, NULL, NULL, NULL,
+                                        &psd2) != ERROR_SUCCESS) {
+#ifdef DEBUG_IPC
+                        debug(("couldn't get owner info for process\r\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)) {
+#ifdef DEBUG_IPC
+                        debug(("couldn't get owner info for filemap: %d\r\n", rc));
+#endif
+                        return 0;
+                    }
+#ifdef DEBUG_IPC
+                    debug(("got security stuff\r\n"));
+#endif
+                    if (!EqualSid(mapowner, procowner))
+                        return 0;          /* security ID mismatch! */
+#ifdef DEBUG_IPC
+                    debug(("security stuff matched\r\n"));
+#endif
+                    LocalFree(psd1);
+                    LocalFree(psd2);
+                } else {
+#ifdef DEBUG_IPC
+                    debug(("security APIs not present\r\n"));
+#endif
                 }
-            } else
-                return 0;              /* invalid request */
+#endif
+                p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+#ifdef DEBUG_IPC
+                debug(("p is %p\r\n", p));
+                {int i; for(i=0;i<5;i++)debug(("p[%d]=%02x\r\n", i, ((unsigned char *)p)[i]));}
+#endif
+                answer_msg(p);
+                ret = 1;
+                UnmapViewOfFile(p);
+            }
+            CloseHandle(filemap);
+            return ret;
         }
-        break;
-      case WM_CLOSEMEM:
-        /*
-         * FIXME!
-         */
-        break;
     }
 
     return DefWindowProc (hwnd, message, wParam, lParam);
@@ -513,8 +719,54 @@ static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
 
 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
     WNDCLASS wndclass;
-    HANDLE mailslot;
     MSG msg;
+    OSVERSIONINFO osi;
+    HMODULE advapi;
+
+    /*
+     * Determine whether we're an NT system (should have security
+     * APIs) or a non-NT system (don't do security).
+     */
+    memset(&osi, 0, sizeof(OSVERSIONINFO));
+    osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    if (GetVersionEx(&osi) && osi.dwPlatformId==VER_PLATFORM_WIN32_NT) {
+        has_security = TRUE;
+    } else
+        has_security = FALSE;
+
+    if (has_security) {
+#ifndef NO_SECURITY
+        /*
+         * Attempt to ge the security API we need.
+         */
+        advapi = LoadLibrary("ADVAPI32.DLL");
+        getsecurityinfo = (gsi_fn_t)GetProcAddress(advapi, "GetSecurityInfo");
+        if (!getsecurityinfo) {
+            MessageBox(NULL,
+                       "Unable to access security APIs. Pageant will\n"
+                       "not run, in case it causes a security breach.",
+                       "Pageant Fatal Error", MB_ICONERROR | MB_OK);
+            return 1;
+        }
+#else
+       MessageBox(NULL,
+                  "This program has been compiled for Win9X and will\n"
+                  "not run on NT, in case it causes a security breach.",
+                  "Pageant Fatal Error", MB_ICONERROR | MB_OK);
+       return 1;
+#endif
+    } else
+        advapi = NULL;
+
+    /*
+     * First bomb out totally if we are already running.
+     */
+    if (FindWindow("Pageant", "Pageant")) {
+        MessageBox(NULL, "Pageant is already running", "Pageant Error",
+                   MB_ICONERROR | MB_OK);
+        if (advapi) FreeLibrary(advapi);
+        return 0;
+    }
 
     instance = inst;
 
@@ -566,28 +818,52 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
             DestroyIcon(hicon); 
 
         systray_menu = CreatePopupMenu();
-        AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "View Keys");
-        AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "Terminate");
+        /* accelerators used: vkxa */
+        AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
+        AppendMenu (systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
+        AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+        AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
     }
 
     ShowWindow (hwnd, SW_HIDE);
 
     /*
-     * Create the mailslot.
+     * Initialise storage for RSA keys.
+     */
+    rsakeys = newtree234(cmpkeys);
+
+    /*
+     * Process the command line and add RSA keys as listed on it.
      */
     {
-        SECURITY_ATTRIBUTES sa;
-        sa.nLength = sizeof(sa);
-        sa.lpSecurityDescriptor = NULL;
-        sa.bInheritHandle = TRUE;
-        mailslot = CreateMailslot(MAILSLOTNAME, 0, 0, &sa);
+        char *p;
+        int inquotes = 0;
+        p = cmdline;
+        while (*p) {
+            while (*p && isspace(*p)) p++;
+            if (*p && !isspace(*p)) {
+                char *q = p, *pp = p;
+                while (*p && (inquotes || !isspace(*p)))
+                {
+                    if (*p == '"') {
+                        inquotes = !inquotes;
+                        p++;
+                        continue;
+                    }
+                    *pp++ = *p++;
+                }
+                if (*pp) {
+                    if (*p) p++;
+                    *pp++ = '\0';
+                }
+                add_keyfile(q);
+            }
+        }
     }
 
     /*
-     * Initialise storage for RSA keys.
+     * Main message loop.
      */
-    rsakeys = newtree234(cmpkeys);
-
     while (GetMessage(&msg, NULL, 0, 0) == 1) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
@@ -606,5 +882,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
         DestroyMenu(systray_menu);
     }
 
+    if (advapi) FreeLibrary(advapi);
     exit(msg.wParam);
 }