source: filezilla/trunk/fuentes/src/putty/unix/uxstore.c @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 4 years ago

First release to xenial

File size: 17.0 KB
Line 
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
29enum {
30    INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
31    INDEX_SESSIONDIR, INDEX_SESSION,
32};
33
34static const char hex[16] = "0123456789ABCDEF";
35
36static 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
68static 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
91static 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
160void *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
201void 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
207void 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
213void 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
229struct skeyval {
230    const char *key;
231    const char *value;
232};
233
234static tree234 *xrmtree = NULL;
235
236int 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
243void 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
277const 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
289void *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
328char *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
348int 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
368FontSpec *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}
406Filename *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
417void 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}
428void write_setting_filename(void *handle, const char *name, Filename *result)
429{
430    write_setting_s(handle, name, result->path);
431}
432
433void 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
451void del_settings(const char *sessionname)
452{
453    char *filename;
454    filename = make_filename(INDEX_SESSION, sessionname);
455    unlink(filename);
456    sfree(filename);
457}
458
459void *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
471char *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
508void 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 */
523int 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
592int 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
602void 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
673void 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
690void 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
745void cleanup_all(void)
746{
747}
Note: See TracBrowser for help on using the repository browser.