]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxstore.c
Can now save and load settings under Unix.
[PuTTY.git] / unix / uxstore.c
1 /*
2  * uxstore.c: Unix-specific implementation of the interface defined
3  * in storage.h.
4  */
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <assert.h>
9 #include <ctype.h>
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <dirent.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include "putty.h"
16 #include "storage.h"
17 #include "tree234.h"
18
19 enum {
20     INDEX_DIR, INDEX_HOSTKEYS, INDEX_RANDSEED,
21     INDEX_SESSIONDIR, INDEX_SESSION,
22 };
23
24 static const char hex[16] = "0123456789ABCDEF";
25
26 static char *mungestr(const char *in)
27 {
28     char *out, *ret;
29
30     if (!in)
31         in = "Default Settings";
32
33     ret = out = snewn(3*strlen(in)+1, char);
34
35     while (*in) {
36         /*
37          * There are remarkably few punctuation characters that
38          * aren't shell-special in some way or likely to be used as
39          * separators in some file format or another! Hence we use
40          * opt-in for safe characters rather than opt-out for
41          * specific unsafe ones...
42          */
43         if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
44             !(*in >= '0' && *in <= '9') &&
45             !(*in >= 'A' && *in <= 'Z') &&
46             !(*in >= 'a' && *in <= 'z')) {
47             *out++ = '%';
48             *out++ = hex[((unsigned char) *in) >> 4];
49             *out++ = hex[((unsigned char) *in) & 15];
50         } else
51             *out++ = *in;
52         in++;
53     }
54     *out = '\0';
55     return ret;
56 }
57
58 static char *unmungestr(const char *in)
59 {
60     char *out, *ret;
61     out = ret = snewn(strlen(in)+1, char);
62     while (*in) {
63         if (*in == '%' && in[1] && in[2]) {
64             int i, j;
65
66             i = in[1] - '0';
67             i -= (i > 9 ? 7 : 0);
68             j = in[2] - '0';
69             j -= (j > 9 ? 7 : 0);
70
71             *out++ = (i << 4) + j;
72             in += 3;
73         } else {
74             *out++ = *in++;
75         }
76     }
77     *out = '\0';
78     return ret;
79 }
80
81 static void make_filename(char *filename, int index, const char *subname)
82 {
83     char *home;
84     int len;
85     home = getenv("HOME");
86     strncpy(filename, home, FILENAME_MAX);
87     len = strlen(filename);
88     if (index == INDEX_SESSION) {
89         char *munged = mungestr(subname);
90         char *fn = dupprintf("/.putty/sessions/%s", munged);
91         strncpy(filename + len, fn, FILENAME_MAX - len);
92         sfree(fn);
93         sfree(munged);
94     } else {
95         strncpy(filename + len,
96                 index == INDEX_DIR ? "/.putty" :
97                 index == INDEX_SESSIONDIR ? "/.putty/sessions" :
98                 index == INDEX_HOSTKEYS ? "/.putty/sshhostkeys" :
99                 index == INDEX_RANDSEED ? "/.putty/randomseed" :
100                 "/.putty/ERROR", FILENAME_MAX - len);
101     }
102     filename[FILENAME_MAX-1] = '\0';
103 }
104
105 /*
106  * Read an entire line of text from a file. Return a buffer
107  * malloced to be as big as necessary (caller must free).
108  */
109 static char *fgetline(FILE *fp)
110 {
111     char *ret = snewn(512, char);
112     int size = 512, len = 0;
113     while (fgets(ret + len, size - len, fp)) {
114         len += strlen(ret + len);
115         if (ret[len-1] == '\n')
116             break;                     /* got a newline, we're done */
117         size = len + 512;
118         ret = sresize(ret, size, char);
119     }
120     if (len == 0) {                    /* first fgets returned NULL */
121         sfree(ret);
122         return NULL;
123     }
124     ret[len] = '\0';
125     return ret;
126 }
127
128 /*
129  * For the moment, the only existing Unix utility is pterm and that
130  * has no GUI configuration at all, so our write routines need do
131  * nothing. Eventually I suppose these will read and write an rc
132  * file somewhere or other.
133  */
134
135 void *open_settings_w(const char *sessionname)
136 {
137     char filename[FILENAME_MAX];
138     FILE *fp;
139
140     /*
141      * Start by making sure the sessions subdir exists. Ignore the
142      * error return from mkdir since it's perfectly likely to be
143      * `already exists', and any other error will trip us up later
144      * on so there's no real need to catch it now.
145      */
146     make_filename(filename, INDEX_SESSIONDIR, sessionname);
147     mkdir(filename, 0700);
148
149     make_filename(filename, INDEX_SESSION, sessionname);
150     fp = fopen(filename, "w");
151     if (!fp)
152         return NULL;                   /* can't open */
153     return fp;
154 }
155
156 void write_setting_s(void *handle, const char *key, const char *value)
157 {
158     FILE *fp = (FILE *)handle;
159     fprintf(fp, "%s=%s\n", key, value);
160 }
161
162 void write_setting_i(void *handle, const char *key, int value)
163 {
164     FILE *fp = (FILE *)handle;
165     fprintf(fp, "%s=%d\n", key, value);
166 }
167
168 void close_settings_w(void *handle)
169 {
170     FILE *fp = (FILE *)handle;
171     fclose(fp);
172 }
173
174 /*
175  * Reading settings, for the moment, is done by retrieving X
176  * resources from the X display. When we introduce disk files, I
177  * think what will happen is that the X resources will override
178  * PuTTY's inbuilt defaults, but that the disk files will then
179  * override those. This isn't optimal, but it's the best I can
180  * immediately work out.
181  */
182
183 struct keyval {
184     const char *key;
185     const char *value;
186 };
187
188 static tree234 *xrmtree = NULL;
189
190 int keycmp(void *av, void *bv)
191 {
192     struct keyval *a = (struct keyval *)av;
193     struct keyval *b = (struct keyval *)bv;
194     return strcmp(a->key, b->key);
195 }
196
197 void provide_xrm_string(char *string)
198 {
199     char *p, *q, *key;
200     struct keyval *xrms, *ret;
201
202     p = q = strchr(string, ':');
203     if (!q) {
204         fprintf(stderr, "pterm: expected a colon in resource string"
205                 " \"%s\"\n", string);
206         return;
207     }
208     q++;
209     while (p > string && p[-1] != '.' && p[-1] != '*')
210         p--;
211     xrms = snew(struct keyval);
212     key = snewn(q-p, char);
213     memcpy(key, p, q-p);
214     key[q-p-1] = '\0';
215     xrms->key = key;
216     while (*q && isspace((unsigned char)*q))
217         q++;
218     xrms->value = dupstr(q);
219
220     if (!xrmtree)
221         xrmtree = newtree234(keycmp);
222
223     ret = add234(xrmtree, xrms);
224     if (ret) {
225         /* Override an existing string. */
226         del234(xrmtree, ret);
227         add234(xrmtree, xrms);
228     }
229 }
230
231 const char *get_setting(const char *key)
232 {
233     struct keyval tmp, *ret;
234     tmp.key = key;
235     if (xrmtree) {
236         ret = find234(xrmtree, &tmp, NULL);
237         if (ret)
238             return ret->value;
239     }
240     return x_get_default(key);
241 }
242
243 void *open_settings_r(const char *sessionname)
244 {
245     char filename[FILENAME_MAX];
246     FILE *fp;
247     char *line;
248     tree234 *ret;
249
250     make_filename(filename, INDEX_SESSION, sessionname);
251     fp = fopen(filename, "r");
252     if (!fp)
253         return NULL;                   /* can't open */
254
255     ret = newtree234(keycmp);
256
257     while ( (line = fgetline(fp)) ) {
258         char *value = strchr(line, '=');
259         struct keyval *kv;
260
261         if (!value)
262             continue;
263         *value++ = '\0';
264         value[strcspn(value, "\r\n")] = '\0';   /* trim trailing NL */
265
266         kv = snew(struct keyval);
267         kv->key = dupstr(line);
268         kv->value = dupstr(value);
269         add234(ret, kv);
270
271         sfree(line);
272     }
273
274     fclose(fp);
275
276     return ret;
277 }
278
279 char *read_setting_s(void *handle, const char *key, char *buffer, int buflen)
280 {
281     tree234 *tree = (tree234 *)handle;
282     const char *val;
283     struct keyval tmp, *kv;
284
285     tmp.key = key;
286     if (tree != NULL &&
287         (kv = find234(tree, &tmp, NULL)) != NULL) {
288         val = kv->value;
289         assert(val != NULL);
290     } else
291         val = get_setting(key);
292
293     if (!val)
294         return NULL;
295     else {
296         strncpy(buffer, val, buflen);
297         buffer[buflen-1] = '\0';
298         return buffer;
299     }
300 }
301
302 int read_setting_i(void *handle, const char *key, int defvalue)
303 {
304     tree234 *tree = (tree234 *)handle;
305     const char *val;
306     struct keyval tmp, *kv;
307
308     tmp.key = key;
309     if (tree != NULL &&
310         (kv = find234(tree, &tmp, NULL)) != NULL) {
311         val = kv->value;
312         assert(val != NULL);
313     } else
314         val = get_setting(key);
315
316     if (!val)
317         return defvalue;
318     else
319         return atoi(val);
320 }
321
322 int read_setting_fontspec(void *handle, const char *name, FontSpec *result)
323 {
324     return !!read_setting_s(handle, name, result->name, sizeof(result->name));
325 }
326 int read_setting_filename(void *handle, const char *name, Filename *result)
327 {
328     return !!read_setting_s(handle, name, result->path, sizeof(result->path));
329 }
330
331 void write_setting_fontspec(void *handle, const char *name, FontSpec result)
332 {
333     write_setting_s(handle, name, result.name);
334 }
335 void write_setting_filename(void *handle, const char *name, Filename result)
336 {
337     write_setting_s(handle, name, result.path);
338 }
339
340 void close_settings_r(void *handle)
341 {
342     tree234 *tree = (tree234 *)handle;
343     struct keyval *kv;
344
345     if (!tree)
346         return;
347
348     while ( (kv = index234(tree, 0)) != NULL) {
349         del234(tree, kv);
350         sfree((char *)kv->key);
351         sfree((char *)kv->value);
352         sfree(kv);
353     }
354
355     freetree234(tree);
356 }
357
358 void del_settings(const char *sessionname)
359 {
360     char filename[FILENAME_MAX];
361     make_filename(filename, INDEX_SESSION, sessionname);
362     unlink(filename);
363 }
364
365 void *enum_settings_start(void)
366 {
367     DIR *dp;
368     char filename[FILENAME_MAX];
369
370     make_filename(filename, INDEX_SESSIONDIR, NULL);
371     dp = opendir(filename);
372
373     return dp;
374 }
375
376 char *enum_settings_next(void *handle, char *buffer, int buflen)
377 {
378     DIR *dp = (DIR *)handle;
379     struct dirent *de;
380     struct stat st;
381     char fullpath[FILENAME_MAX];
382     int len;
383     char *unmunged;
384
385     make_filename(fullpath, INDEX_SESSIONDIR, NULL);
386     len = strlen(fullpath);
387
388     while ( (de = readdir(dp)) != NULL ) {
389         if (len < FILENAME_MAX) {
390             fullpath[len] = '/';
391             strncpy(fullpath+len+1, de->d_name, FILENAME_MAX-(len+1));
392             fullpath[FILENAME_MAX-1] = '\0';
393         }
394
395         if (stat(fullpath, &st) < 0 || !S_ISREG(st.st_mode))
396             continue;                  /* try another one */
397
398         unmunged = unmungestr(de->d_name);
399         strncpy(buffer, unmunged, buflen);
400         buffer[buflen-1] = '\0';
401         sfree(unmunged);
402         return buffer;
403     }
404
405     return NULL;
406 }
407
408 void enum_settings_finish(void *handle)
409 {
410     DIR *dp = (DIR *)handle;
411     closedir(dp);
412 }
413
414 /*
415  * Lines in the host keys file are of the form
416  * 
417  *   type@port:hostname keydata
418  * 
419  * e.g.
420  * 
421  *   rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
422  */
423 int verify_host_key(const char *hostname, int port,
424                     const char *keytype, const char *key)
425 {
426     FILE *fp;
427     char filename[FILENAME_MAX];
428     char *line;
429     int ret;
430
431     make_filename(filename, INDEX_HOSTKEYS, NULL);
432     fp = fopen(filename, "r");
433     if (!fp)
434         return 1;                      /* key does not exist */
435
436     ret = 1;
437     while ( (line = fgetline(fp)) ) {
438         int i;
439         char *p = line;
440         char porttext[20];
441
442         line[strcspn(line, "\n")] = '\0';   /* strip trailing newline */
443
444         i = strlen(keytype);
445         if (strncmp(p, keytype, i))
446             goto done;
447         p += i;
448
449         if (*p != '@')
450             goto done;
451         p++;
452
453         sprintf(porttext, "%d", port);
454         i = strlen(porttext);
455         if (strncmp(p, porttext, i))
456             goto done;
457         p += i;
458
459         if (*p != ':')
460             goto done;
461         p++;
462
463         i = strlen(hostname);
464         if (strncmp(p, hostname, i))
465             goto done;
466         p += i;
467
468         if (*p != ' ')
469             goto done;
470         p++;
471
472         /*
473          * Found the key. Now just work out whether it's the right
474          * one or not.
475          */
476         if (!strcmp(p, key))
477             ret = 0;                   /* key matched OK */
478         else
479             ret = 2;                   /* key mismatch */
480
481         done:
482         sfree(line);
483         if (ret != 1)
484             break;
485     }
486
487     return ret;
488 }
489
490 void store_host_key(const char *hostname, int port,
491                     const char *keytype, const char *key)
492 {
493     FILE *fp;
494     int fd;
495     char filename[FILENAME_MAX];
496
497     make_filename(filename, INDEX_HOSTKEYS, NULL);
498     fd = open(filename, O_CREAT | O_APPEND | O_RDWR, 0600);
499     if (fd < 0) {
500         char dir[FILENAME_MAX];
501
502         make_filename(dir, INDEX_DIR, NULL);
503         mkdir(dir, 0700);
504         fd = open(filename, O_CREAT | O_APPEND | O_RDWR, 0600);
505     }
506     if (fd < 0) {
507         perror(filename);
508         exit(1);
509     }
510     fp = fdopen(fd, "a");
511     fprintf(fp, "%s@%d:%s %s\n", keytype, port, hostname, key);
512     fclose(fp);
513 }
514
515 void read_random_seed(noise_consumer_t consumer)
516 {
517     int fd;
518     char fname[FILENAME_MAX];
519
520     make_filename(fname, INDEX_RANDSEED, NULL);
521     fd = open(fname, O_RDONLY);
522     if (fd) {
523         char buf[512];
524         int ret;
525         while ( (ret = read(fd, buf, sizeof(buf))) > 0)
526             consumer(buf, ret);
527         close(fd);
528     }
529 }
530
531 void write_random_seed(void *data, int len)
532 {
533     int fd;
534     char fname[FILENAME_MAX];
535
536     make_filename(fname, INDEX_RANDSEED, NULL);
537     /*
538      * Don't truncate the random seed file if it already exists; if
539      * something goes wrong half way through writing it, it would
540      * be better to leave the old data there than to leave it empty.
541      */
542     fd = open(fname, O_CREAT | O_WRONLY, 0600);
543     if (fd < 0) {
544         char dir[FILENAME_MAX];
545
546         make_filename(dir, INDEX_DIR, NULL);
547         mkdir(dir, 0700);
548         fd = open(fname, O_CREAT | O_WRONLY, 0600);
549     }
550
551     while (len > 0) {
552         int ret = write(fd, data, len);
553         if (ret <= 0) break;
554         len -= ret;
555         data = (char *)data + len;
556     }
557
558     close(fd);
559 }
560
561 void cleanup_all(void)
562 {
563 }