/*
 * $Id: atom_age.c,v 1.56 1999/07/11 03:40:51 psi4 Exp $
 * (c) Psi-4 1999
 */

#include <sys/ioctl.h>
#include <sys/rnd.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <unistd.h>

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

int main __P((int, char *[]));
static void usage __P((void));
void log_it __P((int, int, const char *,...));

/*
 * If there is a temporary buffer used by the kernel to copy the data into
 * the random device (e.g., RND_TEMP_BUFFER_SIZE in /sys/dev/rnd.c), then
 * this should probably be some multiple of that.  Otherwise, make it some
 * reasonably large power of two.
 */
#define BUFFER_SIZE 65536

static const char *ProgramName;

/*
 * Read data from a specified serial port and insert it into the kernel's
 * /dev/random entropy pool.
 */
int
main(argc, argv)
	int     argc;
	char   *argv[];
{
	struct termios t;
	ssize_t bytes_read;
	ssize_t bytes_written;
	ssize_t i;
	u_int32_t injected_entropy;
	int     atom_age_fd;
	int     ch;
	int     debug;
	int     dev_random_fd;
	char    buf[BUFFER_SIZE + 1];
	char   *device;

	atom_age_fd = -1;
	debug = 0;
	dev_random_fd = -1;

	ProgramName = argv[0];

	while ((ch = getopt(argc, argv, "d")) != -1)
		switch (ch) {
		case 'd':
			debug = 1;
			break;
		case '?':
		default:
			usage();
			/* NOTREACHED */
		}
	argc -= optind;
	argv += optind;

	if (argc != 1)
		usage();

	device = argv[0];

	if (debug) {
		if (setlinebuf(stdout) != 0)
			err(1, "setting line buffering on stdout");
	} else {
		if (daemon(0, 0) != 0)
			err(2, "daemonizing");
		(void) openlog(ProgramName, LOG_CONS, LOG_LOCAL3);
	}

	if ((atom_age_fd = open(device, O_RDONLY)) == -1) {
		log_it(debug, LOG_ERR, "can't open %s for read: %s\n", device,
		    strerror(errno));
		exit(3);
	}
	(void) cfmakeraw(&t);
	t.c_cflag &= ~PARODD;
	t.c_cflag |= CREAD;

	if (cfsetspeed(&t, B19200)) {
		log_it(debug, LOG_ERR, "can't set speed on %s: %s\n", device,
		    strerror(errno));
		exit(4);
	}
	if (tcsetattr(atom_age_fd, TCSANOW, &t) == -1) {
		log_it(debug, LOG_ERR, "can't set attrs on %s: %s\n", device,
		    strerror(errno));
		exit(5);
	}
	if ((dev_random_fd = open("/dev/random", O_WRONLY)) == -1)
		log_it(debug, LOG_WARNING,
		    "can't open /dev/random for write: %s\n", strerror(errno));

	for (;;) {
		bytes_read = read(atom_age_fd, &buf[0], BUFFER_SIZE);
		if (bytes_read < 0) {
			log_it(debug, LOG_ERR, "can't read from %s: %s\n",
			    device, strerror(errno));
			exit(7);
		}
		if (bytes_read > 0) {
			buf[bytes_read] = '\0';
			log_it(debug, LOG_DEBUG, "data: %d %s\n", bytes_read,
			    buf);
			for (i = 0; (i < bytes_read) && isxdigit(buf[i]); ++i);
			if (i < bytes_read) {
				log_it(debug, LOG_WARNING,
				    "device produced non-hex digit: %#x\n",
				    buf[i]);
				continue;
			}
			if (dev_random_fd != -1) {
				if ((bytes_written = write(dev_random_fd, buf,
					    bytes_read)) == -1) {
					log_it(debug, LOG_ERR,
					    "can't write to /dev/random: %s\n",
					    strerror(errno));
					exit(8);
				}
				log_it(debug, LOG_DEBUG,
				    "wrote %d bytes to /dev/random\n",
				    bytes_written);
				injected_entropy = bytes_written * 8 / 2;
				if (ioctl(dev_random_fd, RNDADDTOENTCNT,
					&injected_entropy) == -1)
					log_it(debug, LOG_WARNING,
					    "can't add to entropy: %s\n",
					    strerror(errno));
				else
					log_it(debug, LOG_DEBUG,
					    "added %u bits to entropy\n",
					    injected_entropy);
			}
		}
	}

	/* NOTREACHED */
	if (close(atom_age_fd) == -1)
		log_it(debug, LOG_ERR, "closing %s: %s\n", device,
		    strerror(errno));
	if ((dev_random_fd != -1) && close(dev_random_fd) == -1)
		log_it(debug, LOG_ERR, "closing /dev/random: %s\n",
		    strerror(errno));

	exit(0);
}

static void
usage()
{

	(void) fprintf(stderr, "usage: ProgramName [-d] device\n");
	exit(1);
}

void
#if __STDC__
log_it(int debug, int l, const char *fmt,...)
#else
log_it(debug, l, *fmt, va_alist)
	int     debug;
	int     l;
	const char *fmt;
va_dcl
#endif
{
	va_list ap;
#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif
	if (debug) {
		fprintf(stderr, "%s: ", ProgramName);
		vfprintf(stderr, fmt, ap);
	} else
		vsyslog(l, fmt, ap);

	va_end(ap);
}
