Rework keylist_update() to fix both a buffer-size limitation and a
[u/mdw/putty] / windows / winpgnt.c
index 07d59d6..e220d6b 100644 (file)
 
 #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 */
 
@@ -114,10 +117,10 @@ 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;
+DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
                    (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
+                      PSID *, PSID *, PACL *, PACL *,
+                      PSECURITY_DESCRIPTOR *));
 #endif
 
 /*
@@ -156,22 +159,8 @@ struct blob {
 };
 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;
 };
 
@@ -185,7 +174,7 @@ static void forget_passphrases(void)
 {
     while (count234(passphrases) > 0) {
        char *pp = index234(passphrases, 0);
-       memset(pp, 0, strlen(pp));
+       smemclr(pp, strlen(pp));
        delpos234(passphrases, 0);
        free(pp);
     }
@@ -256,7 +245,7 @@ static HWND passphrase_box;
 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
                                   WPARAM wParam, LPARAM lParam)
 {
-    static char *passphrase = NULL;
+    static char **passphrase = NULL;
     struct PassphraseProcStruct *p;
 
     switch (msg) {
@@ -284,8 +273,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)) {
@@ -300,9 +290,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;
        }
@@ -364,28 +353,27 @@ static void keylist_update(void)
                               0, (LPARAM) listentry);
        }
        for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
-           char listentry[512], *p;
-           int len;
+           char *listentry, *p;
+           int fp_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));
+            listentry = dupprintf("%s\t%s", p, skey->comment);
+            fp_len = strlen(listentry);
+            sfree(p);
+
            p = strchr(listentry, ' ');
-           if (p)
+           if (p && p < listentry + fp_len)
                *p = '\t';
            p = strchr(listentry, ' ');
-           if (p)
+           if (p && p < listentry + fp_len)
                *p = '\t';
-           len = strlen(listentry);
-           if (len < sizeof(listentry) - 2) {
-               listentry[len] = '\t';
-               strncpy(listentry + len + 1, skey->comment,
-                       sizeof(listentry) - len - 1);
-           }
+
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
                               (LPARAM) listentry);
+            sfree(listentry);
        }
        SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
     }
@@ -394,9 +382,9 @@ static void keylist_update(void)
 /*
  * This function loads a key from a file and adds it.
  */
-static void add_keyfile(Filename filename)
+static void add_keyfile(Filename *filename)
 {
-    char passphrase[PASSPHRASE_MAXLEN];
+    char *passphrase;
     struct RSAKey *rkey = NULL;
     struct ssh2_userkey *skey = NULL;
     int needs_pass;
@@ -404,11 +392,10 @@ static void add_keyfile(Filename filename)
     int attempts;
     char *comment;
     const char *error = NULL;
-    struct PassphraseProcStruct pps;
     int type;
     int original_pass;
        
-    type = key_type(&filename);
+    type = key_type(filename);
     if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
        char *msg = dupprintf("Couldn't load this key (%s)",
                              key_type_to_str(type));
@@ -428,7 +415,7 @@ static void add_keyfile(Filename filename)
        int i, nkeys, bloblen, keylistlen;
 
        if (type == SSH_KEYTYPE_SSH1) {
-           if (!rsakey_pubblob(&filename, &blob, &bloblen, &error)) {
+           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
                char *msg = dupprintf("Couldn't load private key (%s)", error);
                message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
                            HELPCTXID(errors_cantloadkey));
@@ -438,7 +425,8 @@ static void add_keyfile(Filename filename)
            keylist = get_keylist1(&keylistlen);
        } else {
            unsigned char *blob2;
-           blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, &error);
+           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
+                                       NULL, &error);
            if (!blob) {
                char *msg = dupprintf("Couldn't load private key (%s)", error);
                message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
@@ -461,7 +449,12 @@ static void add_keyfile(Filename filename)
                           MB_OK | MB_ICONERROR);
                return;
            }
-           nkeys = GET_32BIT(keylist);
+           nkeys = toint(GET_32BIT(keylist));
+           if (nkeys < 0) {
+               MessageBox(NULL, "Received broken key list?!", APPNAME,
+                          MB_OK | MB_ICONERROR);
+               return;
+           }
            p = keylist + 4;
            keylistlen -= 4;
 
