--- /dev/null
+/*
+ * Copyright (c) 2006, Matthew Mondor
+ * ALL RIGHTS RESERVED.
+ */
+
+/*
+ * Strategy:
+ *
+ * 1) First obtain wd* or sd* devices using sysctl(3) which could be used to
+ * produce entropy. Error and exit if no such devices can be used.
+ * 2) Report our PID and detach, allowing SIGTERM to kill us.
+ * 3) In an endless loop, read various arbitrary blocks of random lenghts and
+ * at random positions from random disks. We take care to toggle the
+ * reading direction often enough to give the heads an opportunity to seek
+ * in both directions.
+ * 4) Upon receiving a SIGTERM signal, close the disk devices and exit.
+ *
+ * To obtain rather decent random values, we use the 4.2BSD random(3) PRNG
+ * but seed it using /dev/urandom. This prevents sucking up too much entropy
+ * from rnd(4), which we're designed to fill rather than waste, afterall.
+ * We periodically reseed the PRNG with /dev/urandom during the main loop as
+ * well.
+ *
+ * XXX Consider using arc4random(3) instead. However, possible problems could
+ * occur using it: it could void rnd(4) entropy, especially that at least
+ * 1024 bytes of the arcfour keystream should be discarded to be random
+ * enough.
+ *
+ * XXX Possibly add an option to tell the daemon to automatially exit after a
+ * certain number of seconds or minutes elapsed. We would use setitimer(2)
+ * to receive a SIGALRM, acting the same as for SIGTERM upon its reception.
+ */
+
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+
+/*
+ * Minimum block size to read (could be disk dependent but it doesn't matter
+ * much for this task.
+ */
+#define BLOCK_SIZE 8192
+/*
+ * Maximum number of BLOCK_SIZE blocks to read in a single read(2)
+ */
+#define BLOCK_MULTIPLE 16
+
+typedef struct drive_entry {
+ SLIST_ENTRY(drive_entry) chain;
+ char *name;
+ off_t max, lastpos;
+ int lastdir, fd;
+} disk_t;
+
+SLIST_HEAD(slisthead, drive_entry);
+
+#define BALIGN_CEIL(v, s) ((((size_t)(v)) + (s) - 1) / (s) * (s))
+
+
+
+int main(int, char **);
+
+static int prng_init(void);
+static void prng_reseed(void);
+static void prng_close(void);
+static void signal_handler(int);
+static int detach(const char *);
+static void disk_open(const char *);
+static int disks_open(void);
+static void disks_close(void);
+static void *buffer_alloc(size_t);
+static void buffer_free(void *, size_t);
+
+
+
+static char *pidfile = "/var/run/entropy_disk.pid";
+static int maxdisks = 4;
+
+static struct slisthead disks_list = SLIST_HEAD_INITIALIZER(drive_entry);
+static int ndisks = 0;
+static disk_t **disks_array = NULL;
+
+static int urandom = -1;
+static int reseed = 0;
+
+static size_t pagesize = 0;
+
+static int run = 1;
+
+
+
+int
+main(int argc, char **argv)
+{
+ char c;
+ void *readbuf;
+ int ret = EXIT_FAILURE;
+
+ setprogname(argv[0]);
+
+ /* Parse commad line arguments */
+ while ((c = getopt(argc, argv, "p:n:?")) != -1) {
+ switch (c) {
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'n':
+ maxdisks = strtol(optarg, NULL, 10);
+ if (maxdisks < 1 || maxdisks > 100) {
+ (void) fprintf(stderr,
+ "<maxdisks> must be between 1 and 100\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case '?':
+ /* FALLTHROUGH */
+ default:
+ (void) fprintf(stderr,
+ "Usage: %s [-p <pidfile>] [-n <maxdisks>]\n",
+ getprogname());
+ return EXIT_FAILURE;
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ /*
+ * Initialization
+ */
+ if (prng_init() != 0)
+ return EXIT_FAILURE;
+
+ if (disks_open() != 0)
+ return EXIT_FAILURE;
+
+ if ((readbuf = buffer_alloc(BLOCK_SIZE * BLOCK_MULTIPLE)) == NULL)
+ goto end;
+ if (detach(pidfile) != 0)
+ goto end;
+
+ /* Main loop */
+ while (run) {
+ disk_t *e;
+ int blocks;
+
+ /* Choose a disk */
+ e = disks_array[random() % ndisks];
+
+ /* Choose a block size */
+ blocks = random() % BLOCK_MULTIPLE;
+
+ /*
+ * Choose a position relative to a previous one and which can
+ * withstand the block size
+ */
+ /* XXX */
+
+ /* Finally read block and clear buffer */
+
+ /* Sleep for a slight random delay */
+ (void) usleep(random() % 1000);
+
+ /* Reseed PRNG out of /dev/urandom periodically */
+ prng_reseed();
+ }
+
+ ret = EXIT_SUCCESS;
+ /* FALLTHROUGH */
+
+end:
+ /* Cleanup */
+ if (readbuf != NULL)
+ buffer_free(readbuf, BLOCK_SIZE * BLOCK_MULTIPLE);
+ (void) unlink(pidfile);
+ disks_close();
+ prng_close();
+
+ return ret;
+}
+
+/*
+ * Seed 4.2BSD random(3) using rnd(4)'s /dev/urandom
+ */
+static int
+prng_init(void)
+{
+ unsigned long seed;
+
+ if ((urandom = open("/dev/urandom", O_RDONLY)) == -1) {
+ (void) fprintf(stderr,
+ "open(/dev/urandom): %s\n", strerror(errno));
+ goto err;
+ }
+ if (read(urandom, &seed, sizeof(seed)) != sizeof(seed)) {
+ (void) fprintf(stderr,
+ "read(/dev/urandom): %s\n", strerror(errno));
+ goto err;
+ }
+
+ srandom(seed);
+ reseed = random() % 20;
+
+ return 0;
+
+err:
+ if (urandom != -1) {
+ (void) close(urandom);
+ urandom = -1;
+ }
+
+ return -1;
+}
+
+static void
+prng_reseed(void)
+{
+ unsigned long seed;
+
+ if (--reseed < 1) {
+ if (read(urandom, &seed, sizeof(seed)) != sizeof(seed)) {
+ (void) fprintf(stderr,
+ "read(/dev/urandom): %s\n", strerror(errno));
+ }
+ srandom(seed);
+ reseed = random() % 20;
+ }
+}
+
+static void
+prng_close(void)
+{
+
+ if (urandom != -1) {
+ (void) close(urandom);
+ urandom = -1;
+ }
+}
+
+/*
+ * Called upon reception of SIGTERM
+ */
+static void
+signal_handler(int sig)
+{
+
+ switch (sig) {
+ case SIGTERM: /* FALLTHROUGH */
+ default:
+ run = 0;
+ }
+}
+
+/*
+ * Become a background daemon and write our PID file.
+ * Returns 0 on success or -1 on error.
+ */
+static int
+detach(const char *pidfile)
+{
+ pid_t pid;
+ int fd;
+ struct sigaction act;
+
+ if ((pid = fork()) == -1)
+ return -1;
+ if (pid != 0)
+ exit(EXIT_SUCCESS);
+
+ /* Create PID file */
+ if ((fd = open(pidfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
+ char str[16];
+
+ (void) snprintf(str, 15, "%d\n", getpid());
+ if (write(fd, str, strlen(str)) == -1 || close(fd) == -1) {
+ (void) fprintf(stderr,
+ "Error writing PID file [%s]: %s\n",
+ pidfile, strerror(errno));
+ return -1;
+ }
+ } else {
+ (void) fprintf(stderr,
+ "Error creating PID file [%s]: %s\n",
+ pidfile, strerror(errno));
+ return -1;
+ }
+
+ /* Be paranoid, redirect our stdio file descriptors to null(4) */
+ (void) setsid();
+ (void) chdir("/");
+ if ((fd = open("/dev/null", O_RDWR)) != -1) {
+ (void) dup2(fd, STDIN_FILENO);
+ (void) dup2(fd, STDOUT_FILENO);
+ /* Keep stderr */
+ if (fd > STDERR_FILENO)
+ (void) close(fd);
+ }
+
+ /* Set our SIGTERM handler and ignore some signals */
+ act.sa_handler = signal_handler;
+ act.sa_flags = 0;
+ (void) sigemptyset(&act.sa_mask);
+ (void) sigaction(SIGTERM, &act, NULL);
+ act.sa_handler = SIG_IGN;
+ (void) sigaction(SIGTTOU, &act, NULL);
+ (void) sigaction(SIGTTIN, &act, NULL);
+ (void) sigaction(SIGTSTP, &act, NULL);
+
+ return 0;
+}
+
+/*
+ * Attempts to open specified disk device and on success adds a new disk_t
+ * entry to the disks_list. Logs any errors to stderr.
+ */
+static void
+disk_open(const char *dname)
+{
+ disk_t *e;
+ char devname[16];
+
+ if ((e = malloc(sizeof(disk_t))) == NULL) {
+ (void) fprintf(stderr,
+ "malloc(%d): %s\n", sizeof(disk_t), strerror(errno));
+ goto err;
+ }
+ if ((e->name = strdup(dname)) == NULL) {
+ (void) fprintf(stderr,
+ "strdup(%d): %s\n", strlen(dname), strerror(errno));
+ goto err;
+ }
+
+ (void) snprintf(devname, 16, "/dev/r%sd", dname);
+ if ((e->fd = open(devname, O_RDONLY)) == -1) {
+ (void) fprintf(stderr,
+ "open(%s): %s\n", devname, strerror(errno));
+ goto err;
+ }
+
+ /*
+ * Although we could use an ioctl(2) to read the label of the device,
+ * using lseek(2) is simpler. If it generates head movement, it's all
+ * for the better.
+ */
+ if (lseek(e->fd, 0, SEEK_END) == -1) {
+ (void) fprintf(stderr,
+ "lseek(%s): %s\n", devname, strerror(errno));
+ goto err;
+ }
+ if ((e->max = lseek(e->fd, 0, SEEK_SET)) == -1) {
+ (void) fprintf(stderr,
+ "lseek(%s): %s\n", devname, strerror(errno));
+ goto err;
+ }
+ e->lastpos = -1;
+ e->lastdir = 0;
+
+ SLIST_INSERT_HEAD(&disks_list, e, chain);
+ ndisks++;
+ return;
+
+err:
+ if (e != NULL) {
+ if (e->name != NULL)
+ free(e->name);
+ if (e->fd != -1)
+ (void) close(e->fd);
+ free(e);
+ }
+}
+
+/*
+ * Queries sysctl(3) for wd* and sd* drive names and opens the devices.
+ * Returns 0 on success or -1 on failure.
+ */
+static int
+disks_open(void)
+{
+ int mib[] = { CTL_HW, HW_DISKNAMES };
+ char buf[1024], *cptr, *sptr;
+ size_t size = sizeof(buf);
+
+ /* Query local disks */
+ if (sysctl(mib, 2, buf, &size, NULL, 0) != 0) {
+ (void) fprintf(stderr,
+ "Error querying sysctl(3) for disk names: %s\n",
+ strerror(errno));
+ goto err;
+ }
+
+ /* Open for each sd(4) or wd(4) disk reported, up to maxdisks */
+ /* XXX We actually could use strsep(3) easily here as well */
+ for (cptr = buf, ndisks = 0; ndisks < maxdisks; ) {
+ for (; *cptr != '\0' && *cptr == ' '; cptr++) ;
+ if (*cptr == '\0')
+ break;
+ sptr = cptr;
+ for (; *cptr != '\0' && *cptr != ' '; cptr++) ;
+ if (*cptr == '\0')
+ break;
+ *cptr++ = '\0';
+ if ((sptr[0] == 's' || sptr[0] == 'w') &&
+ sptr[1] == 'd')
+ disk_open(sptr);
+ }
+
+ if (ndisks == 0) {
+ (void) fprintf(stderr,
+ "No sd(4) or wd(4) disks to generate entropy from\n");
+ goto err;
+ }
+
+ /* Initialize fast index array */
+ if ((disks_array = malloc(sizeof(disk_t *) * ndisks)) == NULL) {
+ (void) fprintf(stderr,
+ "malloc(%d): %s\n", sizeof(disk_t *) * ndisks,
+ strerror(errno));
+ goto err;
+ }
+ {
+ int i = 0;
+ disk_t *e;
+
+ SLIST_FOREACH(e, &disks_list, chain)
+ disks_array[i++] = e;
+ }
+
+ return 0;
+
+err:
+ disks_close();
+
+ return -1;
+}
+
+/*
+ * Closes any open drive devices if any.
+ */
+static void
+disks_close(void)
+{
+ disk_t *e;
+
+ if (disks_array != NULL)
+ free(disks_array);
+
+ while (!SLIST_EMPTY(&disks_list)) {
+ e = SLIST_FIRST(&disks_list);
+ SLIST_REMOVE_HEAD(&disks_list, chain);
+ if (e->fd != -1)
+ (void) close(e->fd);
+ if (e->name != NULL)
+ free(e->name);
+ free(e);
+ }
+
+ ndisks = 0;
+}
+
+/*
+ * Allocate a wired memory buffer.
+ */
+static void *
+buffer_alloc(size_t size)
+{
+ void *buf = NULL;
+
+ if (pagesize == 0) {
+ if ((pagesize = (size_t)sysconf(_SC_PAGESIZE)) == -1) {
+ perror("sysconf(_SC_PAGESIZE)");
+ goto err;
+ }
+ }
+
+ size = (size_t)BALIGN_CEIL(size, pagesize);
+
+ if ((buf = mmap(NULL, size, PROT_WRITE, MAP_ANON, -1, 0)) ==
+ MAP_FAILED) {
+ (void) fprintf(stderr,
+ "mmap(%d): %s\n", size, strerror(errno));
+ buf = NULL;
+ goto err;
+ }
+ if (mlock(buf, size) == -1) {
+ perror("mlock()");
+ goto err;
+ }
+ if (madvise(buf, size, MADV_SEQUENTIAL) == -1) {
+ perror("madvise()");
+ goto err;
+ }
+
+ return buf;
+
+err:
+ if (buf != NULL) {
+ (void) munlock(buf, size);
+ (void) munmap(buf, size);
+ }
+
+ return NULL;
+}
+
+/*
+ * Free a previously allocated wired buffer.
+ */
+static void
+buffer_free(void *buf, size_t size)
+{
+
+ assert(pagesize != 0 && buf != NULL);
+
+ size = (size_t)BALIGN_CEIL(size, pagesize);
+
+ (void) memset(buf, 0, size);
+ (void) munlock(buf, size);
+ (void) munmap(buf, size);
+}