/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */


/*! \file
 * \brief
 * This is the system independent part of the entropy module.  It is
 * compiled via inclusion from the relevant OS source file, ie,
 * \link unix/entropy.c unix/entropy.c \endlink or win32/entropy.c.
 *
 * \author Much of this code is modeled after the NetBSD /dev/random implementation,
 * written by Michael Graff <explorer@netbsd.org>.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>

#include <isc/buffer.h>
#include <isc/entropy.h>
#include <isc/keyboard.h>
#include <isc/list.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/msgs.h>
#include <isc/mutex.h>
#include <isc/platform.h>
#include <isc/print.h>
#include <isc/region.h>
#include <isc/safe.h>
#include <isc/sha1.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>

#ifdef PKCS11CRYPTO
#include <pk11/pk11.h>
#endif

#define ENTROPY_MAGIC		ISC_MAGIC('E', 'n', 't', 'e')
#define SOURCE_MAGIC		ISC_MAGIC('E', 'n', 't', 's')

#define VALID_ENTROPY(e)	ISC_MAGIC_VALID(e, ENTROPY_MAGIC)
#define VALID_SOURCE(s)		ISC_MAGIC_VALID(s, SOURCE_MAGIC)

/***
 *** "constants."  Do not change these unless you _really_ know what
 *** you are doing.
 ***/

/*%
 * Size of entropy pool in 32-bit words.  This _MUST_ be a power of 2.
 */
#define RND_POOLWORDS	128
/*% Pool in bytes. */
#define RND_POOLBYTES	(RND_POOLWORDS * 4)
/*% Pool in bits. */
#define RND_POOLBITS	(RND_POOLWORDS * 32)

/*%
 * Number of bytes returned per hash.  This must be true:
 *	threshold * 2 <= digest_size_in_bytes
 */
#define RND_ENTROPY_THRESHOLD	10
#define THRESHOLD_BITS		(RND_ENTROPY_THRESHOLD * 8)

/*%
 * Size of the input event queue in samples.
 */
#define RND_EVENTQSIZE	32

/*%
 * The number of times we'll "reseed" for pseudorandom seeds.  This is an
 * extremely weak pseudorandom seed.  If the caller is using lots of
 * pseudorandom data and they cannot provide a stronger random source,
 * there is little we can do other than hope they're smart enough to
 * call _adddata() with something better than we can come up with.
 */
#define RND_INITIALIZE	128

/*% Entropy Pool */
typedef struct {
	uint32_t	cursor;		/*%< current add point in the pool */
	uint32_t	entropy;	/*%< current entropy estimate in bits */
	uint32_t	pseudo;		/*%< bits extracted in pseudorandom */
	uint32_t	rotate;		/*%< how many bits to rotate by */
	uint32_t	pool[RND_POOLWORDS];	/*%< random pool data */
} isc_entropypool_t;

struct isc_entropy {
	unsigned int			magic;
	isc_mem_t		       *mctx;
	isc_mutex_t			lock;
	unsigned int			refcnt;
	uint32_t			initialized;
	uint32_t			initcount;
	isc_entropypool_t		pool;
	unsigned int			nsources;
	isc_entropysource_t	       *nextsource;
	ISC_LIST(isc_entropysource_t)	sources;
};

/*% Sample Queue */
typedef struct {
	uint32_t	last_time;	/*%< last time recorded */
	uint32_t	last_delta;	/*%< last delta value */
	uint32_t	last_delta2;	/*%< last delta2 value */
	uint32_t	nsamples;	/*%< number of samples filled in */
	uint32_t   *samples;	/*%< the samples */
	uint32_t   *extra;		/*%< extra samples added in */
} sample_queue_t;

typedef struct {
	sample_queue_t	samplequeue;
} isc_entropysamplesource_t;

typedef struct {
	bool		start_called;
	isc_entropystart_t	startfunc;
	isc_entropyget_t	getfunc;
	isc_entropystop_t	stopfunc;
	void		       *arg;
	sample_queue_t		samplequeue;
} isc_cbsource_t;

typedef struct {
	FILESOURCE_HANDLE_TYPE handle;
} isc_entropyfilesource_t;

struct isc_entropysource {
	unsigned int	magic;
	unsigned int	type;
	isc_entropy_t  *ent;
	uint32_t	total;		/*%< entropy from this source */
	ISC_LINK(isc_entropysource_t)	link;
	char		name[32];
	bool	bad;
	bool	warn_keyboard;
	isc_keyboard_t	kbd;
	union {
		isc_entropysamplesource_t	sample;
		isc_entropyfilesource_t		file;
		isc_cbsource_t			callback;
		isc_entropyusocketsource_t	usocket;
	} sources;
};

