]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - winstore.c
Rationalised host key storage. Also started code reorg: persistent-state
[PuTTY.git] / winstore.c
1 /*
2  * winstore.c: Windows-specific implementation of the interface
3  * defined in storage.h.
4  */
5
6 #include <windows.h>
7 #include <stdio.h>
8 #include "putty.h"
9 #include "storage.h"
10
11 static char seedpath[2*MAX_PATH+10] = "\0";
12
13 static char hex[16] = "0123456789ABCDEF";
14
15 static void mungestr(char *in, char *out) {
16     int candot = 0;
17
18     while (*in) {
19         if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
20             *in == '%' || *in < ' ' || *in > '~' || (*in == '.' && !candot)) {
21             *out++ = '%';
22             *out++ = hex[((unsigned char)*in) >> 4];
23             *out++ = hex[((unsigned char)*in) & 15];
24         } else
25             *out++ = *in;
26         in++;
27         candot = 1;
28     }
29     *out = '\0';
30     return;
31 }
32
33 static void unmungestr(char *in, char *out) {
34     while (*in) {
35         if (*in == '%' && in[1] && in[2]) {
36             int i, j;
37
38             i = in[1] - '0'; i -= (i > 9 ? 7 : 0);
39             j = in[2] - '0'; j -= (j > 9 ? 7 : 0);
40
41             *out++ = (i<<4) + j;
42             in += 3;
43         } else
44             *out++ = *in++;
45     }
46     *out = '\0';
47     return;
48 }
49
50 void *open_settings_w(char *sessionname);
51 void write_setting_s(void *handle, char *key, char *value);
52 void write_setting_i(void *handle, char *key, int value);
53 void *close_settings_w(void *handle);
54
55 void *open_settings_r(char *sessionname);
56 char *read_setting_s(void *handle, char *key, char *buffer, int buflen);
57 int read_setting_i(void *handle, char *key, int defvalue);
58 void *close_settings_r(void *handle);
59
60 static void hostkey_regname(char *buffer, char *hostname, char *keytype) {
61     strcpy(buffer, keytype);
62     strcat(buffer, "@");
63     mungestr(hostname, buffer + strlen(buffer));
64 }
65
66 int verify_host_key(char *hostname, char *keytype, char *key) {
67     char *otherstr, *regname;
68     int len;
69     HKEY rkey;
70     DWORD readlen;
71     DWORD type;
72     int ret, compare;
73
74     len = 1 + strlen(key);
75
76     /*
77      * Now read a saved key in from the registry and see what it
78      * says.
79      */
80     otherstr = smalloc(len);
81     regname = smalloc(3*(strlen(hostname)+strlen(keytype))+5);
82     if (!otherstr || !regname)
83         fatalbox("Out of memory");
84
85     hostkey_regname(regname, hostname, keytype);
86
87     if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
88                      &rkey) != ERROR_SUCCESS)
89         return 1;                      /* key does not exist in registry */
90
91     readlen = len;
92     ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);
93
94     if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
95         !strcmp(keytype, "rsa")) {
96         /*
97          * Key didn't exist. If the key type is RSA, we'll try
98          * another trick, which is to look up the _old_ key format
99          * under just the hostname and translate that.
100          */
101         char *justhost = regname + 1 + strlen(keytype);
102         char *oldstyle = smalloc(len + 10);   /* safety margin */
103         readlen = len;
104         ret = RegQueryValueEx(rkey, justhost, NULL, &type,
105                               oldstyle, &readlen);
106         
107         if (ret == ERROR_SUCCESS && type == REG_SZ) {
108             /*
109              * The old format is two old-style bignums separated by
110              * a slash. An old-style bignum is made of groups of
111              * four hex digits: digits are ordered in sensible
112              * (most to least significant) order within each group,
113              * but groups are ordered in silly (least to most)
114              * order within the bignum. The new format is two
115              * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
116              * A nonzero except in the special case 0x0, which
117              * doesn't appear anyway in RSA keys) separated by a
118              * comma. All hex digits are lowercase in both formats.
119              */
120             char *p = otherstr;
121             char *q = oldstyle;
122             int i, j;
123
124             for (i = 0; i < 2; i++) {
125                 int ndigits, nwords;
126                 *p++ = '0'; *p++ = 'x';
127                 ndigits = strcspn(q, "/");   /* find / or end of string */
128                 nwords = ndigits / 4;
129                 /* now trim ndigits to remove leading zeros */
130                 while (q[ (ndigits-1) ^ 3 ] == '0' && ndigits > 1)
131                     ndigits--;
132                 /* now move digits over to new string */
133                 for (j = 0; j < ndigits; j++)
134                     p[ndigits-1-j] = q[j^3];
135                 p += ndigits;
136                 q += nwords*4;
137                 if (*q) {
138                     q++;               /* eat the slash */
139                     *p++ = ',';        /* add a comma */
140                 }
141                 *p = '\0';             /* terminate the string */
142             }
143
144             /*
145              * Now _if_ this key matches, we'll enter it in the new
146              * format. If not, we'll assume something odd went
147              * wrong, and hyper-cautiously do nothing.
148              */
149             if (!strcmp(otherstr, key))
150                 RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,
151                               strlen(otherstr)+1);
152         }
153     }
154
155     RegCloseKey(rkey);
156
157     compare = strcmp(otherstr, key);
158
159     sfree(otherstr);
160     sfree(regname);
161
162     if (ret == ERROR_MORE_DATA ||
163         (ret == ERROR_SUCCESS && type == REG_SZ && compare))
164         return 2;                      /* key is different in registry */
165     else if (ret != ERROR_SUCCESS || type != REG_SZ)
166         return 1;                      /* key does not exist in registry */
167     else
168         return 0;                      /* key matched OK in registry */
169 }
170
171 void store_host_key(char *hostname, char *keytype, char *key) {
172     char *regname;
173     HKEY rkey;
174
175     regname = smalloc(3*(strlen(hostname)+strlen(keytype))+5);
176     if (!regname)
177         fatalbox("Out of memory");
178
179     hostkey_regname(regname, hostname, keytype);
180
181     if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
182                      &rkey) != ERROR_SUCCESS)
183         return;                        /* key does not exist in registry */
184     RegSetValueEx(rkey, regname, 0, REG_SZ, key,
185                   strlen(key)+1);
186     RegCloseKey(rkey);
187 }
188
189 /*
190  * Find the random seed file path and store it in `seedpath'.
191  */
192 static void get_seedpath(void) {
193     HKEY rkey;
194     DWORD type, size;
195
196     size = sizeof(seedpath);
197
198     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey)==ERROR_SUCCESS) {
199         int ret = RegQueryValueEx(rkey, "RandSeedFile",
200                                   0, &type, seedpath, &size);
201         if (ret != ERROR_SUCCESS || type != REG_SZ)
202             seedpath[0] = '\0';
203         RegCloseKey(rkey);
204     } else
205         seedpath[0] = '\0';
206
207     if (!seedpath[0]) {
208         int len, ret;
209
210         len = GetEnvironmentVariable("HOMEDRIVE", seedpath, sizeof(seedpath));
211         ret = GetEnvironmentVariable("HOMEPATH", seedpath+len,
212                                       sizeof(seedpath)-len);
213         if (ret == 0) {                /* probably win95; store in \WINDOWS */
214             GetWindowsDirectory(seedpath, sizeof(seedpath));
215             len = strlen(seedpath);
216         } else
217             len += ret;
218         strcpy(seedpath+len, "\\PUTTY.RND");
219     }
220 }
221
222 void read_random_seed(noise_consumer_t consumer) {
223     HANDLE seedf;
224
225     if (!seedpath[0])
226         get_seedpath();
227
228     seedf = CreateFile(seedpath, GENERIC_READ,
229                        FILE_SHARE_READ | FILE_SHARE_WRITE,
230                        NULL, OPEN_EXISTING, 0, NULL);
231
232     if (seedf != INVALID_HANDLE_VALUE) {
233         while (1) {
234             char buf[1024];
235             DWORD len;
236
237             if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
238                 consumer(buf, len);
239             else
240                 break;
241         }
242         CloseHandle(seedf);
243     }
244 }
245
246 void write_random_seed(void *data, size_t len) {
247     HANDLE seedf;
248
249     if (!seedpath[0])
250         get_seedpath();
251
252     seedf = CreateFile(seedpath, GENERIC_WRITE, 0,
253                        NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
254
255     if (seedf != INVALID_HANDLE_VALUE) {
256         DWORD lenwritten;
257
258         WriteFile(seedf, data, len, &lenwritten, NULL);
259         CloseHandle(seedf);
260     }
261 }
262
263 /*
264  * Recursively delete a registry key and everything under it.
265  */
266 static void registry_recursive_remove(HKEY key) {
267     DWORD i;
268     char name[MAX_PATH+1];
269     HKEY subkey;
270
271     i = 0;
272     while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
273         if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
274             registry_recursive_remove(subkey);
275             RegCloseKey(subkey);
276         }
277         RegDeleteKey(key, name);
278     }
279 }
280
281 void cleanup_all(void) {
282     HKEY key;
283     int ret;
284     char name[MAX_PATH+1];
285
286     /* ------------------------------------------------------------
287      * Wipe out the random seed file.
288      */
289     if (!seedpath[0])
290         get_seedpath();
291     remove(seedpath);
292
293     /* ------------------------------------------------------------
294      * Destroy all registry information associated with PuTTY.
295      */
296
297     /*
298      * Open the main PuTTY registry key and remove everything in it.
299      */
300     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == ERROR_SUCCESS) {
301         registry_recursive_remove(key);
302         RegCloseKey(key);
303     }
304     /*
305      * Now open the parent key and remove the PuTTY main key. Once
306      * we've done that, see if the parent key has any other
307      * children.
308      */
309     if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
310                    &key) == ERROR_SUCCESS) {
311         RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
312         ret = RegEnumKey(key, 0, name, sizeof(name));
313         RegCloseKey(key);
314         /*
315          * If the parent key had no other children, we must delete
316          * it in its turn. That means opening the _grandparent_
317          * key.
318          */
319         if (ret != ERROR_SUCCESS) {
320             if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
321                            &key) == ERROR_SUCCESS) {
322                 RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
323                 RegCloseKey(key);
324             }
325         }
326     }
327     /*
328      * Now we're done.
329      */
330 }