]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxstore.c
913b736ac2ea7e410b98804143b042f3cec5cd8d
[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 <string.h>
9 #include <assert.h>
10 #include <errno.h>
11 #include <ctype.h>
12 #include <limits.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <dirent.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <pwd.h>
19 #include "putty.h"
20 #include "storage.h"
21 #include "tree234.h"
22
23 #ifdef PATH_MAX
24 #define FNLEN PATH_MAX
25 #else
26 #define FNLEN 1024 /* XXX */
27 #endif
28
29 enum {
30     INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
31     INDEX_SESSIONDIR, INDEX_SESSION,
32 };
33
34 static const char hex[16] = "0123456789ABCDEF";
35
36 static char *mungestr(const char *in)
37 {
38     char *out, *ret;
39
40     if (!in || !*in)
41         in = "Default Settings";
42
43     ret = out = snewn(3*strlen(in)+1, char);
44
45     while (*in) {
46         /*
47          * There are remarkably few punctuation characters that
48          * aren't shell-special in some way or likely to be used as
49          * separators in some file format or another! Hence we use
50          * opt-in for safe characters rather than opt-out for
51          * specific unsafe ones...
52          */
53         if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
54             !(*in >= '0' && *in <= '9') &&
55             !(*in >= 'A' && *in <= 'Z') &&
56             !(*in >= 'a' && *in <= 'z')) {
57             *out++ = '%';
58             *out++ = hex[((unsigned char) *in) >> 4];
59             *out++ = hex[((unsigned char) *in) & 15];
60         } else
61             *out++ = *in;
62         in++;
63     }
64     *out = '\0';
65     return ret;
66 }
67
68 static char *unmungestr(const char *in)
69 {
70     char *out, *ret;
71     out = ret = snewn(strlen(in)+1, char);
72     while (*in) {
73         if (*in == '%' && in[1] && in[2]) {
74             int i, j;
75
76             i = in[1] - '0';
77             i -= (i > 9 ? 7 : 0);
78             j = in[2] - '0';
79             j -= (j > 9 ? 7 : 0);
80
81             *out++ = (i << 4) + j;
82             in += 3;
83         } else {
84             *out++ = *in++;
85         }
86     }
87     *out = '\0';
88     return ret;
89 }
90
91 static char *make_filename(int index, const char *subname)
92 {
93     char *env, *tmp, *ret;
94
95     /*
96      * Allow override of the PuTTY configuration location, and of
97      * specific subparts of it, by means of environment variables.
98      */
99     if (index == INDEX_DIR) {
100         struct passwd *pwd;
101
102         env = getenv("PUTTYDIR");
103         if (env)
104             return dupstr(env);
105         env = getenv("HOME");
106         if (env)
107             return dupprintf("%s/.putty", env);
108         pwd = getpwuid(getuid());
109         if (pwd && pwd->pw_dir)
110             return dupprintf("%s/.putty", pwd->pw_dir);
111         return dupstr("/.putty");
112     }
113     if (index == INDEX_SESSIONDIR) {
114         env = getenv("PUTTYSESSIONS");
115         if (env)
116             return dupstr(env);
117         tmp = make_filename(INDEX_DIR, NULL);
118         ret = dupprintf("%s/sessions", tmp);
119         sfree(tmp);
120         return ret;
121     }
122     if (index == INDEX_SESSION) {
123         char *munged = mungestr(subname);
124         tmp = make_filename(INDEX_SESSIONDIR, NULL);
125         ret = dupprintf("%s/%s", tmp, munged);
126         sfree(tmp);
127         sfree(munged);
128         return ret;
129     }
130     if (index == INDEX_HOSTKEYS) {
131         env = getenv("PUTTYSSHHOSTKEYS");
132         if (env)
133             return dupstr(env);
134         tmp = make_filename(INDEX_DIR, NULL);
135         ret = dupprintf("%s/sshhostkeys", tmp);
136         sfree(tmp);
137         return ret;
138     }
139     if (index == INDEX_HOSTKEYS_TMP) {
140         tmp = make_filename(INDEX_HOSTKEYS, NULL);
141         ret = dupprintf("%s.tmp", tmp);
142         sfree(tmp);
143         return ret;
144     }
145     if (index == INDEX_RANDSEED) {
146         env = getenv("PUTTYRANDOMSEED");
147         if (env)
148             return dupstr(env);
149         tmp = make_filename(INDEX_DIR, NULL);
150         ret = dupprintf("%s/randomseed", tmp);
151         sfree(tmp);
152         return ret;
153     }
154     tmp = make_filename(INDEX_DIR, NULL);
155     ret = dupprintf("%s/ERROR", tmp);
156     sfree(tmp);
157     return ret;
158 }
159
160 void *open_settings_w(const char *sessionname, char **errmsg)
161 {
162     char *filename;
163     FILE *fp;
164
165     *errmsg = NULL;
166
167     /*
168      * Start by making sure the .putty directory and its sessions
169      * subdir actually exist. Ignore error returns from mkdir since
170      * they're perfectly likely to be `already exists', and any
171      * other error will trip us up later on so there's no real need
172      * to catch it now.
173      */
174     filename = make_filename(INDEX_SESSIONDIR, NULL);
175     if (mkdir(filename, 0700) != 0) {
176         char *filename2 = make_filename(INDEX_DIR, NULL);
177         mkdir(filename2, 0700);
178         sfree(filename2);
179         mkdir(filename, 0700);
180     }
181     sfree(filename);
182
183     filename = make_filename(INDEX_SESSION, sessionname);
184     fp = fopen(filename, "w");
185     if (!fp) {
186         *errmsg = dupprintf("Unable to create %s: %s",
187                             filename, strerror(errno));
188         sfree(filename);
189         return NULL;                   /* can't open */
190     }
191     sfree(filename);
192     return fp;
193 }
194
195 void write_setting_s(void *handle, const char *key, const char *value)
196 {
197     FILE *fp = (FILE *)handle;
198     fprintf(fp, "%s=%s\n", key, value);
199 }
200
201 void write_setting_i(void *handle, const char *key, int value)
202 {
203     FILE *fp = (FILE *)handle;
204     fprintf(fp, "%s=%d\n", key, value);
205 }
206
207 void close_settings_w(void *handle)
208 {
209     FILE *fp = (FILE *)handle;
210     fclose(fp);
211 }
212
213 /*
214  * Reading settings, for the moment, is done by retrieving X
215  * resources from the X display. When we introduce disk files, I
216  * think what will happen is that the X resources will override
217  * PuTTY's inbuilt defaults, but that the disk files will then
218  * override those. This isn't optimal, but it's the best I can
219  * immediately work out.
220  * FIXME: the above comment is a bit out of date. Did it happen?
221  */
222
223 struct skeyval {
224     const char *key;
225     const char *value;
226 };
227
228 static tree234 *xrmtree = NULL;
229
230 int keycmp(void *av, void *bv)
231 {
232     struct skeyval *a = (struct skeyval *)av;
233     struct skeyval *b = (struct skeyval *)bv;
234     return strcmp(a->key, b->key);
235 }
236
237 void provide_xrm_string(char *string)
238 {
239     char *p, *q, *key;
240     struct skeyval *xrms, *ret;
241
242     p = q = strchr(string, ':');
243     if (!q) {
244         fprintf(stderr, "pterm: expected a colon in resource string"
245                 " \"%s\"\n", string);
246         return;
247     }
248     q++;
249     while (p > string && p[-1] != '.' && p[-1] != '*')
250         p--;
251     xrms = snew(struct skeyval);
252     key = snewn(q-p, char);
253     memcpy(key, p, q-p);
254     key[q-p-1] = '\0';
255     xrms->key = key;
256     while (*q && isspace((unsigned char)*q))
257         q++;
258     xrms->value = dupstr(q);
259
260     if (!xrmtree)
261         xrmtree = newtree234(keycmp);
262
263     ret = add234(xrmtree, xrms);
264     if (ret) {
265         /* Override an existing string. */
266         del234(xrmtree, ret);
267         add234(xrmtree, xrms);
268     }
269 }
270
271 const char *get_setting(const char *key)
272 {
273     struct skeyval tmp, *ret;
274     tmp.key = key;
275     if (xrmtree) {
276         ret = find234(xrmtree, &tmp, NULL);
277         if (ret)
278             return ret->value;
279     }
280     return x_get_default(key);
281 }
282
283 void *open_settings_r(const char *sessionname)
284 {
285     char *filename;
286     FILE *fp;
287     char *line;
288     tree234 *ret;
289
290     filename = make_filename(INDEX_SESSION, sessionname);
291     fp = fopen(filename, "r");
292     sfree(filename);
293     if (!fp)
294         return NULL;                   /* can't open */
295
296     ret = newtree234(keycmp);
297
298     while ( (line = fgetline(fp)) ) {
299         char *value = strchr(line, '=');
300         struct skeyval *kv;
301
302         if (!value) {
303             sfree(line);
304             continue;
305         }
306         *value++ = '\0';
307         value[strcspn(value, "\r\n")] = '\0';   /* trim trailing NL */
308
309         kv = snew(struct skeyval);
310         kv->key = dupstr(line);
311         kv->value = dupstr(value);
312         add234(ret, kv);
313
314         sfree(line);
315     }
316
317     fclose(fp);
318
319     return ret;
320 }
321
322 char *read_setting_s(void *handle, const char *key)
323 {
324     tree234 *tree = (tree234 *)handle;
325     const char *val;
326     struct skeyval tmp, *kv;
327
328     tmp.key = key;
329     if (tree != NULL &&
330         (kv = find234(tree, &tmp, NULL)) != NULL) {
331         val = kv->value;
332         assert(val != NULL);
333     } else
334         val = get_setting(key);
335
336     if (!val)
337         return NULL;
338     else
339         return dupstr(val);
340 }
341
342 int read_setting_i(void *handle, const char *key, int defvalue)
343 {
344     tree234 *tree = (tree234 *)handle;
345     const char *val;
346     struct skeyval tmp, *kv;
347
348     tmp.key = key;
349     if (tree != NULL &&
350         (kv = find234(tree, &tmp, NULL)) != NULL) {
351         val = kv->value;
352         assert(val != NULL);
353     } else
354         val = get_setting(key);
355
356     if (!val)
357         return defvalue;
358     else
359         return atoi(val);
360 }
361
362 FontSpec *read_setting_fontspec(void *handle, const char *name)
363 {
364     /*
365      * In GTK1-only PuTTY, we used to store font names simply as a
366      * valid X font description string (logical or alias), under a
367      * bare key such as "Font".
368      * 
369      * In GTK2 PuTTY, we have a prefix system where "client:"
370      * indicates a Pango font and "server:" an X one; existing
371      * configuration needs to be reinterpreted as having the
372      * "server:" prefix, so we change the storage key from the
373      * provided name string (e.g. "Font") to a suffixed one
374      * ("FontName").
375      */
376     char *suffname = dupcat(name, "Name", NULL);
377     char *tmp;
378
379     if ((tmp = read_setting_s(handle, suffname)) != NULL) {
380         FontSpec *fs = fontspec_new(tmp);
381         sfree(suffname);
382         sfree(tmp);
383         return fs;                     /* got new-style name */
384     }
385     sfree(suffname);
386
387     /* Fall back to old-style name. */
388     tmp = read_setting_s(handle, name);
389     if (tmp && *tmp) {
390         char *tmp2 = dupcat("server:", tmp, NULL);
391         FontSpec *fs = fontspec_new(tmp2);
392         sfree(tmp2);
393         sfree(tmp);
394         return fs;
395     } else {
396         sfree(tmp);
397         return NULL;
398     }
399 }
400 Filename *read_setting_filename(void *handle, const char *name)
401 {
402     char *tmp = read_setting_s(handle, name);
403     if (tmp) {
404         Filename *ret = filename_from_str(tmp);
405         sfree(tmp);
406         return ret;
407     } else
408         return NULL;
409 }
410
411 void write_setting_fontspec(void *handle, const char *name, FontSpec *fs)
412 {
413     /*
414      * read_setting_fontspec had to handle two cases, but when
415      * writing our settings back out we simply always generate the
416      * new-style name.
417      */
418     char *suffname = dupcat(name, "Name", NULL);
419     write_setting_s(handle, suffname, fs->name);
420     sfree(suffname);
421 }
422 void write_setting_filename(void *handle, const char *name, Filename *result)
423 {
424     write_setting_s(handle, name, result->path);
425 }
426
427 void close_settings_r(void *handle)
428 {
429     tree234 *tree = (tree234 *)handle;
430     struct skeyval *kv;
431
432     if (!tree)
433         return;
434
435     while ( (kv = index234(tree, 0)) != NULL) {
436         del234(tree, kv);
437         sfree((char *)kv->key);
438         sfree((char *)kv->value);
439         sfree(kv);
440     }
441
442     freetree234(tree);
443 }
444
445 void del_settings(const char *sessionname)
446 {
447     char *filename;
448     filename = make_filename(INDEX_SESSION, sessionname);
449     unlink(filename);
450     sfree(filename);
451 }
452
453 void *enum_settings_start(void)
454 {
455     DIR *dp;
456     char *filename;
457
458     filename = make_filename(INDEX_SESSIONDIR, NULL);
459     dp = opendir(filename);
460     sfree(filename);
461
462     return dp;
463 }
464
465 char *enum_settings_next(void *handle, char *buffer, int buflen)
466 {
467     DIR *dp = (DIR *)handle;
468     struct dirent *de;
469     struct stat st;
470     char *fullpath;
471     int maxlen, thislen, len;
472     char *unmunged;
473
474     fullpath = make_filename(INDEX_SESSIONDIR, NULL);
475     maxlen = len = strlen(fullpath);
476
477     while ( (de = readdir(dp)) != NULL ) {
478         thislen = len + 1 + strlen(de->d_name);
479         if (maxlen < thislen) {
480             maxlen = thislen;
481             fullpath = sresize(fullpath, maxlen+1, char);
482         }
483         fullpath[len] = '/';
484         strncpy(fullpath+len+1, de->d_name, thislen - (len+1));
485         fullpath[thislen] = '\0';
486
487         if (stat(fullpath, &st) < 0 || !S_ISREG(st.st_mode))
488             continue;                  /* try another one */
489
490         unmunged = unmungestr(de->d_name);
491         strncpy(buffer, unmunged, buflen);
492         buffer[buflen-1] = '\0';
493         sfree(unmunged);
494         sfree(fullpath);
495         return buffer;
496     }
497
498     sfree(fullpath);
499     return NULL;
500 }
501
502 void enum_settings_finish(void *handle)
503 {
504     DIR *dp = (DIR *)handle;
505     closedir(dp);
506 }
507
508 /*
509  * Lines in the host keys file are of the form
510  * 
511  *   type@port:hostname keydata
512  * 
513  * e.g.
514  * 
515  *   rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
516  */
517 int verify_host_key(const char *hostname, int port,
518                     const char *keytype, const char *key)
519 {
520     FILE *fp;
521     char *filename;
522     char *line;
523     int ret;
524
525     filename = make_filename(INDEX_HOSTKEYS, NULL);
526     fp = fopen(filename, "r");
527     sfree(filename);
528     if (!fp)
529         return 1;                      /* key does not exist */
530
531     ret = 1;
532     while ( (line = fgetline(fp)) ) {
533         int i;
534         char *p = line;
535         char porttext[20];
536
537         line[strcspn(line, "\n")] = '\0';   /* strip trailing newline */
538
539         i = strlen(keytype);
540         if (strncmp(p, keytype, i))
541             goto done;
542         p += i;
543
544         if (*p != '@')
545             goto done;
546         p++;
547
548         sprintf(porttext, "%d", port);
549         i = strlen(porttext);
550         if (strncmp(p, porttext, i))
551             goto done;
552         p += i;
553
554         if (*p != ':')
555             goto done;
556         p++;
557
558         i = strlen(hostname);
559         if (strncmp(p, hostname, i))
560             goto done;
561         p += i;
562
563         if (*p != ' ')
564             goto done;
565         p++;
566
567         /*
568          * Found the key. Now just work out whether it's the right
569          * one or not.
570          */
571         if (!strcmp(p, key))
572             ret = 0;                   /* key matched OK */
573         else
574             ret = 2;                   /* key mismatch */
575
576         done:
577         sfree(line);
578         if (ret != 1)
579             break;
580     }
581
582     fclose(fp);
583     return ret;
584 }
585
586 void store_host_key(const char *hostname, int port,
587                     const char *keytype, const char *key)
588 {
589     FILE *rfp, *wfp;
590     char *newtext, *line;
591     int headerlen;
592     char *filename, *tmpfilename;
593
594     /*
595      * Open both the old file and a new file.
596      */
597     tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL);
598     wfp = fopen(tmpfilename, "w");
599     if (!wfp && errno == ENOENT) {
600         char *dir;
601
602         dir = make_filename(INDEX_DIR, NULL);
603         if (mkdir(dir, 0700) < 0) {
604             char *msg = dupprintf("Unable to store host key: mkdir(\"%s\") "
605                                   "returned '%s'", dir, strerror(errno));
606             nonfatal(msg);
607             sfree(dir);
608             sfree(tmpfilename);
609             return;
610         }
611         sfree(dir);
612
613         wfp = fopen(tmpfilename, "w");
614     }
615     if (!wfp) {
616         char *msg = dupprintf("Unable to store host key: open(\"%s\") "
617                               "returned '%s'", tmpfilename, strerror(errno));
618         nonfatal(msg);
619         sfree(tmpfilename);
620         return;
621     }
622     filename = make_filename(INDEX_HOSTKEYS, NULL);
623     rfp = fopen(filename, "r");
624
625     newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key);
626     headerlen = 1 + strcspn(newtext, " ");   /* count the space too */
627
628     /*
629      * Copy all lines from the old file to the new one that _don't_
630      * involve the same host key identifier as the one we're adding.
631      */
632     if (rfp) {
633         while ( (line = fgetline(rfp)) ) {
634             if (strncmp(line, newtext, headerlen))
635                 fputs(line, wfp);
636             sfree(line);
637         }
638         fclose(rfp);
639     }
640
641     /*
642      * Now add the new line at the end.
643      */
644     fputs(newtext, wfp);
645
646     fclose(wfp);
647
648     rename(tmpfilename, filename);
649
650     sfree(tmpfilename);
651     sfree(filename);
652     sfree(newtext);
653 }
654
655 void read_random_seed(noise_consumer_t consumer)
656 {
657     int fd;
658     char *fname;
659
660     fname = make_filename(INDEX_RANDSEED, NULL);
661     fd = open(fname, O_RDONLY);
662     sfree(fname);
663     if (fd >= 0) {
664         char buf[512];
665         int ret;
666         while ( (ret = read(fd, buf, sizeof(buf))) > 0)
667             consumer(buf, ret);
668         close(fd);
669     }
670 }
671
672 void write_random_seed(void *data, int len)
673 {
674     int fd;
675     char *fname;
676
677     fname = make_filename(INDEX_RANDSEED, NULL);
678     /*
679      * Don't truncate the random seed file if it already exists; if
680      * something goes wrong half way through writing it, it would
681      * be better to leave the old data there than to leave it empty.
682      */
683     fd = open(fname, O_CREAT | O_WRONLY, 0600);
684     if (fd < 0) {
685         if (errno != ENOENT) {
686             char *msg = dupprintf("Unable to write random seed: open(\"%s\") "
687                                   "returned '%s'", fname, strerror(errno));
688             nonfatal(msg);
689             sfree(msg);
690             return;
691         }
692         char *dir;
693
694         dir = make_filename(INDEX_DIR, NULL);
695         if (mkdir(dir, 0700) < 0) {
696             char *msg = dupprintf("Unable to write random seed: mkdir(\"%s\") "
697                                   "returned '%s'", dir, strerror(errno));
698             nonfatal(msg);
699             sfree(msg);
700             sfree(dir);
701             return;
702         }
703         sfree(dir);
704
705         fd = open(fname, O_CREAT | O_WRONLY, 0600);
706         if (errno != ENOENT) {
707             char *msg = dupprintf("Unable to write random seed: open(\"%s\") "
708                                   "returned '%s'", fname, strerror(errno));
709             nonfatal(msg);
710             sfree(msg);
711             return;
712         }
713     }
714
715     while (len > 0) {
716         int ret = write(fd, data, len);
717         if (ret < 0) {
718             char *msg = dupprintf("Unable to write random seed: write "
719                                   "returned '%s'", strerror(errno));
720             nonfatal(msg);
721             sfree(msg);
722             break;
723         }
724         len -= ret;
725         data = (char *)data + len;
726     }
727
728     close(fd);
729     sfree(fname);
730 }
731
732 void cleanup_all(void)
733 {
734 }