#define ENTROPY_SOURCETYPE_SAMPLE	1	/*%< Type is a sample source */
#define ENTROPY_SOURCETYPE_FILE		2	/*%< Type is a file source */
#define ENTROPY_SOURCETYPE_CALLBACK	3	/*%< Type is a callback source */
#define ENTROPY_SOURCETYPE_USOCKET	4	/*%< Type is a Unix socket source */

/*@{*/
/*%
 * The random pool "taps"
 */
#define TAP1	99
#define TAP2	59
#define TAP3	31
#define TAP4	 9
#define TAP5	 7
/*@}*/

/*@{*/
/*%
 * Declarations for function provided by the system dependent sources that
 * include this file.
 */
static void
fillpool(isc_entropy_t *, unsigned int, bool);

static int
wait_for_sources(isc_entropy_t *);

static void
destroyfilesource(isc_entropyfilesource_t *source);

static void
destroyusocketsource(isc_entropyusocketsource_t *source);

/*@}*/

static void
samplequeue_release(isc_entropy_t *ent, sample_queue_t *sq) {
	REQUIRE(sq->samples != NULL);
	REQUIRE(sq->extra != NULL);

	isc_mem_put(ent->mctx, sq->samples, RND_EVENTQSIZE * 4);
	isc_mem_put(ent->mctx, sq->extra, RND_EVENTQSIZE * 4);
	sq->samples = NULL;
	sq->extra = NULL;
}

static isc_result_t
samplesource_allocate(isc_entropy_t *ent, sample_queue_t *sq) {
	sq->samples = isc_mem_get(ent->mctx, RND_EVENTQSIZE * 4);
	if (sq->samples == NULL)
		return (ISC_R_NOMEMORY);

	sq->extra = isc_mem_get(ent->mctx, RND_EVENTQSIZE * 4);
	if (sq->extra == NULL) {
		isc_mem_put(ent->mctx, sq->samples, RND_EVENTQSIZE * 4);
		sq->samples = NULL;
		return (ISC_R_NOMEMORY);
	}

	sq->nsamples = 0;

	return (ISC_R_SUCCESS);
}

/*%
 * Add in entropy, even when the value we're adding in could be
 * very large.
 */
static inline void
add_entropy(isc_entropy_t *ent, uint32_t entropy) {
	/* clamp input.  Yes, this must be done. */
	entropy = ISC_MIN(entropy, RND_POOLBITS);
	/* Add in the entropy we already have. */
	entropy += ent->pool.entropy;
	/* Clamp. */
	ent->pool.entropy = ISC_MIN(entropy, RND_POOLBITS);
}

/*%
 * Decrement the amount of entropy the pool has.
 */
static inline void
subtract_entropy(isc_entropy_t *ent, uint32_t entropy) {
	entropy = ISC_MIN(entropy, ent->pool.entropy);
	ent->pool.entropy -= entropy;
}

/*!
 * Add in entropy, even when the value we're adding in could be
 * very large.
 */
static inline void
add_pseudo(isc_entropy_t *ent, uint32_t pseudo) {
	/* clamp input.  Yes, this must be done. */
	pseudo = ISC_MIN(pseudo, RND_POOLBITS * 8);
	/* Add in the pseudo we already have. */
	pseudo += ent->pool.pseudo;
	/* Clamp. */
	ent->pool.pseudo = ISC_MIN(pseudo, RND_POOLBITS * 8);
}

/*!
 * Decrement the amount of pseudo the pool has.
 */
static inline void
subtract_pseudo(isc_entropy_t *ent, uint32_t pseudo) {
	pseudo = ISC_MIN(pseudo, ent->pool.pseudo);
	ent->pool.pseudo -= pseudo;
}

/*!
 * Add one word to the pool, rotating the input as needed.
 */
static inline void
entropypool_add_word(isc_entropypool_t *rp, uint32_t val) {
	/*
	 * Steal some values out of the pool, and xor them into the
	 * word we were given.
	 *
	 * Mix the new value into the pool using xor.  This will
	 * prevent the actual values from being known to the caller
	 * since the previous values are assumed to be unknown as well.
	 */
	val ^= rp->pool[(rp->cursor + TAP1) & (RND_POOLWORDS - 1)];
	val ^= rp->pool[(rp->cursor + TAP2) & (RND_POOLWORDS - 1)];
	val ^= rp->pool[(rp->cursor + TAP3) & (RND_POOLWORDS - 1)];
	val ^= rp->pool[(rp->cursor + TAP4) & (RND_POOLWORDS - 1)];
	val ^= rp->pool[(rp->cursor + TAP5) & (RND_POOLWORDS - 1)];
	if (rp->rotate == 0)
		rp->pool[rp->cursor++] ^= val;
	else
		rp->pool[rp->cursor++] ^=
		  ((val << rp->rotate) | (val >> (32 - rp->rotate)));

	/*
	 * If we have looped around the pool, increment the rotate
	 * variable so the next value will get xored in rotated to
	 * a different position.
	 * Increment by a value that is relatively prime to the word size
	 * to try to spread the bits throughout the pool quickly when the
	 * pool is empty.
	 */
	if (rp->cursor == RND_POOLWORDS) {
		rp->cursor = 0;
		rp->rotate = (rp->rotate + 7) & 31;
	}
}

