/****************************************************************************
 * Random gatherer functions for OS/2 that use the rndos2.dll library       *
 ****************************************************************************/

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <types.h>

#define INCL_DOSERRORS
#define INCL_DOSMODULEMGR
#define INCL_LONGLONG
#include <os2.h>

#define LONGLONG		long long

#include "types.h"  /* for byte and u32 typedefs */
#include "util.h"
#include "i18n.h"
#include "../g10/options.h"

#include "../os2/rndos2.h"

int (* EXPENTRY p_rndos2_open)(UCHAR);
int (* EXPENTRY p_rndos2_close)(UCHAR);
int (* EXPENTRY p_rndos2_read)(UCHAR, PCHAR, ULONG);
int (* EXPENTRY p_rndos2_write)(UCHAR, PCHAR, ULONG);
int (* EXPENTRY p_rndos2_select)(UCHAR, UCHAR);
int (* EXPENTRY p_rndos2_ioctl)(UCHAR, ULONG, PVOID);
char* (* EXPENTRY p_rndos2_version)(void);



#define RNDOS2_DLL	"rndos2.dll"

HMODULE		rndos2_handle = NULLHANDLE;

int rndos2_load(void);
int rndos2_unload(void);

/****************
 * Using a level of 0 should never block and better add nothing
 * to the pool.  So this is just a dummy for this gatherer.
 */
#define RANDOM_BYTES_LEN	128
int rndos2_gather_random(void (*add)(const void*, size_t, int), int requester, size_t length, int level) {
	int		rc;
	char		random_bytes[RANDOM_BYTES_LEN];
	unsigned long	to_read;
	UCHAR		random_device = RND_URND;

	if (level == 0)
		return 0;

	if (level >= 2)
		random_device = RND_SRND;	/* Use a real strong pseudo random device (/dev/srandom on BSD) */


	while (length > 0) {
		if (length >= RANDOM_BYTES_LEN)
			to_read = RANDOM_BYTES_LEN;
		else
			to_read = length;

		rc = (*p_rndos2_read)(random_device, (PVOID)&random_bytes, to_read);
		if (rc != 0) {
			fprintf(stderr, "rndos2_read() failed (rc = %u)\n", rc);
			return -1;
		}
		(*add)((PVOID)&random_bytes, to_read, requester);
		length -= to_read;
	}

	return 0;
}

int rndos2_load(void) {
	APIRET		rc = NO_ERROR;
	char		rndos2_file[__CCHMAXPATH + 1];
	char		load_error[__CCHMAXPATH];

	memset((char*)&load_error, 0, sizeof(load_error));
	/*
	 * Look for rndos2.dll in the following places:
	 * (1) GNUPG_LIBDIR
	 * (2) The directory where gpg[v].exe resides
	 * (3) OS/2 LIBPATH
	 */
	memset((char*)&rndos2_file, 0, sizeof(rndos2_file));
	strcat(rndos2_file, GNUPG_LIBDIR);
	if (strlen(rndos2_file) + strlen(RNDOS2_DLL) + 1 < __CCHMAXPATH) {
		strcat(rndos2_file, "/");
		strcat(rndos2_file, RNDOS2_DLL);
		rc = DosLoadModule(load_error, sizeof(load_error),
			rndos2_file, &rndos2_handle);
		if (rc != NO_ERROR) {
			if (opt.verbose)
				log_info(_("loading %s failed\n"), rndos2_file);
		} else
			goto query_addr;
	}

	memset((char*)&rndos2_file, 0, sizeof(rndos2_file));
	strcat(rndos2_file, __OS2_PROGDIR);
	if (strlen(rndos2_file) + strlen(RNDOS2_DLL) + 1 < __CCHMAXPATH) {
		strcat(rndos2_file, "/");
		strcat(rndos2_file, RNDOS2_DLL);
		rc = DosLoadModule(load_error, sizeof(load_error),
			rndos2_file, &rndos2_handle);
		if (rc != NO_ERROR) {
			if (opt.verbose)
				log_info(_("loading %s failed\n"), rndos2_file);
		} else
			goto query_addr;
	}

	memset((char*)&rndos2_file, 0, sizeof(rndos2_file));
	strcat(rndos2_file, RNDOS2_DLL);
	rc = DosLoadModule(load_error, sizeof(load_error), rndos2_file, &rndos2_handle);
	if (rc != NO_ERROR) {
		if (opt.verbose)
 			log_info(_("loading %s failed\n"), rndos2_file);
		return -1;
	}

query_addr:
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_open", (PFN*)&p_rndos2_open);
	if (rc != NO_ERROR)
		p_rndos2_open = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_close", (PFN*)&p_rndos2_close);
	if (rc != NO_ERROR)
		p_rndos2_close = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_read", (PFN*)&p_rndos2_read);
	if (rc != NO_ERROR)
		p_rndos2_read = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_write", (PFN*)&p_rndos2_write);
	if (rc != NO_ERROR)
		p_rndos2_write = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_select", (PFN*)&p_rndos2_select);
	if (rc != NO_ERROR)
		p_rndos2_select = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_ioctl", (PFN*)&p_rndos2_ioctl);
	if (rc != NO_ERROR)
		p_rndos2_ioctl = NULL;
	rc = DosQueryProcAddr(rndos2_handle, 0, "rndos2_version", (PFN*)&p_rndos2_version);
	if (rc != NO_ERROR)
		p_rndos2_version = NULL;
	if ((p_rndos2_open == NULL) ||
	    (p_rndos2_close == NULL) ||
	    (p_rndos2_read == NULL) ||
	    (p_rndos2_write == NULL) ||
	    (p_rndos2_select == NULL) ||
	    (p_rndos2_ioctl == NULL) ||
	    (p_rndos2_version == NULL)) {
		log_error(_("WARNING: %s is not a valid rndos2.dll!\n"), rndos2_file);
		return -1;
	}
	if (opt.verbose)
		log_info(_("%s loaded, version string: %s\n"), rndos2_file, (*p_rndos2_version)());

	return 0;
}

int rndos2_unload(void) {
	APIRET		rc = NO_ERROR;

	if (rndos2_handle != NULLHANDLE) {
		rc = DosFreeModule(rndos2_handle);
		if (rc != NO_ERROR)
			fprintf(stderr, "DosFreeModule() failed (rc = %u)\n", (unsigned int)rc);
	}

	return (rc == NO_ERROR ? 0 : -1);
}

