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