/*!
 * Add a buffer's worth of data to the pool.
 *
 * Requires that the lock is held on the entropy pool.
 */
static void
entropypool_adddata(isc_entropy_t *ent, void *p, unsigned int len,
		    uint32_t entropy)
{
	uint32_t val;
	unsigned long addr;
	uint8_t *buf;

	/* Silly MSVC in 64 bit mode complains here... */
#ifdef _WIN64
	addr = (unsigned long)((unsigned long long)p);
#else
	addr = (unsigned long)p;
#endif
	buf = p;

	if ((addr & 0x03U) != 0U) {
		val = 0;
		switch (len) {
		case 3:
			val = *buf++;
			len--;
			/* FALLTHROUGH */
		case 2:
			val = val << 8 | *buf++;
			len--;
			/* FALLTHROUGH */
		case 1:
			val = val << 8 | *buf++;
			len--;
		}

		entropypool_add_word(&ent->pool, val);
	}

	for (; len > 3; len -= 4) {
		val = *((uint32_t *)buf);

		entropypool_add_word(&ent->pool, val);
		buf += 4;
	}

	if (len != 0) {
		val = 0;
		switch (len) {
		case 3:
			val = *buf++;
			/* FALLTHROUGH */
		case 2:
			val = val << 8 | *buf++;
			/* FALLTHROUGH */
		case 1:
			val = val << 8 | *buf++;
		}

		entropypool_add_word(&ent->pool, val);
	}

	add_entropy(ent, entropy);
	subtract_pseudo(ent, entropy);
}

static inline void
reseed(isc_entropy_t *ent) {
	isc_time_t t;
	pid_t pid;

	if (ent->initcount == 0) {
		pid = getpid();
		entropypool_adddata(ent, &pid, sizeof(pid), 0);
		pid = getppid();
		entropypool_adddata(ent, &pid, sizeof(pid), 0);
	}

	/*!
	 * After we've reseeded 100 times, only add new timing info every
	 * 50 requests.  This will keep us from using lots and lots of
	 * CPU just to return bad pseudorandom data anyway.
	 */
	if (ent->initcount > 100)
		if ((ent->initcount % 50) != 0)
			return;

	TIME_NOW(&t);
	entropypool_adddata(ent, &t, sizeof(t), 0);
	ent->initcount++;
}

static inline unsigned int
estimate_entropy(sample_queue_t *sq, uint32_t t) {
	int32_t		delta;
	int32_t		delta2;
	int32_t		delta3;

	/*!
	 * If the time counter has overflowed, calculate the real difference.
	 * If it has not, it is simpler.
	 */
	if (t < sq->last_time)
		delta = UINT_MAX - sq->last_time + t;
	else
		delta = sq->last_time - t;

	if (delta < 0)
		delta = -delta;

	/*
	 * Calculate the second and third order differentials
	 */
	delta2 = sq->last_delta - delta;
	if (delta2 < 0)
		delta2 = -delta2;

	delta3 = sq->last_delta2 - delta2;
	if (delta3 < 0)
		delta3 = -delta3;

	sq->last_time = t;
	sq->last_delta = delta;
	sq->last_delta2 = delta2;

	/*
	 * If any delta is 0, we got no entropy.  If all are non-zero, we
	 * might have something.
	 */
	if (delta == 0 || delta2 == 0 || delta3 == 0)
		return 0;

	/*
	 * We could find the smallest delta and claim we got log2(delta)
	 * bits, but for now return that we found 1 bit.
	 */
	return 1;
}

static unsigned int
crunchsamples(isc_entropy_t *ent, sample_queue_t *sq) {
	unsigned int ns;
	unsigned int added;

	if (sq->nsamples < 6)
		return (0);

	added = 0;
	sq->last_time = sq->samples[0];
	sq->last_delta = 0;
	sq->last_delta2 = 0;

	/*
	 * Prime the values by adding in the first 4 samples in.  This
	 * should completely initialize the delta calculations.
	 */
	for (ns = 0; ns < 4; ns++)
		(void)estimate_entropy(sq, sq->samples[ns]);

	for (ns = 4; ns < sq->nsamples; ns++)
		added += estimate_entropy(sq, sq->samples[ns]);

	entropypool_adddata(ent, sq->samples, sq->nsamples * 4, added);
	entropypool_adddata(ent, sq->extra, sq->nsamples * 4, 0);

	/*
	 * Move the last 4 samples into the first 4 positions, and start
	 * adding new samples from that point.
	 */
	for (ns = 0; ns < 4; ns++) {
		sq->samples[ns] = sq->samples[sq->nsamples - 4 + ns];
		sq->extra[ns] = sq->extra[sq->nsamples - 4 + ns];
	}

	sq->nsamples = 4;

	return (added);
}

