/*
 * httprandom.c: Retrieve true random numbers from the random.org and
 *               the Hotbits server.
 *
 *
 * Copyright (c) 2003, eazy <eazy@ondaquadra.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define MAXLEN 300
#define HTTP_PORT 80
#define RANDOM_ORG "www.random.org"
#define HOTBITS "www.fourmilab.ch"
#define HOTBITS_ERR "You have exceeded your 24-hour quota for HotBits."

void
err_quit()
{
	perror("error");
	exit(-1);
}

void
usage(const char *name)
{
	fprintf(stderr, "Usage: %s [-h] <-r|-f> [-n nbytes]\n", name);
	fprintf(stderr, "\t-h\tthis help\n");
	fprintf(stderr, "\t-r\tcollect from random.org\n");
	fprintf(stderr, "\t-f\tcollect from Hotbits\n");
	fprintf(stderr, "\t-n\tnumber of bytes for request\n");
	exit(-1);
}

/*
   The checkbuf script tells you how full the random.org buffer is.
   The base URL of the script is:
   http://www.random.org/cgi-bin/checkbuf
 */

int
checkbuf(struct sockaddr_in addr)
{
	int             sock, n, i = 0;
	char            checkbuf[MAXLEN], buf[10], *ptr;

	/*
           Prepare the request to send to the checkbuf cgi script
         */
	memset(checkbuf, 0, MAXLEN);
	snprintf(checkbuf, MAXLEN - 1, "GET /cgi-bin/checkbuf\nHost: %s\n\n", RANDOM_ORG);

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		err_quit();

	/*
           Connect to random.org
         */
	if (connect(sock, (struct sockaddr *) & addr, sizeof(addr)) == -1)
		err_quit();

	/*
           Send the request to checkbuf cgi script
         */
	if (write(sock, checkbuf, strlen(checkbuf)) == -1)
		err_quit();

	/*
           Read random data returned from random.org
         */
	while ((n = read(sock, &buf[i], MAXLEN - 1)) > 0) {
		i += n;
	}
	if (n == -1)
		err_quit();

	buf[i] = 0;

	/*
           Parse the string and find the level of random.org buffer
         */
	if ((ptr = strstr(buf, "%")) == NULL) {
		fprintf(stderr, "Can't find the level of random.org buffer\n");
		return -1;
	}
	*ptr = 0;
	close(sock);

	/*
           If the level of random.org buffer is less than 20% return
           a negative value
         */
	if (atoi(buf) < 20)
		return -1;

	return 0;
}

int
httpget(struct sockaddr_in addr, const char *request, int outfd)
{
	int             sock, n;
	char            random[MAXLEN];

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		err_quit();

	if (connect(sock, (struct sockaddr *) & addr, sizeof(addr)) == -1)
		err_quit();

	/*
           Send the request to cgi script
         */
	if (write(sock, request, strlen(request)) == -1)
		err_quit();

	/*
           Read random data returned from the cgi script
         */
	while ((n = read(sock, random, MAXLEN - 1)) > 0) {
		random[n] = 0;

                /*
                   Exit if the quota for Hotbits are exceeded
                 */
		if (!strncmp(random, HOTBITS_ERR, strlen(HOTBITS_ERR))) {
			fprintf(stderr, "%s\n", HOTBITS_ERR);
			exit(0);
		} else {
			if (lockf(outfd, F_LOCK, 0) == -1)
				err_quit();
			/*
	                   Write random data to file or stdout
	                 */
			if (write(outfd, random, n) == -1)
				err_quit();

			if (lockf(outfd, F_ULOCK, 0) == -1)
				err_quit();
		}
	}
	if (n == -1)
		err_quit();

	close(sock);

}

/*
   Collect random data from random.org
 */
void
randomorg(int nbytes, int outfd)
{
	char            randbyte[MAXLEN];
	struct hostent *host;
	struct sockaddr_in addr;

	/*
           Resolve random.org hostname
         */
	if ((host = gethostbyname(RANDOM_ORG)) == NULL) {
		fprintf(stderr, "Can't resolve host %s\n", RANDOM_ORG);
		exit(-1);
	}
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(HTTP_PORT);
	memcpy(&addr.sin_addr, host->h_addr, sizeof(struct in_addr));

	memset(randbyte, 0, MAXLEN);
	snprintf(randbyte, MAXLEN - 1, "GET /cgi-bin/randbyte?nbytes=%d&format=f\nHost: %s\n\n", nbytes, RANDOM_ORG);

	for (;;) {

		/*
                   Check the buffer level every once in a while and only issue requests
                   when the buffer level is 20% or higher
                 */
		while (checkbuf(addr)) {
			fprintf(stderr, "The random.org buffer is lower then 20%, I'll retry later\n");
			sleep(60);
		}
		httpget(addr, randbyte, outfd);
	}
}

/*
   Collect random data from Hotbits
 */
void
hotbits(int nbytes, int outfd)
{
	char            request[MAXLEN];
	struct hostent *host;
	struct sockaddr_in addr;
	/*
           Resolve fourmilab.ch hostname
         */
	if ((host = gethostbyname(HOTBITS)) == NULL) {
		fprintf(stderr, "Can't resolve host %s\n", HOTBITS);
		exit(-1);
	}
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(HTTP_PORT);
	memcpy(&addr.sin_addr, host->h_addr, sizeof(struct in_addr));

	memset(request, 0, MAXLEN);
	snprintf(request, MAXLEN - 1, "GET /cgi-bin/uncgi/Hotbits?nbytes=%d&fmt=bin\nHost: %s\n\n", nbytes, HOTBITS);

	for (;;) {
		httpget(addr, request, outfd);
		sleep(5);
	}

}

int
main(int argc, char **argv)
{
	pid_t           pid;
	int             opt, nbytes = 256, outfd = STDOUT_FILENO;
	struct {
		int             org;
	}               random;
	struct {
		int             ch;
	}               fourmilab;

	if (argc < 2)
		usage(argv[0]);

	random.org = fourmilab.ch = 0;

	opterr = 0;
	while ((opt = getopt(argc, argv, "rfhn:")) != -1)
		switch (opt) {
		case 'r':
			random.org = 1;
			break;
		case 'f':
			fourmilab.ch = 1;
			break;
		case 'n':
			nbytes = atoi(optarg);
			break;
		case 'h':
		case '?':
                default:
			usage(argv[0]);
		}

	if (optind < argc)
		usage(argv[0]);

	if (random.org)
		if ((pid = fork()) == -1)
			err_quit();
		else if (pid == 0) {	/* child */
			randomorg(nbytes, outfd);
			exit(0);
		}

	if (fourmilab.ch)
		if ((pid = fork()) == -1)
			err_quit();
		else if (pid == 0) {	/* child */
			hotbits(nbytes, outfd);
			exit(0);
		}

        /*
           Wait termination of child process
         */
	while (waitpid(-1, NULL, 0) != -1);

}