@@ -489,8 +482,8 @@ static void add_keyfile(Filename filename)
                                   MB_OK | MB_ICONERROR);
                        return;
                    }
-                   n = 4 + GET_32BIT(p);
-                   if (keylistlen < n) {
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
                        MessageBox(NULL, "Received broken key list?!", APPNAME,
                                   MB_OK | MB_ICONERROR);
                        return;
@@ -506,8 +499,8 @@ static void add_keyfile(Filename filename)
                                   MB_OK | MB_ICONERROR);
                        return;
                    }
-                   n = 4 + GET_32BIT(p);
-                   if (keylistlen < n) {
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
                        MessageBox(NULL, "Received broken key list?!", APPNAME,
                                   MB_OK | MB_ICONERROR);
                        return;
@@ -525,23 +518,30 @@ static void add_keyfile(Filename filename)
 
     error = NULL;
     if (type == SSH_KEYTYPE_SSH1)
-       needs_pass = rsakey_encrypted(&filename, &comment);
+       needs_pass = rsakey_encrypted(filename, &comment);
     else
-       needs_pass = ssh2_userkey_encrypted(&filename, &comment);
+       needs_pass = ssh2_userkey_encrypted(filename, &comment);
     attempts = 0;
     if (type == SSH_KEYTYPE_SSH1)
        rkey = snew(struct RSAKey);
-    pps.passphrase = passphrase;
-    pps.comment = comment;
+    passphrase = NULL;
     original_pass = 0;
     do {
+        burnstr(passphrase);
+        passphrase = NULL;
+
        if (needs_pass) {
            /* try all the remembered passphrases first */
            char *pp = index234(passphrases, attempts);
            if(pp) {
-               strcpy(passphrase, pp);
+               passphrase = dupstr(pp);
            } else {
                int dlgret;
+                struct PassphraseProcStruct pps;
+
+                pps.passphrase = &passphrase;
+                pps.comment = comment;
+
                original_pass = 1;
                dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
                                        NULL, PassphraseProc, (LPARAM) &pps);
@@ -553,13 +553,16 @@ static void add_keyfile(Filename filename)
                        sfree(rkey);
                    return;                    /* operation cancelled */
                }
+
+                assert(passphrase != NULL);
            }
        } else
-           *passphrase = '\0';
+           passphrase = dupstr("");
+
        if (type == SSH_KEYTYPE_SSH1)
-           ret = loadrsakey(&filename, rkey, passphrase, &error);
+           ret = loadrsakey(filename, rkey, passphrase, &error);
        else {
-           skey = ssh2_load_userkey(&filename, passphrase, &error);
+           skey = ssh2_load_userkey(filename, passphrase, &error);
            if (skey == SSH2_WRONG_PASSPHRASE)
                ret = -1;
            else if (!skey)
@@ -570,11 +573,14 @@ static void add_keyfile(Filename filename)
        attempts++;
     } while (ret == -1);
 
-    /* if they typed in an ok passphrase, remember it */
     if(original_pass && ret) {
-       char *pp = dupstr(passphrase);
-       addpos234(passphrases, pp, 0);
+        /* If they typed in an ok passphrase, remember it */
+       addpos234(passphrases, passphrase, 0);
+    } else {
+        /* Otherwise, destroy it */
+        burnstr(passphrase);
     }
+    passphrase = NULL;
 
     if (comment)
        sfree(comment);
@@ -966,7 +972,7 @@ static void answer_msg(void *msg)
            MD5Init(&md5c);
            MD5Update(&md5c, response_source, 48);
            MD5Final(response_md5, &md5c);
-           memset(response_source, 0, 48);     /* burn the evidence */
+           smemclr(response_source, 48);       /* burn the evidence */
            freebn(response);          /* and that evidence */
            freebn(challenge);         /* yes, and that evidence */
            freebn(reqkey.exponent);   /* and free some memory ... */
@@ -996,17 +1002,17 @@ static void answer_msg(void *msg)
 
            if (msgend < p+4)
                goto failure;
-           b.len = GET_32BIT(p);
+           b.len = toint(GET_32BIT(p));
+            if (b.len < 0 || b.len > msgend - (p+4))
+                goto failure;
            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);
+           datalen = toint(GET_32BIT(p));
            p += 4;
-           if (msgend < p+datalen)
+           if (datalen < 0 || datalen > msgend - p)
                goto failure;
            data = p;
            key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