static unsigned int
get_from_callback(isc_entropysource_t *source, unsigned int desired,
		  bool blocking)
{
	isc_entropy_t *ent = source->ent;
	isc_cbsource_t *cbs = &source->sources.callback;
	unsigned int added;
	unsigned int got;
	isc_result_t result;

	if (desired == 0)
		return (0);

	if (source->bad)
		return (0);

	if (!cbs->start_called && cbs->startfunc != NULL) {
		result = cbs->startfunc(source, cbs->arg, blocking);
		if (result != ISC_R_SUCCESS)
			return (0);
		cbs->start_called = true;
	}

	added = 0;
	result = ISC_R_SUCCESS;
	while (desired > 0 && result == ISC_R_SUCCESS) {
		result = cbs->getfunc(source, cbs->arg, blocking);
		if (result == ISC_R_QUEUEFULL) {
			got = crunchsamples(ent, &cbs->samplequeue);
			added += got;
			desired -= ISC_MIN(got, desired);
			result = ISC_R_SUCCESS;
		} else if (result != ISC_R_SUCCESS &&
			   result != ISC_R_NOTBLOCKING)
			source->bad = true;

	}

	return (added);
}

/*
 * Extract some number of bytes from the random pool, decreasing the
 * estimate of randomness as each byte is extracted.
 *
 * Do this by stirring the pool and returning a part of hash as randomness.
 * Note that no secrets are given away here since parts of the hash are
 * xored together before returned.
 *
 * Honor the request from the caller to only return good data, any data,
 * etc.
 */
isc_result_t
isc_entropy_getdata(isc_entropy_t *ent, void *data, unsigned int length,
		    unsigned int *returned, unsigned int flags)
{
	unsigned int i;
	isc_sha1_t hash;
	unsigned char digest[ISC_SHA1_DIGESTLENGTH];
	uint32_t remain, deltae, count, total;
	uint8_t *buf;
	bool goodonly, partial, blocking;

	REQUIRE(VALID_ENTROPY(ent));
	REQUIRE(data != NULL);
	REQUIRE(length > 0);

	goodonly = (flags & ISC_ENTROPY_GOODONLY);
	partial = (flags & ISC_ENTROPY_PARTIAL);
	blocking = (flags & ISC_ENTROPY_BLOCKING);

	REQUIRE(!partial || returned != NULL);

	LOCK(&ent->lock);

	remain = length;
	buf = data;
	total = 0;
	while (remain != 0) {
		count = ISC_MIN(remain, RND_ENTROPY_THRESHOLD);

		/*
		 * If we are extracting good data only, make certain we
		 * have enough data in our pool for this pass.  If we don't,
		 * get some, and fail if we can't, and partial returns
		 * are not ok.
		 */
		if (goodonly) {
			unsigned int fillcount;

			fillcount = ISC_MAX(remain * 8, count * 8);

			/*
			 * If, however, we have at least THRESHOLD_BITS
			 * of entropy in the pool, don't block here.  It is
			 * better to drain the pool once in a while and
			 * then refill it than it is to constantly keep the
			 * pool full.
			 */
			if (ent->pool.entropy >= THRESHOLD_BITS)
				fillpool(ent, fillcount, false);
			else
				fillpool(ent, fillcount, blocking);

			/*
			 * Verify that we got enough entropy to do one
			 * extraction.  If we didn't, bail.
			 */
			if (ent->pool.entropy < THRESHOLD_BITS) {
				if (!partial)
					goto zeroize;
				else
					goto partial_output;
			}
		} else {
			/*
			 * If we've extracted half our pool size in bits
			 * since the last refresh, try to refresh here.
			 */
			if (ent->initialized < THRESHOLD_BITS)
				fillpool(ent, THRESHOLD_BITS, blocking);
			else
				fillpool(ent, 0, false);

			/*
			 * If we've not initialized with enough good random
			 * data, seed with our crappy code.
			 */
			if (ent->initialized < THRESHOLD_BITS)
				reseed(ent);
		}

		isc_sha1_init(&hash);
		isc_sha1_update(&hash, (void *)(ent->pool.pool),
				RND_POOLBYTES);
		isc_sha1_final(&hash, digest);

		/*
		 * Stir the extracted data (all of it) back into the pool.
		 */
		entropypool_adddata(ent, digest, ISC_SHA1_DIGESTLENGTH, 0);

		for (i = 0; i < count; i++)
			buf[i] = digest[i] ^ digest[i + RND_ENTROPY_THRESHOLD];

		buf += count;
		remain -= count;

		deltae = count * 8;
		deltae = ISC_MIN(deltae, ent->pool.entropy);
		total += deltae;
		subtract_entropy(ent, deltae);
		add_pseudo(ent, count * 8);
	}

 partial_output:
	isc_safe_memwipe(digest, sizeof(digest));

	if (returned != NULL)
		*returned = (length - remain);

	UNLOCK(&ent->lock);

	return (ISC_R_SUCCESS);

 zeroize:
	/* put the entropy we almost extracted back */
	add_entropy(ent, total);
	isc_safe_memwipe(data, length);
	isc_safe_memwipe(digest, sizeof(digest));
	if (returned != NULL)
		*returned = 0;

	UNLOCK(&ent->lock);

	return (ISC_R_NOENTROPY);
}

