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