/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "randgen.h"
#include <unistd.h>
#include <fcntl.h>

#if defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
/* For reproducing tests, fall back onto using a simple deterministic PRNG */
/* Marsaglia's 1999 KISS, de-macro-ified, and with the fixed KISS11 SHR3,
   which is clearly what was intended given the "cycle length 2^123" claim. */
static bool kiss_in_use;
static unsigned int kiss_seed;
static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
static void
kiss_init(unsigned int seed)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
	i_info("Random numbers are PRNG using kiss, as per DOVECOT_SRAND=%u", seed);
#endif
	kiss_seed = seed;
	kiss_jsr = 0x5eed5eed; /* simply mustn't be 0 */
	kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
	kiss_in_use = TRUE;
}
ATTR_NO_SANITIZE_UNDEFINED
ATTR_NO_SANITIZE_INTEGER
static unsigned int
kiss_rand(void)
{
	kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16);
	kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16);
	kiss_jcong = 69069 * kiss_jcong + 1234567;
	kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */
	kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */
	kiss_jsr^=(kiss_jsr<<5);
	return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr;
}
int rand_get_last_seed(unsigned int *seed_r)
{
	if (!kiss_in_use)
		return -1; /* not using a deterministic PRNG, seed is irrelevant */
	*seed_r = kiss_seed;
	return 0;
}
#endif

/* get randomness from either getrandom, arc4random or /dev/urandom */

#if defined(HAVE_GETRANDOM) && HAVE_DECL_GETRANDOM != 0
#  include <sys/random.h>
#  define USE_GETRANDOM
static bool getrandom_present = TRUE;
#elif defined(HAVE_ARC4RANDOM)
#  if defined(HAVE_LIBBSD)
#    include <bsd/stdlib.h>
#  endif
#  define USE_ARC4RANDOM
#else
static bool getrandom_present = FALSE;
#  define USE_RANDOM_DEV
#endif

static int init_refcount = 0;
static int urandom_fd = -1;

#if defined(USE_GETRANDOM) || defined(USE_RANDOM_DEV)
static unsigned char random_next[RANDOM_READ_BUFFER_SIZE];
static size_t random_next_pos = 0;
static size_t random_next_size = 0;

static void random_open_urandom(void)
{
	urandom_fd = open(DEV_URANDOM_PATH, O_RDONLY);
	if (urandom_fd == -1) {
		if (errno == ENOENT) {
			i_fatal("open("DEV_URANDOM_PATH") failed: doesn't exist,"
				"currently we require it");
		} else {
			i_fatal("open("DEV_URANDOM_PATH") failed: %m");
		}
	}
	fd_close_on_exec(urandom_fd, TRUE);
}

static inline int random_read(unsigned char *buf, size_t size)
{
	ssize_t ret = 0;
# if defined(USE_GETRANDOM)
	if (getrandom_present) {
		ret = getrandom(buf, size, 0);
		if (ret < 0 && errno == ENOSYS) {
			getrandom_present = FALSE;
			/* It gets complicated here...  While the libc (and its
			headers) indicated that getrandom() was available when
			we were compiled, the kernel disagreed just now at
			runtime. Fall back to reading /dev/urandom. */
			random_open_urandom();
		}
	}
	/* this is here to avoid clang complain,
	   because getrandom_present will be always FALSE
	   if USE_GETRANDOM is not defined */
	if (!getrandom_present)
# endif
		ret = read(urandom_fd, buf, size);
	if (unlikely(ret <= 0)) {
		if (ret == 0) {
			i_fatal("read("DEV_URANDOM_PATH") failed: EOF");
		} else if (errno != EINTR) {
			if (getrandom_present) {
				i_fatal("getrandom() failed: %m");
			} else {
				i_fatal("read("DEV_URANDOM_PATH") failed: %m");
			}
		}
	}
	i_assert(ret > 0 || errno == EINTR);
	return ret;
}
#endif

void random_fill(void *buf, size_t size)
{
	i_assert(init_refcount > 0);
	i_assert(size < SSIZE_T_MAX);

#if defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
	if (kiss_in_use) {
		for (size_t pos = 0; pos < size; pos++)
			((unsigned char*)buf)[pos] = (unsigned char)(kiss_rand() % 256);
		return;
	}
#endif

#if defined(USE_ARC4RANDOM)
	arc4random_buf(buf, size);
#else
	size_t pos;
	ssize_t ret;

	for (pos = 0; pos < size; ) {
		if (size >= sizeof(random_next) && random_next_size == 0) {
			/* Asking for lots of randomness. Read directly to the
			   destination buffer. */
			ret = random_read(PTR_OFFSET(buf, pos), size - pos);
			if (ret > -1)
				pos += ret;
		} else {
			/* Asking for a little randomness. Read via a larger
			   buffer to reduce the number of syscalls. */
			if (random_next_size > random_next_pos)
				ret = random_next_size - random_next_pos;
			else {
				random_next_pos = 0;
				ret = random_read(random_next,
						  sizeof(random_next));
				random_next_size = ret < 0 ? 0 : ret;
			}
			if (ret > 0) {
				size_t used = I_MIN(size - pos, (size_t)ret);
				memcpy(PTR_OFFSET(buf, pos),
				       random_next + random_next_pos, used);
				random_next_pos += used;
				pos += used;
			}
		}
	}
#endif /* defined(USE_ARC4RANDOM) */
}

void random_init(void)
{
	if (init_refcount++ > 0)
		return;

#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
	kiss_init(0);
	srand(0);
#else
	/* static analyzer seems to require this */
	unsigned int seed = 0;
	const char *env_seed;

	env_seed = getenv("DOVECOT_SRAND");
#ifdef DEBUG
	if (env_seed != NULL && str_to_uint(env_seed, &seed) >= 0) {
		kiss_init(seed);
		/* getrandom_present = FALSE; not needed, only used in random_read() */
		goto normal_exit;
	}
#else
	if (env_seed != NULL && *env_seed != '\0')
		i_warning("DOVECOT_SRAND is not available in non-debug builds");
#endif /* DEBUG */

#if defined(USE_RANDOM_DEV)
	random_open_urandom();
#endif
	/* DO NOT REMOVE THIS - It is also
	   needed to make sure getrandom really works.
	*/
	random_fill(&seed, sizeof(seed));
#ifdef DEBUG
	if (env_seed != NULL) {
		if (strcmp(env_seed, "kiss") != 0)
			i_fatal("DOVECOT_SRAND not a number or 'kiss'");
		kiss_init(seed);
		i_close_fd(&urandom_fd);
	}

normal_exit:
#endif
	srand(seed);
#endif
}

void random_deinit(void)
{
	if (--init_refcount > 0)
		return;
	i_close_fd(&urandom_fd);
}