static void
isc_entropypool_init(isc_entropypool_t *pool) {
	pool->cursor = RND_POOLWORDS - 1;
	pool->entropy = 0;
	pool->pseudo = 0;
	pool->rotate = 0;
	memset(pool->pool, 0, RND_POOLBYTES);
}

static void
isc_entropypool_invalidate(isc_entropypool_t *pool) {
	pool->cursor = 0;
	pool->entropy = 0;
	pool->pseudo = 0;
	pool->rotate = 0;
	memset(pool->pool, 0, RND_POOLBYTES);
}

isc_result_t
isc_entropy_create(isc_mem_t *mctx, isc_entropy_t **entp) {
	isc_result_t result;
	isc_entropy_t *ent;

	REQUIRE(mctx != NULL);
	REQUIRE(entp != NULL && *entp == NULL);

	ent = isc_mem_get(mctx, sizeof(isc_entropy_t));
	if (ent == NULL)
		return (ISC_R_NOMEMORY);

	/*
	 * We need a lock.
	 */
	result = isc_mutex_init(&ent->lock);
	if (result != ISC_R_SUCCESS)
		goto errout;

	/*
	 * From here down, no failures will/can occur.
	 */
	ISC_LIST_INIT(ent->sources);
	ent->nextsource = NULL;
	ent->nsources = 0;
	ent->mctx = NULL;
	isc_mem_attach(mctx, &ent->mctx);
	ent->refcnt = 1;
	ent->initialized = 0;
	ent->initcount = 0;
	ent->magic = ENTROPY_MAGIC;

	isc_entropypool_init(&ent->pool);

	*entp = ent;
	return (ISC_R_SUCCESS);

 errout:
	isc_mem_put(mctx, ent, sizeof(isc_entropy_t));

	return (result);
}

/*!
 * Requires "ent" be locked.
 */
static void
destroysource(isc_entropysource_t **sourcep) {
	isc_entropysource_t *source;
	isc_entropy_t *ent;
	isc_cbsource_t *cbs;

	source = *sourcep;
	*sourcep = NULL;
	ent = source->ent;

	ISC_LIST_UNLINK(ent->sources, source, link);
	ent->nextsource = NULL;
	REQUIRE(ent->nsources > 0);
	ent->nsources--;

	switch (source->type) {
	case ENTROPY_SOURCETYPE_FILE:
		if (! source->bad)
			destroyfilesource(&source->sources.file);
		break;
	case ENTROPY_SOURCETYPE_USOCKET:
		if (! source->bad)
			destroyusocketsource(&source->sources.usocket);
		break;
	case ENTROPY_SOURCETYPE_SAMPLE:
		samplequeue_release(ent, &source->sources.sample.samplequeue);
		break;
	case ENTROPY_SOURCETYPE_CALLBACK:
		cbs = &source->sources.callback;
		if (cbs->start_called && cbs->stopfunc != NULL) {
			cbs->stopfunc(source, cbs->arg);
			cbs->start_called = false;
		}
		samplequeue_release(ent, &cbs->samplequeue);
		break;
	}

	isc_safe_memwipe(source, sizeof(*source));
	isc_mem_put(ent->mctx, source, sizeof(*source));
}

static inline bool
destroy_check(isc_entropy_t *ent) {
	isc_entropysource_t *source;

	if (ent->refcnt > 0)
		return (false);

	source = ISC_LIST_HEAD(ent->sources);
	while (source != NULL) {
		switch (source->type) {
		case ENTROPY_SOURCETYPE_FILE:
		case ENTROPY_SOURCETYPE_USOCKET:
			break;
		default:
			return (false);
		}
		source = ISC_LIST_NEXT(source, link);
	}

	return (true);
}