@@ -1079,9 +1085,9 @@ static void answer_msg(void *msg)
                sfree(key);
                goto failure;
            }
-            commentlen = GET_32BIT(p);
+            commentlen = toint(GET_32BIT(p));
 
-           if (msgend < p+commentlen) {
+           if (commentlen < 0 || commentlen > msgend - p) {
                freersakey(key);
                sfree(key);
                goto failure;
@@ -1118,9 +1124,9 @@ static void answer_msg(void *msg)
 
            if (msgend < p+4)
                goto failure;
-           alglen = GET_32BIT(p);
+           alglen = toint(GET_32BIT(p));
            p += 4;
-           if (msgend < p+alglen)
+           if (alglen < 0 || alglen > msgend - p)
                goto failure;
            alg = p;
            p += alglen;
@@ -1154,10 +1160,10 @@ static void answer_msg(void *msg)
                sfree(key);
                goto failure;
            }
-           commlen = GET_32BIT(p);
+           commlen = toint(GET_32BIT(p));
            p += 4;
 
-           if (msgend < p+commlen) {
+           if (commlen < 0 || commlen > msgend - p) {
                key->alg->freekey(key->data);
                sfree(key);
                goto failure;
@@ -1221,10 +1227,10 @@ static void answer_msg(void *msg)
 
            if (msgend < p+4)
                goto failure;
-           b.len = GET_32BIT(p);
+           b.len = toint(GET_32BIT(p));
            p += 4;
 
-           if (msgend < p+b.len)
+           if (b.len < 0 || b.len > msgend - p)
                goto failure;
            b.blob = p;
            p += b.len;
@@ -1482,15 +1488,15 @@ 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;
        {
@@ -1583,29 +1589,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(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 *topic = NULL;
             switch (id) {
-              case 100: topic = "pageant.keylist"; break;
-              case 101: topic = "pageant.addkey"; break;
-              case 102: topic = "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 (topic) {
-               char *cmd = dupprintf("JI(`',`%s')", topic);
-                WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
-               sfree(cmd);
-                requested_help = TRUE;
+               launch_help(hwnd, topic);
             } else {
                 MessageBeep(0);
             }
@@ -1699,6 +1698,53 @@ 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)
 {
@@ -1799,11 +1845,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            }
            break;
          case IDM_HELP:
-            if (help_path) {
-                WinHelp(hwnd, help_path, HELP_COMMAND,
-                        (DWORD)"JI(`',`pageant.general')");
-                requested_help = TRUE;
-            }
+           launch_help(hwnd, WINHELP_CTX_pageant_general);
            break;
          default:
            {
@@ -1830,10 +1872,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
        }
        break;
       case WM_DESTROY:
-        if (requested_help) {
-            WinHelp(hwnd, help_path, HELP_QUIT, 0);
-            requested_help = FALSE;
-        }
+       quit_help(hwnd);
        PostQuitMessage(0);
        return 0;
       case WM_COPYDATA:
@@ -1843,10 +1882,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;
@@ -1866,46 +1904,54 @@ 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
                        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)) {
+                       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
                        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);
                        return 0;      /* security ID mismatch! */
+                    }
 #ifdef DEBUG_IPC
                    debug(("security stuff matched\n"));
 #endif
-                   LocalFree(psd1);
-                   LocalFree(psd2);
+                    LocalFree(psd);
+                    sfree(ourself);
+                    sfree(ourself2);
                } else {
 #ifdef DEBUG_IPC
                    debug(("security APIs not present\n"));
@@ -1918,9 +1964,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;
@@ -1959,7 +2005,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;
 
@@ -1994,10 +2044,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 (!init_advapi()) {
            MessageBox(NULL,
                       "Unable to access security APIs. Pageant will\n"
                       "not run, in case it causes a security breach.",
@@ -2017,22 +2064,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     /*
      * 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_HELP_FILE);
-        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
@@ -2041,7 +2073,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;
@@ -2172,7 +2204,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
           "&View Keys");
     AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
-    if (help_path)
+    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);
@@ -2212,5 +2244,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
     if (advapi)
        FreeLibrary(advapi);
-    return msg.wParam;
+
+    cleanup_exit(msg.wParam);
+    return msg.wParam;                /* just in case optimiser complains */
 }