diff options
Diffstat (limited to 'ssh-rand-helper.c')
-rw-r--r-- | ssh-rand-helper.c | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/ssh-rand-helper.c b/ssh-rand-helper.c new file mode 100644 index 000000000..5b7a9fc61 --- /dev/null +++ b/ssh-rand-helper.c | |||
@@ -0,0 +1,805 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2001 Damien Miller. All rights reserved. | ||
3 | * | ||
4 | * Redistribution and use in source and binary forms, with or without | ||
5 | * modification, are permitted provided that the following conditions | ||
6 | * are met: | ||
7 | * 1. Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * 2. Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * | ||
13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
14 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
16 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
17 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
18 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
19 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
20 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
22 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
23 | */ | ||
24 | |||
25 | #include "includes.h" | ||
26 | |||
27 | #include <openssl/rand.h> | ||
28 | #include <openssl/sha.h> | ||
29 | #include <openssl/crypto.h> | ||
30 | |||
31 | /* SunOS 4.4.4 needs this */ | ||
32 | #ifdef HAVE_FLOATINGPOINT_H | ||
33 | # include <floatingpoint.h> | ||
34 | #endif /* HAVE_FLOATINGPOINT_H */ | ||
35 | |||
36 | #include "misc.h" | ||
37 | #include "xmalloc.h" | ||
38 | #include "atomicio.h" | ||
39 | #include "pathnames.h" | ||
40 | #include "log.h" | ||
41 | |||
42 | RCSID("$Id: ssh-rand-helper.c,v 1.1 2001/12/23 14:41:48 djm Exp $"); | ||
43 | |||
44 | #define RANDOM_SEED_SIZE 48 | ||
45 | |||
46 | #ifndef SSH_PRNG_SEED_FILE | ||
47 | # define SSH_PRNG_SEED_FILE _PATH_SSH_USER_DIR"/prng_seed" | ||
48 | #endif /* SSH_PRNG_SEED_FILE */ | ||
49 | #ifndef SSH_PRNG_COMMAND_FILE | ||
50 | # define SSH_PRNG_COMMAND_FILE ETCDIR "/ssh_prng_cmds" | ||
51 | #endif /* SSH_PRNG_COMMAND_FILE */ | ||
52 | |||
53 | |||
54 | #ifndef offsetof | ||
55 | # define offsetof(type, member) ((size_t) &((type *)0)->member) | ||
56 | #endif | ||
57 | |||
58 | /* Number of times to pass through command list gathering entropy */ | ||
59 | #define NUM_ENTROPY_RUNS 1 | ||
60 | |||
61 | /* Scale entropy estimates back by this amount on subsequent runs */ | ||
62 | #define SCALE_PER_RUN 10.0 | ||
63 | |||
64 | /* Minimum number of commands to be considered valid */ | ||
65 | #define MIN_ENTROPY_SOURCES 16 | ||
66 | |||
67 | #define WHITESPACE " \t\n" | ||
68 | |||
69 | #ifndef RUSAGE_SELF | ||
70 | # define RUSAGE_SELF 0 | ||
71 | #endif | ||
72 | #ifndef RUSAGE_CHILDREN | ||
73 | # define RUSAGE_CHILDREN 0 | ||
74 | #endif | ||
75 | |||
76 | #if defined(PRNGD_SOCKET) || defined(PRNGD_PORT) | ||
77 | # define USE_PRNGD | ||
78 | #endif | ||
79 | |||
80 | #ifdef USE_PRNGD | ||
81 | /* Collect entropy from PRNGD/EGD */ | ||
82 | int | ||
83 | get_random_bytes(unsigned char *buf, int len) | ||
84 | { | ||
85 | int fd; | ||
86 | char msg[2]; | ||
87 | #ifdef PRNGD_PORT | ||
88 | struct sockaddr_in addr; | ||
89 | #else | ||
90 | struct sockaddr_un addr; | ||
91 | #endif | ||
92 | int addr_len, rval, errors; | ||
93 | mysig_t old_sigpipe; | ||
94 | |||
95 | if (len > 255) | ||
96 | fatal("Too many bytes to read from PRNGD"); | ||
97 | |||
98 | memset(&addr, '\0', sizeof(addr)); | ||
99 | |||
100 | #ifdef PRNGD_PORT | ||
101 | addr.sin_family = AF_INET; | ||
102 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
103 | addr.sin_port = htons(PRNGD_PORT); | ||
104 | addr_len = sizeof(struct sockaddr_in); | ||
105 | #else /* use IP socket PRNGD_SOCKET instead */ | ||
106 | /* Sanity checks */ | ||
107 | if (sizeof(PRNGD_SOCKET) > sizeof(addr.sun_path)) | ||
108 | fatal("Random pool path is too long"); | ||
109 | |||
110 | addr.sun_family = AF_UNIX; | ||
111 | strlcpy(addr.sun_path, PRNGD_SOCKET, sizeof(addr.sun_path)); | ||
112 | addr_len = offsetof(struct sockaddr_un, sun_path) + | ||
113 | sizeof(PRNGD_SOCKET); | ||
114 | #endif | ||
115 | |||
116 | old_sigpipe = mysignal(SIGPIPE, SIG_IGN); | ||
117 | |||
118 | errors = rval = 0; | ||
119 | reopen: | ||
120 | #ifdef PRNGD_PORT | ||
121 | fd = socket(addr.sin_family, SOCK_STREAM, 0); | ||
122 | if (fd == -1) { | ||
123 | error("Couldn't create AF_INET socket: %s", strerror(errno)); | ||
124 | goto done; | ||
125 | } | ||
126 | #else | ||
127 | fd = socket(addr.sun_family, SOCK_STREAM, 0); | ||
128 | if (fd == -1) { | ||
129 | error("Couldn't create AF_UNIX socket: %s", strerror(errno)); | ||
130 | goto done; | ||
131 | } | ||
132 | #endif | ||
133 | |||
134 | if (connect(fd, (struct sockaddr*)&addr, addr_len) == -1) { | ||
135 | #ifdef PRNGD_PORT | ||
136 | error("Couldn't connect to PRNGD port %d: %s", | ||
137 | PRNGD_PORT, strerror(errno)); | ||
138 | #else | ||
139 | error("Couldn't connect to PRNGD socket \"%s\": %s", | ||
140 | addr.sun_path, strerror(errno)); | ||
141 | #endif | ||
142 | goto done; | ||
143 | } | ||
144 | |||
145 | /* Send blocking read request to PRNGD */ | ||
146 | msg[0] = 0x02; | ||
147 | msg[1] = len; | ||
148 | |||
149 | if (atomicio(write, fd, msg, sizeof(msg)) != sizeof(msg)) { | ||
150 | if (errno == EPIPE && errors < 10) { | ||
151 | close(fd); | ||
152 | errors++; | ||
153 | goto reopen; | ||
154 | } | ||
155 | error("Couldn't write to PRNGD socket: %s", | ||
156 | strerror(errno)); | ||
157 | goto done; | ||
158 | } | ||
159 | |||
160 | if (atomicio(read, fd, buf, len) != len) { | ||
161 | if (errno == EPIPE && errors < 10) { | ||
162 | close(fd); | ||
163 | errors++; | ||
164 | goto reopen; | ||
165 | } | ||
166 | error("Couldn't read from PRNGD socket: %s", | ||
167 | strerror(errno)); | ||
168 | goto done; | ||
169 | } | ||
170 | |||
171 | rval = 1; | ||
172 | done: | ||
173 | mysignal(SIGPIPE, old_sigpipe); | ||
174 | if (fd != -1) | ||
175 | close(fd); | ||
176 | return(rval); | ||
177 | } | ||
178 | |||
179 | static void | ||
180 | seed_openssl_rng(void) | ||
181 | { | ||
182 | unsigned char buf[RANDOM_SEED_SIZE]; | ||
183 | |||
184 | if (!get_random_bytes(buf, sizeof(buf))) | ||
185 | fatal("Entropy collection failed"); | ||
186 | |||
187 | RAND_add(buf, sizeof(buf), sizeof(buf)); | ||
188 | memset(buf, '\0', sizeof(buf)); | ||
189 | } | ||
190 | |||
191 | #else /* USE_PRNGD */ | ||
192 | |||
193 | /* | ||
194 | * FIXME: proper entropy estimations. All current values are guesses | ||
195 | * FIXME: (ATL) do estimates at compile time? | ||
196 | * FIXME: More entropy sources | ||
197 | */ | ||
198 | |||
199 | /* slow command timeouts (all in milliseconds) */ | ||
200 | /* static int entropy_timeout_default = ENTROPY_TIMEOUT_MSEC; */ | ||
201 | static int entropy_timeout_current = ENTROPY_TIMEOUT_MSEC; | ||
202 | |||
203 | typedef struct | ||
204 | { | ||
205 | /* Proportion of data that is entropy */ | ||
206 | double rate; | ||
207 | /* Counter goes positive if this command times out */ | ||
208 | unsigned int badness; | ||
209 | /* Increases by factor of two each timeout */ | ||
210 | unsigned int sticky_badness; | ||
211 | /* Path to executable */ | ||
212 | char *path; | ||
213 | /* argv to pass to executable */ | ||
214 | char *args[5]; | ||
215 | /* full command string (debug) */ | ||
216 | char *cmdstring; | ||
217 | } entropy_source_t; | ||
218 | |||
219 | double stir_from_system(void); | ||
220 | double stir_from_programs(void); | ||
221 | double stir_gettimeofday(double entropy_estimate); | ||
222 | double stir_clock(double entropy_estimate); | ||
223 | double stir_rusage(int who, double entropy_estimate); | ||
224 | double hash_output_from_command(entropy_source_t *src, char *hash); | ||
225 | |||
226 | /* this is initialised from a file, by prng_read_commands() */ | ||
227 | entropy_source_t *entropy_sources = NULL; | ||
228 | |||
229 | double | ||
230 | stir_from_system(void) | ||
231 | { | ||
232 | double total_entropy_estimate; | ||
233 | long int i; | ||
234 | |||
235 | total_entropy_estimate = 0; | ||
236 | |||
237 | i = getpid(); | ||
238 | RAND_add(&i, sizeof(i), 0.5); | ||
239 | total_entropy_estimate += 0.1; | ||
240 | |||
241 | i = getppid(); | ||
242 | RAND_add(&i, sizeof(i), 0.5); | ||
243 | total_entropy_estimate += 0.1; | ||
244 | |||
245 | i = getuid(); | ||
246 | RAND_add(&i, sizeof(i), 0.0); | ||
247 | i = getgid(); | ||
248 | RAND_add(&i, sizeof(i), 0.0); | ||
249 | |||
250 | total_entropy_estimate += stir_gettimeofday(1.0); | ||
251 | total_entropy_estimate += stir_clock(0.5); | ||
252 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 2.0); | ||
253 | |||
254 | return(total_entropy_estimate); | ||
255 | } | ||
256 | |||
257 | double | ||
258 | stir_from_programs(void) | ||
259 | { | ||
260 | int i; | ||
261 | int c; | ||
262 | double entropy_estimate; | ||
263 | double total_entropy_estimate; | ||
264 | char hash[SHA_DIGEST_LENGTH]; | ||
265 | |||
266 | total_entropy_estimate = 0; | ||
267 | for(i = 0; i < NUM_ENTROPY_RUNS; i++) { | ||
268 | c = 0; | ||
269 | while (entropy_sources[c].path != NULL) { | ||
270 | |||
271 | if (!entropy_sources[c].badness) { | ||
272 | /* Hash output from command */ | ||
273 | entropy_estimate = hash_output_from_command(&entropy_sources[c], hash); | ||
274 | |||
275 | /* Scale back entropy estimate according to command's rate */ | ||
276 | entropy_estimate *= entropy_sources[c].rate; | ||
277 | |||
278 | /* Upper bound of entropy estimate is SHA_DIGEST_LENGTH */ | ||
279 | if (entropy_estimate > SHA_DIGEST_LENGTH) | ||
280 | entropy_estimate = SHA_DIGEST_LENGTH; | ||
281 | |||
282 | /* Scale back estimates for subsequent passes through list */ | ||
283 | entropy_estimate /= SCALE_PER_RUN * (i + 1.0); | ||
284 | |||
285 | /* Stir it in */ | ||
286 | RAND_add(hash, sizeof(hash), entropy_estimate); | ||
287 | |||
288 | debug3("Got %0.2f bytes of entropy from '%s'", entropy_estimate, | ||
289 | entropy_sources[c].cmdstring); | ||
290 | |||
291 | total_entropy_estimate += entropy_estimate; | ||
292 | |||
293 | /* Execution times should be a little unpredictable */ | ||
294 | total_entropy_estimate += stir_gettimeofday(0.05); | ||
295 | total_entropy_estimate += stir_clock(0.05); | ||
296 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 0.1); | ||
297 | total_entropy_estimate += stir_rusage(RUSAGE_CHILDREN, 0.1); | ||
298 | } else { | ||
299 | debug2("Command '%s' disabled (badness %d)", | ||
300 | entropy_sources[c].cmdstring, entropy_sources[c].badness); | ||
301 | |||
302 | if (entropy_sources[c].badness > 0) | ||
303 | entropy_sources[c].badness--; | ||
304 | } | ||
305 | |||
306 | c++; | ||
307 | } | ||
308 | } | ||
309 | |||
310 | return(total_entropy_estimate); | ||
311 | } | ||
312 | |||
313 | double | ||
314 | stir_gettimeofday(double entropy_estimate) | ||
315 | { | ||
316 | struct timeval tv; | ||
317 | |||
318 | if (gettimeofday(&tv, NULL) == -1) | ||
319 | fatal("Couldn't gettimeofday: %s", strerror(errno)); | ||
320 | |||
321 | RAND_add(&tv, sizeof(tv), entropy_estimate); | ||
322 | |||
323 | return(entropy_estimate); | ||
324 | } | ||
325 | |||
326 | double | ||
327 | stir_clock(double entropy_estimate) | ||
328 | { | ||
329 | #ifdef HAVE_CLOCK | ||
330 | clock_t c; | ||
331 | |||
332 | c = clock(); | ||
333 | RAND_add(&c, sizeof(c), entropy_estimate); | ||
334 | |||
335 | return(entropy_estimate); | ||
336 | #else /* _HAVE_CLOCK */ | ||
337 | return(0); | ||
338 | #endif /* _HAVE_CLOCK */ | ||
339 | } | ||
340 | |||
341 | double | ||
342 | stir_rusage(int who, double entropy_estimate) | ||
343 | { | ||
344 | #ifdef HAVE_GETRUSAGE | ||
345 | struct rusage ru; | ||
346 | |||
347 | if (getrusage(who, &ru) == -1) | ||
348 | return(0); | ||
349 | |||
350 | RAND_add(&ru, sizeof(ru), entropy_estimate); | ||
351 | |||
352 | return(entropy_estimate); | ||
353 | #else /* _HAVE_GETRUSAGE */ | ||
354 | return(0); | ||
355 | #endif /* _HAVE_GETRUSAGE */ | ||
356 | } | ||
357 | |||
358 | |||
359 | static int | ||
360 | _get_timeval_msec_difference(struct timeval *t1, struct timeval *t2) { | ||
361 | int secdiff, usecdiff; | ||
362 | |||
363 | secdiff = t2->tv_sec - t1->tv_sec; | ||
364 | usecdiff = (secdiff*1000000) + (t2->tv_usec - t1->tv_usec); | ||
365 | return (int)(usecdiff / 1000); | ||
366 | } | ||
367 | |||
368 | double | ||
369 | hash_output_from_command(entropy_source_t *src, char *hash) | ||
370 | { | ||
371 | static int devnull = -1; | ||
372 | int p[2]; | ||
373 | fd_set rdset; | ||
374 | int cmd_eof = 0, error_abort = 0; | ||
375 | struct timeval tv_start, tv_current; | ||
376 | int msec_elapsed = 0; | ||
377 | pid_t pid; | ||
378 | int status; | ||
379 | char buf[16384]; | ||
380 | int bytes_read; | ||
381 | int total_bytes_read; | ||
382 | SHA_CTX sha; | ||
383 | |||
384 | debug3("Reading output from \'%s\'", src->cmdstring); | ||
385 | |||
386 | if (devnull == -1) { | ||
387 | devnull = open("/dev/null", O_RDWR); | ||
388 | if (devnull == -1) | ||
389 | fatal("Couldn't open /dev/null: %s", strerror(errno)); | ||
390 | } | ||
391 | |||
392 | if (pipe(p) == -1) | ||
393 | fatal("Couldn't open pipe: %s", strerror(errno)); | ||
394 | |||
395 | (void)gettimeofday(&tv_start, NULL); /* record start time */ | ||
396 | |||
397 | switch (pid = fork()) { | ||
398 | case -1: /* Error */ | ||
399 | close(p[0]); | ||
400 | close(p[1]); | ||
401 | fatal("Couldn't fork: %s", strerror(errno)); | ||
402 | /* NOTREACHED */ | ||
403 | case 0: /* Child */ | ||
404 | dup2(devnull, STDIN_FILENO); | ||
405 | dup2(p[1], STDOUT_FILENO); | ||
406 | dup2(p[1], STDERR_FILENO); | ||
407 | close(p[0]); | ||
408 | close(p[1]); | ||
409 | close(devnull); | ||
410 | |||
411 | execv(src->path, (char**)(src->args)); | ||
412 | debug("(child) Couldn't exec '%s': %s", src->cmdstring, | ||
413 | strerror(errno)); | ||
414 | _exit(-1); | ||
415 | default: /* Parent */ | ||
416 | break; | ||
417 | } | ||
418 | |||
419 | RAND_add(&pid, sizeof(&pid), 0.0); | ||
420 | |||
421 | close(p[1]); | ||
422 | |||
423 | /* Hash output from child */ | ||
424 | SHA1_Init(&sha); | ||
425 | total_bytes_read = 0; | ||
426 | |||
427 | while (!error_abort && !cmd_eof) { | ||
428 | int ret; | ||
429 | struct timeval tv; | ||
430 | int msec_remaining; | ||
431 | |||
432 | (void) gettimeofday(&tv_current, 0); | ||
433 | msec_elapsed = _get_timeval_msec_difference(&tv_start, &tv_current); | ||
434 | if (msec_elapsed >= entropy_timeout_current) { | ||
435 | error_abort=1; | ||
436 | continue; | ||
437 | } | ||
438 | msec_remaining = entropy_timeout_current - msec_elapsed; | ||
439 | |||
440 | FD_ZERO(&rdset); | ||
441 | FD_SET(p[0], &rdset); | ||
442 | tv.tv_sec = msec_remaining / 1000; | ||
443 | tv.tv_usec = (msec_remaining % 1000) * 1000; | ||
444 | |||
445 | ret = select(p[0]+1, &rdset, NULL, NULL, &tv); | ||
446 | |||
447 | RAND_add(&tv, sizeof(tv), 0.0); | ||
448 | |||
449 | switch (ret) { | ||
450 | case 0: | ||
451 | /* timer expired */ | ||
452 | error_abort = 1; | ||
453 | break; | ||
454 | case 1: | ||
455 | /* command input */ | ||
456 | do { | ||
457 | bytes_read = read(p[0], buf, sizeof(buf)); | ||
458 | } while (bytes_read == -1 && errno == EINTR); | ||
459 | RAND_add(&bytes_read, sizeof(&bytes_read), 0.0); | ||
460 | if (bytes_read == -1) { | ||
461 | error_abort = 1; | ||
462 | break; | ||
463 | } else if (bytes_read) { | ||
464 | SHA1_Update(&sha, buf, bytes_read); | ||
465 | total_bytes_read += bytes_read; | ||
466 | } else { | ||
467 | cmd_eof = 1; | ||
468 | } | ||
469 | break; | ||
470 | case -1: | ||
471 | default: | ||
472 | /* error */ | ||
473 | debug("Command '%s': select() failed: %s", src->cmdstring, | ||
474 | strerror(errno)); | ||
475 | error_abort = 1; | ||
476 | break; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | SHA1_Final(hash, &sha); | ||
481 | |||
482 | close(p[0]); | ||
483 | |||
484 | debug3("Time elapsed: %d msec", msec_elapsed); | ||
485 | |||
486 | if (waitpid(pid, &status, 0) == -1) { | ||
487 | error("Couldn't wait for child '%s' completion: %s", src->cmdstring, | ||
488 | strerror(errno)); | ||
489 | return(0.0); | ||
490 | } | ||
491 | |||
492 | RAND_add(&status, sizeof(&status), 0.0); | ||
493 | |||
494 | if (error_abort) { | ||
495 | /* closing p[0] on timeout causes the entropy command to | ||
496 | * SIGPIPE. Take whatever output we got, and mark this command | ||
497 | * as slow */ | ||
498 | debug2("Command '%s' timed out", src->cmdstring); | ||
499 | src->sticky_badness *= 2; | ||
500 | src->badness = src->sticky_badness; | ||
501 | return(total_bytes_read); | ||
502 | } | ||
503 | |||
504 | if (WIFEXITED(status)) { | ||
505 | if (WEXITSTATUS(status)==0) { | ||
506 | return(total_bytes_read); | ||
507 | } else { | ||
508 | debug2("Command '%s' exit status was %d", src->cmdstring, | ||
509 | WEXITSTATUS(status)); | ||
510 | src->badness = src->sticky_badness = 128; | ||
511 | return (0.0); | ||
512 | } | ||
513 | } else if (WIFSIGNALED(status)) { | ||
514 | debug2("Command '%s' returned on uncaught signal %d !", src->cmdstring, | ||
515 | status); | ||
516 | src->badness = src->sticky_badness = 128; | ||
517 | return(0.0); | ||
518 | } else | ||
519 | return(0.0); | ||
520 | } | ||
521 | |||
522 | /* | ||
523 | * prng seedfile functions | ||
524 | */ | ||
525 | int | ||
526 | prng_check_seedfile(char *filename) { | ||
527 | |||
528 | struct stat st; | ||
529 | |||
530 | /* FIXME raceable: eg replace seed between this stat and subsequent open */ | ||
531 | /* Not such a problem because we don't trust the seed file anyway */ | ||
532 | if (lstat(filename, &st) == -1) { | ||
533 | /* Give up on hard errors */ | ||
534 | if (errno != ENOENT) | ||
535 | debug("WARNING: Couldn't stat random seed file \"%s\": %s", | ||
536 | filename, strerror(errno)); | ||
537 | |||
538 | return(0); | ||
539 | } | ||
540 | |||
541 | /* regular file? */ | ||
542 | if (!S_ISREG(st.st_mode)) | ||
543 | fatal("PRNG seedfile %.100s is not a regular file", filename); | ||
544 | |||
545 | /* mode 0600, owned by root or the current user? */ | ||
546 | if (((st.st_mode & 0177) != 0) || !(st.st_uid == getuid())) { | ||
547 | debug("WARNING: PRNG seedfile %.100s must be mode 0600, owned by uid %d", | ||
548 | filename, getuid()); | ||
549 | return(0); | ||
550 | } | ||
551 | |||
552 | return(1); | ||
553 | } | ||
554 | |||
555 | void | ||
556 | prng_write_seedfile(void) { | ||
557 | int fd; | ||
558 | char seed[1024]; | ||
559 | char filename[1024]; | ||
560 | struct passwd *pw; | ||
561 | |||
562 | pw = getpwuid(getuid()); | ||
563 | if (pw == NULL) | ||
564 | fatal("Couldn't get password entry for current user (%i): %s", | ||
565 | getuid(), strerror(errno)); | ||
566 | |||
567 | /* Try to ensure that the parent directory is there */ | ||
568 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
569 | _PATH_SSH_USER_DIR); | ||
570 | mkdir(filename, 0700); | ||
571 | |||
572 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
573 | SSH_PRNG_SEED_FILE); | ||
574 | |||
575 | debug("writing PRNG seed to file %.100s", filename); | ||
576 | |||
577 | RAND_bytes(seed, sizeof(seed)); | ||
578 | |||
579 | /* Don't care if the seed doesn't exist */ | ||
580 | prng_check_seedfile(filename); | ||
581 | |||
582 | if ((fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600)) == -1) { | ||
583 | debug("WARNING: couldn't access PRNG seedfile %.100s (%.100s)", | ||
584 | filename, strerror(errno)); | ||
585 | } else { | ||
586 | if (atomicio(write, fd, &seed, sizeof(seed)) != sizeof(seed)) | ||
587 | fatal("problem writing PRNG seedfile %.100s (%.100s)", filename, | ||
588 | strerror(errno)); | ||
589 | close(fd); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | void | ||
594 | prng_read_seedfile(void) { | ||
595 | int fd; | ||
596 | char seed[1024]; | ||
597 | char filename[1024]; | ||
598 | struct passwd *pw; | ||
599 | |||
600 | pw = getpwuid(getuid()); | ||
601 | if (pw == NULL) | ||
602 | fatal("Couldn't get password entry for current user (%i): %s", | ||
603 | getuid(), strerror(errno)); | ||
604 | |||
605 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
606 | SSH_PRNG_SEED_FILE); | ||
607 | |||
608 | debug("loading PRNG seed from file %.100s", filename); | ||
609 | |||
610 | if (!prng_check_seedfile(filename)) { | ||
611 | verbose("Random seed file not found or not valid, ignoring."); | ||
612 | return; | ||
613 | } | ||
614 | |||
615 | /* open the file and read in the seed */ | ||
616 | fd = open(filename, O_RDONLY); | ||
617 | if (fd == -1) | ||
618 | fatal("could not open PRNG seedfile %.100s (%.100s)", filename, | ||
619 | strerror(errno)); | ||
620 | |||
621 | if (atomicio(read, fd, &seed, sizeof(seed)) != sizeof(seed)) { | ||
622 | verbose("invalid or short read from PRNG seedfile %.100s - ignoring", | ||
623 | filename); | ||
624 | memset(seed, '\0', sizeof(seed)); | ||
625 | } | ||
626 | close(fd); | ||
627 | |||
628 | /* stir in the seed, with estimated entropy zero */ | ||
629 | RAND_add(&seed, sizeof(seed), 0.0); | ||
630 | } | ||
631 | |||
632 | |||
633 | /* | ||
634 | * entropy command initialisation functions | ||
635 | */ | ||
636 | int | ||
637 | prng_read_commands(char *cmdfilename) | ||
638 | { | ||
639 | FILE *f; | ||
640 | char *cp; | ||
641 | char line[1024]; | ||
642 | char cmd[1024]; | ||
643 | char path[256]; | ||
644 | int linenum; | ||
645 | int num_cmds = 64; | ||
646 | int cur_cmd = 0; | ||
647 | double est; | ||
648 | entropy_source_t *entcmd; | ||
649 | |||
650 | f = fopen(cmdfilename, "r"); | ||
651 | if (!f) { | ||
652 | fatal("couldn't read entropy commands file %.100s: %.100s", | ||
653 | cmdfilename, strerror(errno)); | ||
654 | } | ||
655 | |||
656 | entcmd = (entropy_source_t *)xmalloc(num_cmds * sizeof(entropy_source_t)); | ||
657 | memset(entcmd, '\0', num_cmds * sizeof(entropy_source_t)); | ||
658 | |||
659 | /* Read in file */ | ||
660 | linenum = 0; | ||
661 | while (fgets(line, sizeof(line), f)) { | ||
662 | int arg; | ||
663 | char *argv; | ||
664 | |||
665 | linenum++; | ||
666 | |||
667 | /* skip leading whitespace, test for blank line or comment */ | ||
668 | cp = line + strspn(line, WHITESPACE); | ||
669 | if ((*cp == 0) || (*cp == '#')) | ||
670 | continue; /* done with this line */ | ||
671 | |||
672 | /* First non-whitespace char should be double quote delimiting */ | ||
673 | /* commandline */ | ||
674 | if (*cp != '"') { | ||
675 | error("bad entropy command, %.100s line %d", cmdfilename, | ||
676 | linenum); | ||
677 | continue; | ||
678 | } | ||
679 | |||
680 | /* first token, command args (incl. argv[0]) in double quotes */ | ||
681 | cp = strtok(cp, "\""); | ||
682 | if (cp == NULL) { | ||
683 | error("missing or bad command string, %.100s line %d -- ignored", | ||
684 | cmdfilename, linenum); | ||
685 | continue; | ||
686 | } | ||
687 | strlcpy(cmd, cp, sizeof(cmd)); | ||
688 | |||
689 | /* second token, full command path */ | ||
690 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
691 | error("missing command path, %.100s line %d -- ignored", | ||
692 | cmdfilename, linenum); | ||
693 | continue; | ||
694 | } | ||
695 | |||
696 | /* did configure mark this as dead? */ | ||
697 | if (strncmp("undef", cp, 5) == 0) | ||
698 | continue; | ||
699 | |||
700 | strlcpy(path, cp, sizeof(path)); | ||
701 | |||
702 | /* third token, entropy rate estimate for this command */ | ||
703 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
704 | error("missing entropy estimate, %.100s line %d -- ignored", | ||
705 | cmdfilename, linenum); | ||
706 | continue; | ||
707 | } | ||
708 | est = strtod(cp, &argv); | ||
709 | |||
710 | /* end of line */ | ||
711 | if ((cp = strtok(NULL, WHITESPACE)) != NULL) { | ||
712 | error("garbage at end of line %d in %.100s -- ignored", linenum, | ||
713 | cmdfilename); | ||
714 | continue; | ||
715 | } | ||
716 | |||
717 | /* save the command for debug messages */ | ||
718 | entcmd[cur_cmd].cmdstring = xstrdup(cmd); | ||
719 | |||
720 | /* split the command args */ | ||
721 | cp = strtok(cmd, WHITESPACE); | ||
722 | arg = 0; | ||
723 | argv = NULL; | ||
724 | do { | ||
725 | char *s = (char*)xmalloc(strlen(cp) + 1); | ||
726 | strncpy(s, cp, strlen(cp) + 1); | ||
727 | entcmd[cur_cmd].args[arg] = s; | ||
728 | arg++; | ||
729 | } while ((arg < 5) && (cp = strtok(NULL, WHITESPACE))); | ||
730 | |||
731 | if (strtok(NULL, WHITESPACE)) | ||
732 | error("ignored extra command elements (max 5), %.100s line %d", | ||
733 | cmdfilename, linenum); | ||
734 | |||
735 | /* Copy the command path and rate estimate */ | ||
736 | entcmd[cur_cmd].path = xstrdup(path); | ||
737 | entcmd[cur_cmd].rate = est; | ||
738 | |||
739 | /* Initialise other values */ | ||
740 | entcmd[cur_cmd].sticky_badness = 1; | ||
741 | |||
742 | cur_cmd++; | ||
743 | |||
744 | /* If we've filled the array, reallocate it twice the size */ | ||
745 | /* Do this now because even if this we're on the last command, | ||
746 | we need another slot to mark the last entry */ | ||
747 | if (cur_cmd == num_cmds) { | ||
748 | num_cmds *= 2; | ||
749 | entcmd = xrealloc(entcmd, num_cmds * sizeof(entropy_source_t)); | ||
750 | } | ||
751 | } | ||
752 | |||
753 | /* zero the last entry */ | ||
754 | memset(&entcmd[cur_cmd], '\0', sizeof(entropy_source_t)); | ||
755 | |||
756 | /* trim to size */ | ||
757 | entropy_sources = xrealloc(entcmd, (cur_cmd+1) * sizeof(entropy_source_t)); | ||
758 | |||
759 | debug("Loaded %d entropy commands from %.100s", cur_cmd, cmdfilename); | ||
760 | |||
761 | return (cur_cmd >= MIN_ENTROPY_SOURCES); | ||
762 | } | ||
763 | |||
764 | static void | ||
765 | seed_openssl_rng(void) | ||
766 | { | ||
767 | /* Read in collection commands */ | ||
768 | if (!prng_read_commands(SSH_PRNG_COMMAND_FILE)) | ||
769 | fatal("PRNG initialisation failed -- exiting."); | ||
770 | |||
771 | prng_read_seedfile(); | ||
772 | |||
773 | debug("Seeded RNG with %i bytes from programs", | ||
774 | (int)stir_from_programs()); | ||
775 | debug("Seeded RNG with %i bytes from system calls", | ||
776 | (int)stir_from_system()); | ||
777 | |||
778 | prng_write_seedfile(); | ||
779 | } | ||
780 | |||
781 | #endif /* USE_PRNGD */ | ||
782 | |||
783 | int | ||
784 | main(int argc, char **argv) | ||
785 | { | ||
786 | unsigned char buf[48]; | ||
787 | int ret; | ||
788 | |||
789 | /* XXX: need some debugging mode */ | ||
790 | log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); | ||
791 | |||
792 | seed_openssl_rng(); | ||
793 | |||
794 | if (!RAND_status()) | ||
795 | fatal("Not enough entropy in RNG"); | ||
796 | |||
797 | RAND_bytes(buf, sizeof(buf)); | ||
798 | |||
799 | ret = atomicio(write, STDOUT_FILENO, buf, sizeof(buf)); | ||
800 | |||
801 | memset(buf, '\0', sizeof(buf)); | ||
802 | |||
803 | return ret == sizeof(buf) ? 0 : 1; | ||
804 | } | ||
805 | |||