static void
destroy(isc_entropy_t **entp) {
	isc_entropy_t *ent;
	isc_entropysource_t *source;
	isc_mem_t *mctx;

	REQUIRE(entp != NULL && *entp != NULL);
	ent = *entp;
	*entp = NULL;

	LOCK(&ent->lock);

	REQUIRE(ent->refcnt == 0);

	/*
	 * Here, detach non-sample sources.
	 */
	source = ISC_LIST_HEAD(ent->sources);
	while (source != NULL) {
		switch(source->type) {
		case ENTROPY_SOURCETYPE_FILE:
		case ENTROPY_SOURCETYPE_USOCKET:
			destroysource(&source);
			break;
		}
		source = ISC_LIST_HEAD(ent->sources);
	}

	/*
	 * If there are other types of sources, we've found a bug.
	 */
	REQUIRE(ISC_LIST_EMPTY(ent->sources));

	mctx = ent->mctx;

	isc_entropypool_invalidate(&ent->pool);

	UNLOCK(&ent->lock);

	DESTROYLOCK(&ent->lock);

	isc_safe_memwipe(ent, sizeof(*ent));
	isc_mem_put(mctx, ent, sizeof(*ent));
	isc_mem_detach(&mctx);
}

void
isc_entropy_destroysource(isc_entropysource_t **sourcep) {
	isc_entropysource_t *source;
	isc_entropy_t *ent;
	bool killit;

	REQUIRE(sourcep != NULL);
	REQUIRE(VALID_SOURCE(*sourcep));

	source = *sourcep;
	*sourcep = NULL;

	ent = source->ent;
	REQUIRE(VALID_ENTROPY(ent));

	LOCK(&ent->lock);

	destroysource(&source);

	killit = destroy_check(ent);

	UNLOCK(&ent->lock);

	if (killit)
		destroy(&ent);
}

isc_result_t
isc_entropy_createcallbacksource(isc_entropy_t *ent,
				 isc_entropystart_t start,
				 isc_entropyget_t get,
				 isc_entropystop_t stop,
				 void *arg,
				 isc_entropysource_t **sourcep)
{
	isc_result_t result;
	isc_entropysource_t *source;
	isc_cbsource_t *cbs;

	REQUIRE(VALID_ENTROPY(ent));
	REQUIRE(get != NULL);
	REQUIRE(sourcep != NULL && *sourcep == NULL);

	LOCK(&ent->lock);

	source = isc_mem_get(ent->mctx, sizeof(isc_entropysource_t));
	if (source == NULL) {
		result = ISC_R_NOMEMORY;
		goto errout;
	}
	source->bad = false;

	cbs = &source->sources.callback;

	result = samplesource_allocate(ent, &cbs->samplequeue);
	if (result != ISC_R_SUCCESS)
		goto errout;

	cbs->start_called = false;
	cbs->startfunc = start;
	cbs->getfunc = get;
	cbs->stopfunc = stop;
	cbs->arg = arg;

	/*
	 * From here down, no failures can occur.
	 */
	source->magic = SOURCE_MAGIC;
	source->type = ENTROPY_SOURCETYPE_CALLBACK;
	source->ent = ent;
	source->total = 0;
	memset(source->name, 0, sizeof(source->name));
	ISC_LINK_INIT(source, link);

	/*
	 * Hook it into the entropy system.
	 */
	ISC_LIST_APPEND(ent->sources, source, link);
	ent->nsources++;

	*sourcep = source;

	UNLOCK(&ent->lock);
	return (ISC_R_SUCCESS);

 errout:
	if (source != NULL)
		isc_mem_put(ent->mctx, source, sizeof(isc_entropysource_t));

	UNLOCK(&ent->lock);

	return (result);
}

void
isc_entropy_stopcallbacksources(isc_entropy_t *ent) {
	isc_entropysource_t *source;
	isc_cbsource_t *cbs;

	REQUIRE(VALID_ENTROPY(ent));

	LOCK(&ent->lock);

	source = ISC_LIST_HEAD(ent->sources);
	while (source != NULL) {
		if (source->type == ENTROPY_SOURCETYPE_CALLBACK) {
			cbs = &source->sources.callback;
			if (cbs->start_called && cbs->stopfunc != NULL) {
				cbs->stopfunc(source, cbs->arg);
				cbs->start_called = false;
			}
		}

		source = ISC_LIST_NEXT(source, link);
	}

	UNLOCK(&ent->lock);
}

