Fix `win-randseed-location': use SHGetFolderPath() to find the
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 9 Jan 2007 18:05:17 +0000 (18:05 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 9 Jan 2007 18:05:17 +0000 (18:05 +0000)
Application Data directory in preference to the old-fashioned
attempt to find the user's home directory, and use the _local-
machine_ Application Data directory in preference even to that.

SHGetFolderPath() is called via GetProcAddress, so this degrades
gracefully on old Windowses. (Tested myself on Win95.)

As part of this change, we now search for a location for the seed
file separately for reading and writing, so that installing the new
PuTTY should cause a seamless migration as the old seed file is read
from the old location and then a new one written to the new location.

`putty -cleanup' attempts to delete the seed file from _all_
affected locations.

Naturally, a user-specified seed file path in the Registry still
takes priority over all other means of finding the location.

git-svn-id: svn://svn.tartarus.org/sgt/putty@7082 cda61777-01e9-0310-a592-d414129be87e

windows/winstore.c

index 31aa637..c44a9ce 100644 (file)
@@ -9,12 +9,24 @@
 #include "putty.h"
 #include "storage.h"
 
-static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
+#include <shlobj.h>
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
 
-static char seedpath[2 * MAX_PATH + 10] = "\0";
+static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
 
 static const char hex[16] = "0123456789ABCDEF";
 
+static int tried_shgetfolderpath = FALSE;
+static HMODULE shell32_module = NULL;
+typedef HRESULT (WINAPI *p_SHGetFolderPath_t)
+    (HWND, int, HANDLE, DWORD, LPTSTR);
+static p_SHGetFolderPath_t p_SHGetFolderPath = NULL;
+
 static void mungestr(const char *in, char *out)
 {
     int candot = 0;
@@ -415,15 +427,52 @@ void store_host_key(const char *hostname, int port,
 }
 
 /*
- * Find the random seed file path and store it in `seedpath'.
+ * Open (or delete) the random seed file.
  */
-static void get_seedpath(void)
+enum { DEL, OPEN_R, OPEN_W };
+static int try_random_seed(char const *path, int action, HANDLE *ret)
+{
+    if (action == DEL) {
+       remove(path);
+       *ret = INVALID_HANDLE_VALUE;
+       return FALSE;                  /* so we'll do the next ones too */
+    }
+
+    *ret = CreateFile(path,
+                     action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,
+                     action == OPEN_W ? 0 : (FILE_SHARE_READ |
+                                             FILE_SHARE_WRITE),
+                     NULL,
+                     action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,
+                     action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,
+                     NULL);
+
+    return (*ret != INVALID_HANDLE_VALUE);
+}
+
+static HANDLE access_random_seed(int action)
 {
     HKEY rkey;
     DWORD type, size;
+    HANDLE rethandle;
+    char seedpath[2 * MAX_PATH + 10] = "\0";
 
-    size = sizeof(seedpath);
+    /*
+     * Iterate over a selection of possible random seed paths until
+     * we find one that works.
+     * 
+     * We do this iteration separately for reading and writing,
+     * meaning that we will automatically migrate random seed files
+     * if a better location becomes available (by reading from the
+     * best location in which we actually find one, and then
+     * writing to the best location in which we can _create_ one).
+     */
 
+    /*
+     * First, try the location specified by the user in the
+     * Registry, if any.
+     */
+    size = sizeof(seedpath);
     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==
        ERROR_SUCCESS) {
        int ret = RegQueryValueEx(rkey, "RandSeedFile",
@@ -431,10 +480,45 @@ static void get_seedpath(void)
        if (ret != ERROR_SUCCESS || type != REG_SZ)
            seedpath[0] = '\0';
        RegCloseKey(rkey);
-    } else
-       seedpath[0] = '\0';
 
-    if (!seedpath[0]) {
+       if (*seedpath && try_random_seed(seedpath, action, &rethandle))
+           return rethandle;
+    }
+
+    /*
+     * Next, try the user's local Application Data directory,
+     * followed by their non-local one. This is found using the
+     * SHGetFolderPath function, which won't be present on all
+     * versions of Windows.
+     */
+    if (!tried_shgetfolderpath) {
+       shell32_module = LoadLibrary("SHELL32.DLL");
+       if (shell32_module) {
+           p_SHGetFolderPath = (p_SHGetFolderPath_t)
+               GetProcAddress(shell32_module, "SHGetFolderPathA");
+       }
+    }
+    if (p_SHGetFolderPath) {
+       if (SUCCEEDED(p_SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA,
+                                       NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+           strcat(seedpath, "\\PUTTY.RND");
+           if (try_random_seed(seedpath, action, &rethandle))
+               return rethandle;
+       }
+
+       if (SUCCEEDED(p_SHGetFolderPath(NULL, CSIDL_APPDATA,
+                                       NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+           strcat(seedpath, "\\PUTTY.RND");
+           if (try_random_seed(seedpath, action, &rethandle))
+               return rethandle;
+       }
+    }
+
+    /*
+     * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the
+     * user's home directory.
+     */
+    {
        int len, ret;
 
        len =
@@ -443,25 +527,30 @@ static void get_seedpath(void)
        ret =
            GetEnvironmentVariable("HOMEPATH", seedpath + len,
                                   sizeof(seedpath) - len);
-       if (ret == 0) {                /* probably win95; store in \WINDOWS */
-           GetWindowsDirectory(seedpath, sizeof(seedpath));
-           len = strlen(seedpath);
-       } else
-           len += ret;
-       strcpy(seedpath + len, "\\PUTTY.RND");
+       if (ret != 0) {
+           strcat(seedpath, "\\PUTTY.RND");
+           if (try_random_seed(seedpath, action, &rethandle))
+               return rethandle;
+       }
     }
+
+    /*
+     * And finally, fall back to C:\WINDOWS.
+     */
+    GetWindowsDirectory(seedpath, sizeof(seedpath));
+    strcat(seedpath, "\\PUTTY.RND");
+    if (try_random_seed(seedpath, action, &rethandle))
+       return rethandle;
+
+    /*
+     * If even that failed, give up.
+     */
+    return INVALID_HANDLE_VALUE;
 }
 
 void read_random_seed(noise_consumer_t consumer)
 {
-    HANDLE seedf;
-
-    if (!seedpath[0])
-       get_seedpath();
-
-    seedf = CreateFile(seedpath, GENERIC_READ,
-                      FILE_SHARE_READ | FILE_SHARE_WRITE,
-                      NULL, OPEN_EXISTING, 0, NULL);
+    HANDLE seedf = access_random_seed(OPEN_R);
 
     if (seedf != INVALID_HANDLE_VALUE) {
        while (1) {
@@ -479,13 +568,7 @@ void read_random_seed(noise_consumer_t consumer)
 
 void write_random_seed(void *data, int len)
 {
-    HANDLE seedf;
-
-    if (!seedpath[0])
-       get_seedpath();
-
-    seedf = CreateFile(seedpath, GENERIC_WRITE, 0,
-                      NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    HANDLE seedf = access_random_seed(OPEN_W);
 
     if (seedf != INVALID_HANDLE_VALUE) {
        DWORD lenwritten;
@@ -521,11 +604,10 @@ void cleanup_all(void)
     char name[MAX_PATH + 1];
 
     /* ------------------------------------------------------------
-     * Wipe out the random seed file.
+     * Wipe out the random seed file, in all of its possible
+     * locations.
      */
-    if (!seedpath[0])
-       get_seedpath();
-    remove(seedpath);
+    access_random_seed(DEL);
 
     /* ------------------------------------------------------------
      * Destroy all registry information associated with PuTTY.