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