isc_result_t
isc_entropy_createsamplesource(isc_entropy_t *ent,
			       isc_entropysource_t **sourcep)
{
	isc_result_t result;
	isc_entropysource_t *source;
	sample_queue_t *sq;

	REQUIRE(VALID_ENTROPY(ent));
	REQUIRE(sourcep != NULL && *sourcep == NULL);

	LOCK(&ent->lock);

	source = isc_mem_get(ent->mctx, sizeof(isc_entropysource_t));
	if (source == NULL) {
		result = ISC_R_NOMEMORY;
		goto errout;
	}

	sq = &source->sources.sample.samplequeue;
	result = samplesource_allocate(ent, sq);
	if (result != ISC_R_SUCCESS)
		goto errout;

	/*
	 * From here down, no failures can occur.
	 */
	source->magic = SOURCE_MAGIC;
	source->type = ENTROPY_SOURCETYPE_SAMPLE;
	source->ent = ent;
	source->total = 0;
	memset(source->name, 0, sizeof(source->name));
	ISC_LINK_INIT(source, link);

	/*
	 * Hook it into the entropy system.
	 */
	ISC_LIST_APPEND(ent->sources, source, link);
	ent->nsources++;

	*sourcep = source;

	UNLOCK(&ent->lock);
	return (ISC_R_SUCCESS);

 errout:
	if (source != NULL)
		isc_mem_put(ent->mctx, source, sizeof(isc_entropysource_t));

	UNLOCK(&ent->lock);

	return (result);
}

/*!
 * Add a sample, and return ISC_R_SUCCESS if the queue has become full,
 * ISC_R_NOENTROPY if it has space remaining, and ISC_R_NOMORE if the
 * queue was full when this function was called.
 */
static isc_result_t
addsample(sample_queue_t *sq, uint32_t sample, uint32_t extra) {
	if (sq->nsamples >= RND_EVENTQSIZE)
		return (ISC_R_NOMORE);

	sq->samples[sq->nsamples] = sample;
	sq->extra[sq->nsamples] = extra;
	sq->nsamples++;

	if (sq->nsamples >= RND_EVENTQSIZE)
		return (ISC_R_QUEUEFULL);

	return (ISC_R_SUCCESS);
}

isc_result_t
isc_entropy_addsample(isc_entropysource_t *source, uint32_t sample,
		      uint32_t extra)
{
	isc_entropy_t *ent;
	sample_queue_t *sq;
	unsigned int entropy;
	isc_result_t result;

	REQUIRE(VALID_SOURCE(source));

	ent = source->ent;

	LOCK(&ent->lock);

	sq = &source->sources.sample.samplequeue;
	result = addsample(sq, sample, extra);
	if (result == ISC_R_QUEUEFULL) {
		entropy = crunchsamples(ent, sq);
		add_entropy(ent, entropy);
	}

	UNLOCK(&ent->lock);

	return (result);
}

isc_result_t
isc_entropy_addcallbacksample(isc_entropysource_t *source, uint32_t sample,
			      uint32_t extra)
{
	sample_queue_t *sq;
	isc_result_t result;

	REQUIRE(VALID_SOURCE(source));
	REQUIRE(source->type == ENTROPY_SOURCETYPE_CALLBACK);

	sq = &source->sources.callback.samplequeue;
	result = addsample(sq, sample, extra);

	return (result);
}

void
isc_entropy_putdata(isc_entropy_t *ent, void *data, unsigned int length,
		    uint32_t entropy)
{
	REQUIRE(VALID_ENTROPY(ent));

	LOCK(&ent->lock);

	entropypool_adddata(ent, data, length, entropy);

	if (ent->initialized < THRESHOLD_BITS)
		ent->initialized = THRESHOLD_BITS;

	UNLOCK(&ent->lock);
}

static void
dumpstats(isc_entropy_t *ent, FILE *out) {
	fprintf(out,
		isc_msgcat_get(isc_msgcat, ISC_MSGSET_ENTROPY,
			       ISC_MSG_ENTROPYSTATS,
			       "Entropy pool %p:  refcnt %u cursor %u,"
			       " rotate %u entropy %u pseudo %u nsources %u"
			       " nextsource %p initialized %u initcount %u\n"),
		ent, ent->refcnt,
		ent->pool.cursor, ent->pool.rotate,
		ent->pool.entropy, ent->pool.pseudo,
		ent->nsources, ent->nextsource, ent->initialized,
		ent->initcount);
}

/*
 * This function ignores locking.  Use at your own risk.
 */
void
isc_entropy_stats(isc_entropy_t *ent, FILE *out) {
	REQUIRE(VALID_ENTROPY(ent));

	LOCK(&ent->lock);
	dumpstats(ent, out);
	UNLOCK(&ent->lock);
}

unsigned int
isc_entropy_status(isc_entropy_t *ent) {
	unsigned int estimate;

	LOCK(&ent->lock);
	estimate = ent->pool.entropy;
	UNLOCK(&ent->lock);

	return estimate;
}

