OSDN Git Service

Add VC++ Project files for PuTTY DLL without exported functions.
[ffftp/ffftp.git] / putty / WINDOWS / WINSTORE.C
1 /*\r
2  * winstore.c: Windows-specific implementation of the interface\r
3  * defined in storage.h.\r
4  */\r
5 \r
6 #include <stdio.h>\r
7 #include <stdlib.h>\r
8 #include <limits.h>\r
9 #include "putty.h"\r
10 #include "storage.h"\r
11 \r
12 #include <shlobj.h>\r
13 #ifndef CSIDL_APPDATA\r
14 #define CSIDL_APPDATA 0x001a\r
15 #endif\r
16 #ifndef CSIDL_LOCAL_APPDATA\r
17 #define CSIDL_LOCAL_APPDATA 0x001c\r
18 #endif\r
19 \r
20 static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";\r
21 static const char *const reg_jumplist_value = "Recent sessions";\r
22 static const char *const puttystr = PUTTY_REG_POS "\\Sessions";\r
23 \r
24 static const char hex[16] = "0123456789ABCDEF";\r
25 \r
26 static int tried_shgetfolderpath = FALSE;\r
27 static HMODULE shell32_module = NULL;\r
28 DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, \r
29                       (HWND, int, HANDLE, DWORD, LPSTR));\r
30 \r
31 static void mungestr(const char *in, char *out)\r
32 {\r
33     int candot = 0;\r
34 \r
35     while (*in) {\r
36         if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||\r
37             *in == '%' || *in < ' ' || *in > '~' || (*in == '.'\r
38                                                      && !candot)) {\r
39             *out++ = '%';\r
40             *out++ = hex[((unsigned char) *in) >> 4];\r
41             *out++ = hex[((unsigned char) *in) & 15];\r
42         } else\r
43             *out++ = *in;\r
44         in++;\r
45         candot = 1;\r
46     }\r
47     *out = '\0';\r
48     return;\r
49 }\r
50 \r
51 static void unmungestr(const char *in, char *out, int outlen)\r
52 {\r
53     while (*in) {\r
54         if (*in == '%' && in[1] && in[2]) {\r
55             int i, j;\r
56 \r
57             i = in[1] - '0';\r
58             i -= (i > 9 ? 7 : 0);\r
59             j = in[2] - '0';\r
60             j -= (j > 9 ? 7 : 0);\r
61 \r
62             *out++ = (i << 4) + j;\r
63             if (!--outlen)\r
64                 return;\r
65             in += 3;\r
66         } else {\r
67             *out++ = *in++;\r
68             if (!--outlen)\r
69                 return;\r
70         }\r
71     }\r
72     *out = '\0';\r
73     return;\r
74 }\r
75 \r
76 void *open_settings_w(const char *sessionname, char **errmsg)\r
77 {\r
78     HKEY subkey1, sesskey;\r
79     int ret;\r
80     char *p;\r
81 \r
82     *errmsg = NULL;\r
83 \r
84     if (!sessionname || !*sessionname)\r
85         sessionname = "Default Settings";\r
86 \r
87     p = snewn(3 * strlen(sessionname) + 1, char);\r
88     mungestr(sessionname, p);\r
89 \r
90     ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1);\r
91     if (ret != ERROR_SUCCESS) {\r
92         sfree(p);\r
93         *errmsg = dupprintf("Unable to create registry key\n"\r
94                             "HKEY_CURRENT_USER\\%s", puttystr);\r
95         return NULL;\r
96     }\r
97     ret = RegCreateKey(subkey1, p, &sesskey);\r
98     RegCloseKey(subkey1);\r
99     if (ret != ERROR_SUCCESS) {\r
100         *errmsg = dupprintf("Unable to create registry key\n"\r
101                             "HKEY_CURRENT_USER\\%s\\%s", puttystr, p);\r
102         sfree(p);\r
103         return NULL;\r
104     }\r
105     sfree(p);\r
106     return (void *) sesskey;\r
107 }\r
108 \r
109 void write_setting_s(void *handle, const char *key, const char *value)\r
110 {\r
111     if (handle)\r
112         RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value,\r
113                       1 + strlen(value));\r
114 }\r
115 \r
116 void write_setting_i(void *handle, const char *key, int value)\r
117 {\r
118     if (handle)\r
119         RegSetValueEx((HKEY) handle, key, 0, REG_DWORD,\r
120                       (CONST BYTE *) &value, sizeof(value));\r
121 }\r
122 \r
123 void close_settings_w(void *handle)\r
124 {\r
125     RegCloseKey((HKEY) handle);\r
126 }\r
127 \r
128 void *open_settings_r(const char *sessionname)\r
129 {\r
130     HKEY subkey1, sesskey;\r
131     char *p;\r
132 \r
133     if (!sessionname || !*sessionname)\r
134         sessionname = "Default Settings";\r
135 \r
136     p = snewn(3 * strlen(sessionname) + 1, char);\r
137     mungestr(sessionname, p);\r
138 \r
139     if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) {\r
140         sesskey = NULL;\r
141     } else {\r
142         if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) {\r
143             sesskey = NULL;\r
144         }\r
145         RegCloseKey(subkey1);\r
146     }\r
147 \r
148     sfree(p);\r
149 \r
150     return (void *) sesskey;\r
151 }\r
152 \r
153 char *read_setting_s(void *handle, const char *key, char *buffer, int buflen)\r
154 {\r
155     DWORD type, size;\r
156     size = buflen;\r
157 \r
158     if (!handle ||\r
159         RegQueryValueEx((HKEY) handle, key, 0,\r
160                         &type, buffer, &size) != ERROR_SUCCESS ||\r
161         type != REG_SZ) return NULL;\r
162     else\r
163         return buffer;\r
164 }\r
165 \r
166 int read_setting_i(void *handle, const char *key, int defvalue)\r
167 {\r
168     DWORD type, val, size;\r
169     size = sizeof(val);\r
170 \r
171     if (!handle ||\r
172         RegQueryValueEx((HKEY) handle, key, 0, &type,\r
173                         (BYTE *) &val, &size) != ERROR_SUCCESS ||\r
174         size != sizeof(val) || type != REG_DWORD)\r
175         return defvalue;\r
176     else\r
177         return val;\r
178 }\r
179 \r
180 int read_setting_fontspec(void *handle, const char *name, FontSpec *result)\r
181 {\r
182     char *settingname;\r
183     FontSpec ret;\r
184 \r
185     if (!read_setting_s(handle, name, ret.name, sizeof(ret.name)))\r
186         return 0;\r
187     settingname = dupcat(name, "IsBold", NULL);\r
188     ret.isbold = read_setting_i(handle, settingname, -1);\r
189     sfree(settingname);\r
190     if (ret.isbold == -1) return 0;\r
191     settingname = dupcat(name, "CharSet", NULL);\r
192     ret.charset = read_setting_i(handle, settingname, -1);\r
193     sfree(settingname);\r
194     if (ret.charset == -1) return 0;\r
195     settingname = dupcat(name, "Height", NULL);\r
196     ret.height = read_setting_i(handle, settingname, INT_MIN);\r
197     sfree(settingname);\r
198     if (ret.height == INT_MIN) return 0;\r
199     *result = ret;\r
200     return 1;\r
201 }\r
202 \r
203 void write_setting_fontspec(void *handle, const char *name, FontSpec font)\r
204 {\r
205     char *settingname;\r
206 \r
207     write_setting_s(handle, name, font.name);\r
208     settingname = dupcat(name, "IsBold", NULL);\r
209     write_setting_i(handle, settingname, font.isbold);\r
210     sfree(settingname);\r
211     settingname = dupcat(name, "CharSet", NULL);\r
212     write_setting_i(handle, settingname, font.charset);\r
213     sfree(settingname);\r
214     settingname = dupcat(name, "Height", NULL);\r
215     write_setting_i(handle, settingname, font.height);\r
216     sfree(settingname);\r
217 }\r
218 \r
219 int read_setting_filename(void *handle, const char *name, Filename *result)\r
220 {\r
221     return !!read_setting_s(handle, name, result->path, sizeof(result->path));\r
222 }\r
223 \r
224 void write_setting_filename(void *handle, const char *name, Filename result)\r
225 {\r
226     write_setting_s(handle, name, result.path);\r
227 }\r
228 \r
229 void close_settings_r(void *handle)\r
230 {\r
231     RegCloseKey((HKEY) handle);\r
232 }\r
233 \r
234 void del_settings(const char *sessionname)\r
235 {\r
236     HKEY subkey1;\r
237     char *p;\r
238 \r
239     if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS)\r
240         return;\r
241 \r
242     p = snewn(3 * strlen(sessionname) + 1, char);\r
243     mungestr(sessionname, p);\r
244     RegDeleteKey(subkey1, p);\r
245     sfree(p);\r
246 \r
247     RegCloseKey(subkey1);\r
248 \r
249     remove_session_from_jumplist(sessionname);\r
250 }\r
251 \r
252 struct enumsettings {\r
253     HKEY key;\r
254     int i;\r
255 };\r
256 \r
257 void *enum_settings_start(void)\r
258 {\r
259     struct enumsettings *ret;\r
260     HKEY key;\r
261 \r
262     if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)\r
263         return NULL;\r
264 \r
265     ret = snew(struct enumsettings);\r
266     if (ret) {\r
267         ret->key = key;\r
268         ret->i = 0;\r
269     }\r
270 \r
271     return ret;\r
272 }\r
273 \r
274 char *enum_settings_next(void *handle, char *buffer, int buflen)\r
275 {\r
276     struct enumsettings *e = (struct enumsettings *) handle;\r
277     char *otherbuf;\r
278     otherbuf = snewn(3 * buflen, char);\r
279     if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) {\r
280         unmungestr(otherbuf, buffer, buflen);\r
281         sfree(otherbuf);\r
282         return buffer;\r
283     } else {\r
284         sfree(otherbuf);\r
285         return NULL;\r
286     }\r
287 }\r
288 \r
289 void enum_settings_finish(void *handle)\r
290 {\r
291     struct enumsettings *e = (struct enumsettings *) handle;\r
292     RegCloseKey(e->key);\r
293     sfree(e);\r
294 }\r
295 \r
296 static void hostkey_regname(char *buffer, const char *hostname,\r
297                             int port, const char *keytype)\r
298 {\r
299     int len;\r
300     strcpy(buffer, keytype);\r
301     strcat(buffer, "@");\r
302     len = strlen(buffer);\r
303     len += sprintf(buffer + len, "%d:", port);\r
304     mungestr(hostname, buffer + strlen(buffer));\r
305 }\r
306 \r
307 int verify_host_key(const char *hostname, int port,\r
308                     const char *keytype, const char *key)\r
309 {\r
310     char *otherstr, *regname;\r
311     int len;\r
312     HKEY rkey;\r
313     DWORD readlen;\r
314     DWORD type;\r
315     int ret, compare;\r
316 \r
317     len = 1 + strlen(key);\r
318 \r
319     /*\r
320      * Now read a saved key in from the registry and see what it\r
321      * says.\r
322      */\r
323     otherstr = snewn(len, char);\r
324     regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);\r
325 \r
326     hostkey_regname(regname, hostname, port, keytype);\r
327 \r
328     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",\r
329                    &rkey) != ERROR_SUCCESS)\r
330         return 1;                      /* key does not exist in registry */\r
331 \r
332     readlen = len;\r
333     ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);\r
334 \r
335     if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&\r
336         !strcmp(keytype, "rsa")) {\r
337         /*\r
338          * Key didn't exist. If the key type is RSA, we'll try\r
339          * another trick, which is to look up the _old_ key format\r
340          * under just the hostname and translate that.\r
341          */\r
342         char *justhost = regname + 1 + strcspn(regname, ":");\r
343         char *oldstyle = snewn(len + 10, char); /* safety margin */\r
344         readlen = len;\r
345         ret = RegQueryValueEx(rkey, justhost, NULL, &type,\r
346                               oldstyle, &readlen);\r
347 \r
348         if (ret == ERROR_SUCCESS && type == REG_SZ) {\r
349             /*\r
350              * The old format is two old-style bignums separated by\r
351              * a slash. An old-style bignum is made of groups of\r
352              * four hex digits: digits are ordered in sensible\r
353              * (most to least significant) order within each group,\r
354              * but groups are ordered in silly (least to most)\r
355              * order within the bignum. The new format is two\r
356              * ordinary C-format hex numbers (0xABCDEFG...XYZ, with\r
357              * A nonzero except in the special case 0x0, which\r
358              * doesn't appear anyway in RSA keys) separated by a\r
359              * comma. All hex digits are lowercase in both formats.\r
360              */\r
361             char *p = otherstr;\r
362             char *q = oldstyle;\r
363             int i, j;\r
364 \r
365             for (i = 0; i < 2; i++) {\r
366                 int ndigits, nwords;\r
367                 *p++ = '0';\r
368                 *p++ = 'x';\r
369                 ndigits = strcspn(q, "/");      /* find / or end of string */\r
370                 nwords = ndigits / 4;\r
371                 /* now trim ndigits to remove leading zeros */\r
372                 while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)\r
373                     ndigits--;\r
374                 /* now move digits over to new string */\r
375                 for (j = 0; j < ndigits; j++)\r
376                     p[ndigits - 1 - j] = q[j ^ 3];\r
377                 p += ndigits;\r
378                 q += nwords * 4;\r
379                 if (*q) {\r
380                     q++;               /* eat the slash */\r
381                     *p++ = ',';        /* add a comma */\r
382                 }\r
383                 *p = '\0';             /* terminate the string */\r
384             }\r
385 \r
386             /*\r
387              * Now _if_ this key matches, we'll enter it in the new\r
388              * format. If not, we'll assume something odd went\r
389              * wrong, and hyper-cautiously do nothing.\r
390              */\r
391             if (!strcmp(otherstr, key))\r
392                 RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,\r
393                               strlen(otherstr) + 1);\r
394         }\r
395     }\r
396 \r
397     RegCloseKey(rkey);\r
398 \r
399     compare = strcmp(otherstr, key);\r
400 \r
401     sfree(otherstr);\r
402     sfree(regname);\r
403 \r
404     if (ret == ERROR_MORE_DATA ||\r
405         (ret == ERROR_SUCCESS && type == REG_SZ && compare))\r
406         return 2;                      /* key is different in registry */\r
407     else if (ret != ERROR_SUCCESS || type != REG_SZ)\r
408         return 1;                      /* key does not exist in registry */\r
409     else\r
410         return 0;                      /* key matched OK in registry */\r
411 }\r
412 \r
413 void store_host_key(const char *hostname, int port,\r
414                     const char *keytype, const char *key)\r
415 {\r
416     char *regname;\r
417     HKEY rkey;\r
418 \r
419     regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);\r
420 \r
421     hostkey_regname(regname, hostname, port, keytype);\r
422 \r
423     if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",\r
424                      &rkey) == ERROR_SUCCESS) {\r
425         RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1);\r
426         RegCloseKey(rkey);\r
427     } /* else key does not exist in registry */\r
428 \r
429     sfree(regname);\r
430 }\r
431 \r
432 /*\r
433  * Open (or delete) the random seed file.\r
434  */\r
435 enum { DEL, OPEN_R, OPEN_W };\r
436 static int try_random_seed(char const *path, int action, HANDLE *ret)\r
437 {\r
438     if (action == DEL) {\r
439         remove(path);\r
440         *ret = INVALID_HANDLE_VALUE;\r
441         return FALSE;                  /* so we'll do the next ones too */\r
442     }\r
443 \r
444     *ret = CreateFile(path,\r
445                       action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,\r
446                       action == OPEN_W ? 0 : (FILE_SHARE_READ |\r
447                                               FILE_SHARE_WRITE),\r
448                       NULL,\r
449                       action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,\r
450                       action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,\r
451                       NULL);\r
452 \r
453     return (*ret != INVALID_HANDLE_VALUE);\r
454 }\r
455 \r
456 static HANDLE access_random_seed(int action)\r
457 {\r
458     HKEY rkey;\r
459     DWORD type, size;\r
460     HANDLE rethandle;\r
461     char seedpath[2 * MAX_PATH + 10] = "\0";\r
462 \r
463     /*\r
464      * Iterate over a selection of possible random seed paths until\r
465      * we find one that works.\r
466      * \r
467      * We do this iteration separately for reading and writing,\r
468      * meaning that we will automatically migrate random seed files\r
469      * if a better location becomes available (by reading from the\r
470      * best location in which we actually find one, and then\r
471      * writing to the best location in which we can _create_ one).\r
472      */\r
473 \r
474     /*\r
475      * First, try the location specified by the user in the\r
476      * Registry, if any.\r
477      */\r
478     size = sizeof(seedpath);\r
479     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==\r
480         ERROR_SUCCESS) {\r
481         int ret = RegQueryValueEx(rkey, "RandSeedFile",\r
482                                   0, &type, seedpath, &size);\r
483         if (ret != ERROR_SUCCESS || type != REG_SZ)\r
484             seedpath[0] = '\0';\r
485         RegCloseKey(rkey);\r
486 \r
487         if (*seedpath && try_random_seed(seedpath, action, &rethandle))\r
488             return rethandle;\r
489     }\r
490 \r
491     /*\r
492      * Next, try the user's local Application Data directory,\r
493      * followed by their non-local one. This is found using the\r
494      * SHGetFolderPath function, which won't be present on all\r
495      * versions of Windows.\r
496      */\r
497     if (!tried_shgetfolderpath) {\r
498         /* This is likely only to bear fruit on systems with IE5+\r
499          * installed, or WinMe/2K+. There is some faffing with\r
500          * SHFOLDER.DLL we could do to try to find an equivalent\r
501          * on older versions of Windows if we cared enough.\r
502          * However, the invocation below requires IE5+ anyway,\r
503          * so stuff that. */\r
504         shell32_module = load_system32_dll("shell32.dll");\r
505         GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA);\r
506         tried_shgetfolderpath = TRUE;\r
507     }\r
508     if (p_SHGetFolderPathA) {\r
509         if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA,\r
510                                          NULL, SHGFP_TYPE_CURRENT, seedpath))) {\r
511             strcat(seedpath, "\\PUTTY.RND");\r
512             if (try_random_seed(seedpath, action, &rethandle))\r
513                 return rethandle;\r
514         }\r
515 \r
516         if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA,\r
517                                          NULL, SHGFP_TYPE_CURRENT, seedpath))) {\r
518             strcat(seedpath, "\\PUTTY.RND");\r
519             if (try_random_seed(seedpath, action, &rethandle))\r
520                 return rethandle;\r
521         }\r
522     }\r
523 \r
524     /*\r
525      * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the\r
526      * user's home directory.\r
527      */\r
528     {\r
529         int len, ret;\r
530 \r
531         len =\r
532             GetEnvironmentVariable("HOMEDRIVE", seedpath,\r
533                                    sizeof(seedpath));\r
534         ret =\r
535             GetEnvironmentVariable("HOMEPATH", seedpath + len,\r
536                                    sizeof(seedpath) - len);\r
537         if (ret != 0) {\r
538             strcat(seedpath, "\\PUTTY.RND");\r
539             if (try_random_seed(seedpath, action, &rethandle))\r
540                 return rethandle;\r
541         }\r
542     }\r
543 \r
544     /*\r
545      * And finally, fall back to C:\WINDOWS.\r
546      */\r
547     GetWindowsDirectory(seedpath, sizeof(seedpath));\r
548     strcat(seedpath, "\\PUTTY.RND");\r
549     if (try_random_seed(seedpath, action, &rethandle))\r
550         return rethandle;\r
551 \r
552     /*\r
553      * If even that failed, give up.\r
554      */\r
555     return INVALID_HANDLE_VALUE;\r
556 }\r
557 \r
558 void read_random_seed(noise_consumer_t consumer)\r
559 {\r
560     HANDLE seedf = access_random_seed(OPEN_R);\r
561 \r
562     if (seedf != INVALID_HANDLE_VALUE) {\r
563         while (1) {\r
564             char buf[1024];\r
565             DWORD len;\r
566 \r
567             if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)\r
568                 consumer(buf, len);\r
569             else\r
570                 break;\r
571         }\r
572         CloseHandle(seedf);\r
573     }\r
574 }\r
575 \r
576 void write_random_seed(void *data, int len)\r
577 {\r
578     HANDLE seedf = access_random_seed(OPEN_W);\r
579 \r
580     if (seedf != INVALID_HANDLE_VALUE) {\r
581         DWORD lenwritten;\r
582 \r
583         WriteFile(seedf, data, len, &lenwritten, NULL);\r
584         CloseHandle(seedf);\r
585     }\r
586 }\r
587 \r
588 /*\r
589  * Internal function supporting the jump list registry code. All the\r
590  * functions to add, remove and read the list have substantially\r
591  * similar content, so this is a generalisation of all of them which\r
592  * transforms the list in the registry by prepending 'add' (if\r
593  * non-null), removing 'rem' from what's left (if non-null), and\r
594  * returning the resulting concatenated list of strings in 'out' (if\r
595  * non-null).\r
596  */\r
597 static int transform_jumplist_registry\r
598     (const char *add, const char *rem, char **out)\r
599 {\r
600     int ret;\r
601     HKEY pjumplist_key, psettings_tmp;\r
602     DWORD type;\r
603     int value_length;\r
604     char *old_value, *new_value;\r
605     char *piterator_old, *piterator_new, *piterator_tmp;\r
606 \r
607     ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,\r
608                          REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,\r
609                          &pjumplist_key, NULL);\r
610     if (ret != ERROR_SUCCESS) {\r
611         return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;\r
612     }\r
613 \r
614     /* Get current list of saved sessions in the registry. */\r
615     value_length = 200;\r
616     old_value = snewn(value_length, char);\r
617     ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,\r
618                           old_value, &value_length);\r
619     /* When the passed buffer is too small, ERROR_MORE_DATA is\r
620      * returned and the required size is returned in the length\r
621      * argument. */\r
622     if (ret == ERROR_MORE_DATA) {\r
623         sfree(old_value);\r
624         old_value = snewn(value_length, char);\r
625         ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,\r
626                               old_value, &value_length);\r
627     }\r
628 \r
629     if (ret == ERROR_FILE_NOT_FOUND) {\r
630         /* Value doesn't exist yet. Start from an empty value. */\r
631         *old_value = '\0';\r
632         *(old_value + 1) = '\0';\r
633     } else if (ret != ERROR_SUCCESS) {\r
634         /* Some non-recoverable error occurred. */\r
635         sfree(old_value);\r
636         RegCloseKey(pjumplist_key);\r
637         return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;\r
638     } else if (type != REG_MULTI_SZ) {\r
639         /* The value present in the registry has the wrong type: we\r
640          * try to delete it and start from an empty value. */\r
641         ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);\r
642         if (ret != ERROR_SUCCESS) {\r
643             sfree(old_value);\r
644             RegCloseKey(pjumplist_key);\r
645             return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;\r
646         }\r
647 \r
648         *old_value = '\0';\r
649         *(old_value + 1) = '\0';\r
650     }\r
651 \r
652     /* Check validity of registry data: REG_MULTI_SZ value must end\r
653      * with \0\0. */\r
654     piterator_tmp = old_value;\r
655     while (((piterator_tmp - old_value) < (value_length - 1)) &&\r
656            !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {\r
657         ++piterator_tmp;\r
658     }\r
659 \r
660     if ((piterator_tmp - old_value) >= (value_length-1)) {\r
661         /* Invalid value. Start from an empty value. */\r
662         *old_value = '\0';\r
663         *(old_value + 1) = '\0';\r
664     }\r
665 \r
666     /*\r
667      * Modify the list, if we're modifying.\r
668      */\r
669     if (add || rem) {\r
670         /* Walk through the existing list and construct the new list of\r
671          * saved sessions. */\r
672         new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);\r
673         piterator_new = new_value;\r
674         piterator_old = old_value;\r
675 \r
676         /* First add the new item to the beginning of the list. */\r
677         if (add) {\r
678             strcpy(piterator_new, add);\r
679             piterator_new += strlen(piterator_new) + 1;\r
680         }\r
681         /* Now add the existing list, taking care to leave out the removed\r
682          * item, if it was already in the existing list. */\r
683         while (*piterator_old != '\0') {\r
684             if (!rem || strcmp(piterator_old, rem) != 0) {\r
685                 /* Check if this is a valid session, otherwise don't add. */\r
686                 psettings_tmp = open_settings_r(piterator_old);\r
687                 if (psettings_tmp != NULL) {\r
688                     close_settings_r(psettings_tmp);\r
689                     strcpy(piterator_new, piterator_old);\r
690                     piterator_new += strlen(piterator_new) + 1;\r
691                 }\r
692             }\r
693             piterator_old += strlen(piterator_old) + 1;\r
694         }\r
695         *piterator_new = '\0';\r
696         ++piterator_new;\r
697 \r
698         /* Save the new list to the registry. */\r
699         ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,\r
700                             new_value, piterator_new - new_value);\r
701 \r
702         sfree(old_value);\r
703         old_value = new_value;\r
704     } else\r
705         ret = ERROR_SUCCESS;\r
706 \r
707     /*\r
708      * Either return or free the result.\r
709      */\r
710     if (out)\r
711         *out = old_value;\r
712     else\r
713         sfree(old_value);\r
714 \r
715     /* Clean up and return. */\r
716     RegCloseKey(pjumplist_key);\r
717 \r
718     if (ret != ERROR_SUCCESS) {\r
719         return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;\r
720     } else {\r
721         return JUMPLISTREG_OK;\r
722     }\r
723 }\r
724 \r
725 /* Adds a new entry to the jumplist entries in the registry. */\r
726 int add_to_jumplist_registry(const char *item)\r
727 {\r
728     return transform_jumplist_registry(item, item, NULL);\r
729 }\r
730 \r
731 /* Removes an item from the jumplist entries in the registry. */\r
732 int remove_from_jumplist_registry(const char *item)\r
733 {\r
734     return transform_jumplist_registry(NULL, item, NULL);\r
735 }\r
736 \r
737 /* Returns the jumplist entries from the registry. Caller must free\r
738  * the returned pointer. */\r
739 char *get_jumplist_registry_entries (void)\r
740 {\r
741     char *list_value;\r
742 \r
743     if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) {\r
744         list_value = snewn(2, char);\r
745         *list_value = '\0';\r
746         *(list_value + 1) = '\0';\r
747     }\r
748     return list_value;\r
749 }\r
750 \r
751 /*\r
752  * Recursively delete a registry key and everything under it.\r
753  */\r
754 static void registry_recursive_remove(HKEY key)\r
755 {\r
756     DWORD i;\r
757     char name[MAX_PATH + 1];\r
758     HKEY subkey;\r
759 \r
760     i = 0;\r
761     while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {\r
762         if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {\r
763             registry_recursive_remove(subkey);\r
764             RegCloseKey(subkey);\r
765         }\r
766         RegDeleteKey(key, name);\r
767     }\r
768 }\r
769 \r
770 void cleanup_all(void)\r
771 {\r
772     HKEY key;\r
773     int ret;\r
774     char name[MAX_PATH + 1];\r
775 \r
776     /* ------------------------------------------------------------\r
777      * Wipe out the random seed file, in all of its possible\r
778      * locations.\r
779      */\r
780     access_random_seed(DEL);\r
781 \r
782     /* ------------------------------------------------------------\r
783      * Ask Windows to delete any jump list information associated\r
784      * with this installation of PuTTY.\r
785      */\r
786     clear_jumplist();\r
787 \r
788     /* ------------------------------------------------------------\r
789      * Destroy all registry information associated with PuTTY.\r
790      */\r
791 \r
792     /*\r
793      * Open the main PuTTY registry key and remove everything in it.\r
794      */\r
795     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==\r
796         ERROR_SUCCESS) {\r
797         registry_recursive_remove(key);\r
798         RegCloseKey(key);\r
799     }\r
800     /*\r
801      * Now open the parent key and remove the PuTTY main key. Once\r
802      * we've done that, see if the parent key has any other\r
803      * children.\r
804      */\r
805     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,\r
806                    &key) == ERROR_SUCCESS) {\r
807         RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);\r
808         ret = RegEnumKey(key, 0, name, sizeof(name));\r
809         RegCloseKey(key);\r
810         /*\r
811          * If the parent key had no other children, we must delete\r
812          * it in its turn. That means opening the _grandparent_\r
813          * key.\r
814          */\r
815         if (ret != ERROR_SUCCESS) {\r
816             if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,\r
817                            &key) == ERROR_SUCCESS) {\r
818                 RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);\r
819                 RegCloseKey(key);\r
820             }\r
821         }\r
822     }\r
823     /*\r
824      * Now we're done.\r
825      */\r
826 }\r