/*
 * $Id: atom_age.c,v 1.86 2002/09/05 15:37:34 psi4 Exp $
 * (c) Psi-4 1999, 2002
 */

#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 main_read_write_loop __P(());
static int read_atom_age __P(());
static void usage __P((void));
static int open_and_configure_tty __P((const char *));
static int inject_entropy __P((int));
static void log_it __P((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 that size but in bytes.  Otherwise, make it
 * some reasonably large power of two.
 */
#define BUFFER_SIZE (RND_POOLWORDS * 4)	/* from sys/rnd.h */

static const char *ProgramName;
static int debug;
static int continuous_mode;
static const char *input_file;
static const char *output_file;
static int input_fd;
static int output_fd;
static char buffer[BUFFER_SIZE + 1];	/* add room for NUL */

/*
 * 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[];
{
	int     ch;

	debug = 0;
	output_fd = -1;
	input_fd = -1;
	continuous_mode = 0;

	ProgramName = argv[0];

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

	if (argc != 2)
		usage();

	input_file = argv[0];
	output_file = argv[1];

	if (debug) {
		if (setlinebuf(stdout) != 0)
			err(1, "setting line buffering on stdout");
	} else {
		if (daemon(0, 0) != 0)
			err(1, "daemonizing");
		(void) openlog(ProgramName, LOG_CONS, LOG_LOCAL3);
		syslog(LOG_INFO, "%s: pid %d starting on %s\n", ProgramName, getpid(),
		    input_file);
	}

	if ((output_fd = open(output_file, O_WRONLY | O_CREAT)) == -1) {
		log_it(LOG_WARNING, "can't open %s for write: %s\n",
		    output_file, strerror(errno));
		exit(1);
	}
	main_read_write_loop();

	if (close(output_fd) == -1)
		log_it(LOG_ERR, "closing %s: %s\n", output_file, strerror(errno));

	return 0;
}


static void
main_read_write_loop()
{
	ssize_t bytes_read;
	ssize_t i;

	input_fd = open_and_configure_tty(input_file);

	/* 1st read from UART is usually corrupted so throw it away */
	read_atom_age();

	for (;;) {
		bytes_read = read_atom_age();
		log_it(LOG_DEBUG, "data: %d %s\n", bytes_read, buffer);
		if (bytes_read > 0) {
			for (i = 0; (i < bytes_read) && isxdigit(buffer[i]); ++i);
			if (i < bytes_read) {
				log_it(LOG_WARNING, "device produced non-hex digit: %#x\n", buffer[i]);
				exit(1);
			}
			inject_entropy(bytes_read);
		}
	}

	if (close(input_fd) == -1)
		log_it(LOG_ERR, "closing %s: %s\n", input_file, strerror(errno));
}

static int
read_atom_age()
{
	ssize_t bytes_read;

	/* Wiggle some lines to trigger AA output. */
	char    write_buffer[2];
	write_buffer[0] = 0x5A;
	write_buffer[1] = '\0';
	if (write(input_fd, write_buffer, 1) == -1) {
		log_it(LOG_ERR, "can't write to %s: %s\n", input_file, strerror(errno));
		exit(1);
	}
	bytes_read = read(input_fd, buffer, BUFFER_SIZE);
	if (bytes_read < 0) {
		log_it(LOG_ERR, "can't read from %s: %s\n", input_file, strerror(errno));
		exit(1);
	}
	buffer[bytes_read] = '\0';
	return bytes_read;
}

static void
usage()
{

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

static int
open_and_configure_tty(device)
	const char *device;
{
	int     fd;
	struct termios t;

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

	if (cfsetspeed(&t, B19200)) {
		log_it(LOG_ERR, "can't set speed on %s: %s\n", device, strerror(errno));
		exit(1);
	}
	if (tcsetattr(fd, TCSANOW, &t) == -1) {
		log_it(LOG_ERR, "can't set attrs on %s: %s\n", device, strerror(errno));
		exit(1);
	}
	return fd;
}

static int
inject_entropy(bytes_read)
	int     bytes_read;
{
	ssize_t bytes_written;
#ifdef RNDADDTOENTCNT
	u_int32_t injected_entropy;
#else
	rnddata_t rd;
#endif

	if (strcmp(output_file, "/dev/random") != 0) {
		if ((bytes_written = write(output_fd, buffer, (size_t) bytes_read)) == -1) {
			log_it(LOG_ERR, "can't write to %s: %s\n", output_file, strerror(errno));
			exit(1);
		}
		log_it(LOG_DEBUG, "wrote %d bytes to %s\n", bytes_written, output_file);
		return bytes_read * 8 / 2;
	}
#ifdef RNDADDTOENTCNT
	if (output_fd != -1) {
		if ((bytes_written = write(output_fd, buffer, (size_t) bytes_read)) == -1) {
			log_it(LOG_ERR, "can't write to %s: %s\n", output_file, strerror(errno));
			exit(1);
		}
		log_it(LOG_DEBUG, "wrote %d bytes to %s\n", bytes_written, output_file);
		injected_entropy = bytes_written * 8 / 2;
		if (ioctl(output_fd, RNDADDTOENTCNT, &injected_entropy) == -1)
			log_it(LOG_WARNING, "can't add to entropy: %s\n", strerror(errno));
		else
			log_it(LOG_DEBUG, "added %u bits to entropy\n", injected_entropy);
	}
	return injected_entropy;
#else				/* RNDADDDATA */
	rd.len = bytes_read;
	rd.entropy = bytes_read * 8 / 2;
	memcpy(rd.data, buffer, bytes_read);
	if (ioctl(output_fd, RNDADDDATA, &rd) == -1)
		log_it(LOG_WARNING,
		    "can't inject data to entropy pool: %s\n",
		    strerror(errno));
	else
		log_it(LOG_DEBUG, "injected %u bits of entropy\n", rd.entropy);
	return rd.entropy;
#endif
}

static void
#if __STDC__
log_it(int l, const char *fmt,...)
#else
log_it(l, *fmt, va_alist)
	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);
}