void
isc_entropy_attach(isc_entropy_t *ent, isc_entropy_t **entp) {
	REQUIRE(VALID_ENTROPY(ent));
	REQUIRE(entp != NULL && *entp == NULL);

	LOCK(&ent->lock);

	ent->refcnt++;
	*entp = ent;

	UNLOCK(&ent->lock);
}

void
isc_entropy_detach(isc_entropy_t **entp) {
	isc_entropy_t *ent;
	bool killit;

	REQUIRE(entp != NULL && VALID_ENTROPY(*entp));
	ent = *entp;
	*entp = NULL;

	LOCK(&ent->lock);

	REQUIRE(ent->refcnt > 0);
	ent->refcnt--;

	killit = destroy_check(ent);

	UNLOCK(&ent->lock);

	if (killit)
		destroy(&ent);
}

static isc_result_t
kbdstart(isc_entropysource_t *source, void *arg, bool blocking) {
	/*
	 * The intent of "first" is to provide a warning message only once
	 * during the run of a program that might try to gather keyboard
	 * entropy multiple times.
	 */
	static bool first = true;

	UNUSED(arg);

	if (! blocking)
		return (ISC_R_NOENTROPY);

	if (first) {
		if (source->warn_keyboard)
			fprintf(stderr, "You must use the keyboard to create "
				"entropy, since your system is lacking\n"
				"/dev/random (or equivalent)\n\n");
		first = false;
	}
	fprintf(stderr, "start typing:\n");

	return (isc_keyboard_open(&source->kbd));
}

static void
kbdstop(isc_entropysource_t *source, void *arg) {

	UNUSED(arg);

	if (! isc_keyboard_canceled(&source->kbd))
		fprintf(stderr, "stop typing.\r\n");

	(void)isc_keyboard_close(&source->kbd, 3);
}

static isc_result_t
kbdget(isc_entropysource_t *source, void *arg, bool blocking) {
	isc_result_t result;
	isc_time_t t;
	uint32_t sample;
	uint32_t extra;
	unsigned char c;

	UNUSED(arg);

	if (!blocking)
		return (ISC_R_NOTBLOCKING);

	result = isc_keyboard_getchar(&source->kbd, &c);
	if (result != ISC_R_SUCCESS)
		return (result);

	TIME_NOW(&t);

	sample = isc_time_nanoseconds(&t);
	extra = c;

	result = isc_entropy_addcallbacksample(source, sample, extra);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "\r\n");
		return (result);
	}

	fprintf(stderr, ".");
	fflush(stderr);

	return (result);
}

isc_result_t
isc_entropy_usebestsource(isc_entropy_t *ectx, isc_entropysource_t **source,
			  const char *randomfile, int use_keyboard)
{
	isc_result_t result;
	isc_result_t final_result = ISC_R_NOENTROPY;
	bool userfile = true;

	REQUIRE(VALID_ENTROPY(ectx));
	REQUIRE(source != NULL && *source == NULL);
	REQUIRE(use_keyboard == ISC_ENTROPY_KEYBOARDYES ||
		use_keyboard == ISC_ENTROPY_KEYBOARDNO  ||
		use_keyboard == ISC_ENTROPY_KEYBOARDMAYBE);

#ifdef PKCS11CRYPTO
	if (randomfile != NULL)
		pk11_rand_seed_fromfile(randomfile);
#endif

#ifdef PATH_RANDOMDEV
	if (randomfile == NULL) {
		randomfile = PATH_RANDOMDEV;
		userfile = false;
	}
#endif

	if (randomfile != NULL && use_keyboard != ISC_ENTROPY_KEYBOARDYES) {
		result = isc_entropy_createfilesource(ectx, randomfile);
		if (result == ISC_R_SUCCESS &&
		    use_keyboard == ISC_ENTROPY_KEYBOARDMAYBE)
			use_keyboard = ISC_ENTROPY_KEYBOARDNO;
		if (result != ISC_R_SUCCESS && userfile)
			return (result);

		final_result = result;
	}

	if (use_keyboard != ISC_ENTROPY_KEYBOARDNO) {
		result = isc_entropy_createcallbacksource(ectx, kbdstart,
							  kbdget, kbdstop,
							  NULL, source);
		if (result == ISC_R_SUCCESS)
			(*source)->warn_keyboard =
				(use_keyboard == ISC_ENTROPY_KEYBOARDMAYBE);

		if (final_result != ISC_R_SUCCESS)
			final_result = result;
	}

	/*
	 * final_result is ISC_R_SUCCESS if at least one source of entropy
	 * could be started, otherwise it is the error from the most recently
	 * failed operation (or ISC_R_NOENTROPY if PATH_RANDOMDEV is not
	 * defined and use_keyboard is ISC_ENTROPY_KEYBOARDNO).
	 */
	return (final_result);
}
