/*
Copyright (c) 2000-2010, Dirk Krause
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 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

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 OWNER 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.
*/



/**	@file	dkappr.c	Random data generation in applications.
*/



#include "dk.h"
/**	Inside the dkappr module.
*/
#define	DKAPPR_C	1
#include "dkapp.h"
#include "dksfc.h"
#include "dksf.h"
#include "dkrandc.h"
#include "dkmem.h"
#include "dkstr.h"

#if DK_HAVE_STRING_H
#include <string.h>
#endif
#if DK_HAVE_STDLIB_H
#include <stdlib.h>
#endif

#if DK_HAVE_OPENSSL_RAND_H
#include <openssl/rand.h>
#endif




#line 69 "dkappr.ctr"




/**	Comma used in messages.
*/
static char comma[] = { "," };

/**	Environment variable to inspect for random seed file name.
*/
static char str_randfile[] = { "RANDFILE" };

/**	Environment variable to inspect for EGD socket name.
*/
static char str_egdsocket[] = { "EGDSOCKET" };

#if 0
static char str_egdpool[] = { "/var/run/egd-pool" };
#endif

/**	Preference key to enable/disable the use of seed files.
*/
static char key_allow_seedfile[] = { "/openssl/allow-random-seed-file" };

/**	Preference key for seed file.
*/
static char key_seedfile[] = { "/openssl/random-seed-file" };

/**	Preference key for EGD socket.
*/
static char key_egdsocket[] = { "/openssl/egd-socket" };

/**	Default seed file name.
*/
static char default_seed_file[] = { "$(user.home)/.rnd" };



#if DK_HAVE_DEV_URANDOM
/**	Random device.
*/
static char str_urandom[] = { "/dev/urandom" };
#endif

#if DK_HAVE_DEV_RANDOM
/**	Random device.
*/
static char str_random[]  = { "/dev/random" };
#endif

#if DK_HAVE_RAND && DK_HAVE_SRAND
/**	File name suffix for rand()/srand() seed files.
*/
static char str_simple_suffix[] = { ".simple" };
#endif

#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
/**	File name suffix for initstate()/setstate()/random() seed files.
*/
static char str_state_suffix[] = { ".isr" };
/**	Internal buffer for initstate()/setstate()/random().
*/
static char random_state_buffer[256];
#endif

#if DK_HAVE_NRAND48
/**	Suffix for nrand48() seed files.
*/
static char str_48_suffix[] = { ".48" };
/**	3 integers for nrand48().
*/
static unsigned short xi[3];
#endif

#if 0
/**	Message asking for keyboard input.
*/
static char default_message_a[] = {
"Some random input from keyboard is needed to seed the PRNG.\n"
"Please hit some keys, press <ENTER> to finish: "
};



/**	Message asking for more keyboard input.
*/
static char default_message_b[] = {
"Some more input is needed: "
};



/**	Warning message if keyboard echo is not disabled.
*/
static char default_message_c[] = {
"Warning: keyboard echo is *not* disabled!"
};
#endif



/**	PRNG names.
*/
static char *prng_strings[] = {
"all", "openssl", "random", "rand48", "rand", NULL
};



/**	Get bytes from keyboard.
	@param	a	Application.
	@param	buf	Result buffer.
	@param	sz	Size of \a buf.
	@param	eo	Flag: Print newline after getting data.
	@return	Number of bytes found.
*/
static
size_t
get_bytes_from_keyboard DK_P4(dk_app_t *,a, unsigned char *,buf, size_t, sz, int,eo)
{
  int cc = 1;
  int is_first = 1;
  size_t back = 0;
  char c, inputline[512], *ptr;
  unsigned char uc, *bufptr;
  unsigned long ul, summary;
  int bytes_in_summary;
  int i;
  
  bytes_in_summary = 0;
  ul = summary = 0UL;
  bufptr = buf;
  while((cc) && (back < sz)) {
    if(is_first) {
      dkapp_stderr_msg_need_random_input(a);
    } else {
      dkapp_stderr_msg_need_more_random_input(a);
    } is_first = 0;
    if(fgets(inputline, sizeof(inputline), stdin)) {
      cc = 0;
      ptr = inputline;
      
      while((*ptr) && (back < sz)) {
	c = *(ptr++);
	if((c != '\n') && (c != '\r')) { cc = 1; }
	uc = (unsigned char)c;
	ul = (unsigned long)uc;
	ul = ul - 32UL; ul = ul % 85UL; ul &= 0x000000FFUL;
	summary = summary * 85UL + ul;
	bytes_in_summary++;
	if(bytes_in_summary >= 5) {
	  for(i = 0; ((i < 4) && (back < sz)); i++) {
	    ul = summary % 256UL;
	    ul &= 0x000000FFUL;
	    uc = (unsigned char)ul;
	    summary = summary / 256UL;
	    *(bufptr++) = uc; back++;
	    if(back >= sz) {
	      cc = 0;
	    }
	  }
	  bytes_in_summary = 0;
	}
      }
      if(bytes_in_summary > 1) {
	  for(i = 0; ((i < (bytes_in_summary - 1)) && (back < sz)); i++) {
	    ul = summary % 256UL;
	    ul &= 0x000000FFUL;
	    uc = (unsigned char)ul;
	    summary = summary / 256UL;
	    *(bufptr++) = uc; back++;
	    if(back >= sz) {
	      cc = 0;
	    }
	  }
	  bytes_in_summary = 0;
      } 
    } else {
      cc = 0;
    }
    if(eo) { fputc('\n', stdout); }
  }
  
  return back;
}



/**	Get seed data from keyboard hits.
	@param	a	Application.
	@param	buf	Result buffer.
	@param	sz	Size of \a buf.
	@return	Number of bytes found.
*/
static
size_t
random_bytes_from_keyboard DK_P3(dk_app_t *,a, unsigned char *,buf, size_t, sz)
{
  size_t back = 0;
  dk_echo_t echodata;
  int echo_off = 0;
  
  if(dksf_echo_test_tty()) {
    if(dksf_echo_save(&echodata)) {
      if(!dksf_echo_off(&echodata)) {
        dkapp_stderr_msg_echo_not_off(a);
      } else {
        echo_off = 1;
      }
      back = get_bytes_from_keyboard(a, buf, sz, echo_off);
      dksf_echo_restore(&echodata);
    } else {
       back = get_bytes_from_keyboard(a, buf, sz, 0);
    }
  } else {
    back = get_bytes_from_keyboard(a, buf, sz, 0);
  } 
  return back;
}



/**	Check ownership and permissions of seed file.
	@param	a	Application.
	@param	fn	File name.
	@return	1 on success, 0 on error.
*/
static
int
check_file_ownership_and_permissions DK_P2(dk_app_t *,a, char *,fn)
{
  int back = 0;
  dk_stat_t *stp;
  int ft;
#if !(DK_HAVE_WINDOWS_H || WIN32)
  int perm1, perm2;
#endif
  
#if DK_HAVE_WINDOWS_H || WIN32
  stp = dkstat_open(fn);
  if(stp) {
    ft = dkstat_filetype(stp);
    ft &= (~(DK_FT_SYMLINK));
    if(ft == DK_FT_REG) {
      back = 1;
    } else {
      /* Warning: Not a regular file */
      dkapp_err_not_a_regular_file(a, fn);
      
    }
    dkstat_close(stp);
  } else {
    /* Warning: File does not exist */
    dkapp_info_file_does_not_exist(a, fn);
    
  }
#else
  long myuid, fileuid;
  stp = dkstat_open(fn);
  if(stp) {
    ft = dkstat_filetype(stp);
    ft &= (~(DK_FT_SYMLINK));
    if(ft == DK_FT_REG) { 
      fileuid = dkstat_uid(stp);
      if(dksf_have_getuid()) {
        myuid = dksf_getuid();
      } else {
        myuid = fileuid;
      }
      if(myuid == fileuid) {
        perm1 = dkstat_permissions(stp);
        perm2 =  (DK_PERM_G_READ | DK_PERM_G_WRITE | DK_PERM_G_EXECUTE);
        perm2 |= (DK_PERM_O_READ | DK_PERM_O_WRITE | DK_PERM_O_EXECUTE);
        if(!(perm1 & perm2)) {
	  back = 1;	
        } else {
          /* Warning: Wrong permissions */
	  dkapp_err_invalid_permissions(a, fn);
	  
        }
      } else {
        /* Warning: Wrong owner */
	dkapp_err_invalid_owner(a, fn);
	
      }
    } else {
      /* Warning: Not a regular file */
      dkapp_err_not_a_regular_file(a, fn);
      
    }
    dkstat_close(stp);
  } else {
    /* Warning: File does not exist */
    dkapp_info_file_does_not_exist(a, fn);
    
  }
#endif
  
  return back;
}



#if DK_HAVE_OPENSSL_RAND_H
/**	Attempt to initialize OpenSSL PRNG from device.
	@param	a	Application.
	@param	dn	Device name.
*/
static
void
attempt_openssl_with_device DK_P2(dk_app_t *,a, char *,dn)
{
  dk_stat_t *stp;
  int can_use_device = 0;
  int ft, res;
  
  stp = dkstat_open(dn);
  if(stp) {
    ft = dkstat_filetype(stp);
    ft &= (~(DK_FT_SYMLINK));
    switch(ft) {
      case DK_FT_CHR: case DK_FT_BLK: {
        can_use_device = 1;
      } break;
    }
    dkstat_close(stp);
  }
  if(can_use_device) {
    res = RAND_load_file(dn, DK_RAND_OSSL_PRNG_SEED_BYTES);
    if(res > 0) {
      if(RAND_status() == 1) {
        (a->random).prng_type = DK_RAND_TYPE_OPENSSL;
      }
    }
  } else {
    /* Warning: Not a device */
    dkapp_err_not_a_device(a, dn);
  }
  
}
#endif



/**	Retrieve random bytes from a random device.
	The data from this function can be used to seed a PRNG.
	@param	a	Application.
	@param	dn	Device name.
	@param	b	Result buffer.
	@param	sz	Size of \a b.
	@return	Number of bytes produced.
*/
static
size_t
bytes_from_device DK_P4(dk_app_t *,a, char *,dn, unsigned char *,b, size_t,sz)
{
  size_t back = 0;
  int can_use_this = 0;
  int ft;
  dk_stat_t *stp;
  FILE *fipo;
  
  stp = dkstat_open(dn);
  if(stp) {
    ft = dkstat_filetype(stp);
    dkstat_close(stp);
    ft &= (~(DK_FT_SYMLINK));
    switch(ft) {
      case DK_FT_BLK: case DK_FT_CHR: { can_use_this = 1; } break;
    }
    if(can_use_this) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
      fipo = fopen64(dn, "rb");
#else
      fipo = fopen(dn, "rb");
#endif
      if(fipo) {
        back = fread((void *)b, 1, sz, fipo);
        fclose(fipo);
      } else {
        /* ERROR: Failed to open */
	dkapp_err_fopenr(a, dn);
      }
    }
  } else {
    /* ERROR: dn does not exist */
    dkapp_info_file_does_not_exist(a, dn);
  } 
  return back;
}



int
dkapp_rand_begin DK_P2(dk_app_t *,a, int,ra)
{
  int back = 0;
  size_t mpl;		/* MAXPATHLEN */
  char *s = NULL;	/* seed file name */
  char *sbuffer = NULL;	/* buffer for seed file name */
#if 0
  char *h = NULL;	/* home directory */
  char *hbuffer = NULL;	/* buffer for home directory */
#endif
#if 0
  char *e = NULL;	/* EGD socket name */
#endif
  int allow_seed_file;	/* allowed to use seed file */
  unsigned char buf[256];	/* buffer for random keyboard data */
  size_t buf_used = 0;		/* number of bytes used in buf */
  char *sa[3], *ptr;
  int res;
  char *x;
  FILE *fipo;
  unsigned int ui;		/* used for srand() */

  
  sa[0] = NULL; sa[1] = NULL; sa[2] = NULL;
  if((a) && (ra)) {

    /* initialization */
    if((a->random).seed_file_name) {
      s = (a->random).seed_file_name;
      dk_delete(s);
      (a->random).seed_file_name = NULL;
      s = NULL;
    }
    (a->random).prng_type = 0;
    mpl = (size_t) dksf_get_maxpathlen();
    allow_seed_file = 1;

    sbuffer = dk_new(char,mpl);
    if(sbuffer) {

      /* check whether seed file use is allowed */
      if(dkapp_get_pref(
        a, key_allow_seedfile, sbuffer, mpl, (~(DK_APP_PREF_EXCL_SYSTEM))
      ))
      {
        ptr = dkstr_start(sbuffer, NULL);
	if(ptr) {
	  dkstr_chomp(ptr, NULL);
	  if(dkstr_is_bool(ptr)) {
	    if(!dkstr_is_on(ptr)) {
	      allow_seed_file = 0;
	    }
	  }
	}
      }

      /* check openssl */
      if(ra & DK_RAND_TYPE_OPENSSL) {
#if DK_HAVE_OPENSSL_RAND_H
	/* seed from file */
	dkapp_debug_attempt_seed_openssl(a);
        if(allow_seed_file) {
	  s = NULL;
	  if(dkapp_get_pref_env(a, key_seedfile, sbuffer, mpl, 0, str_randfile)) {
	    ptr = dkstr_start(sbuffer, NULL);
	    if(ptr) {
	      dkstr_chomp(ptr, NULL);
	      if(dkapp_transform_string_ext1(a,sbuffer,mpl,ptr,1)) {
	        dksf_correct_fnsep(sbuffer);
	        s = sbuffer;
	      }
	    }
	  }
	  if(!(s)) {
	    if(dkapp_transform_string_ext1(a,sbuffer,mpl,default_seed_file,1))
	    {
	      dksf_correct_fnsep(sbuffer);
	      s = sbuffer;
	    }
	  }
	  if(s) {
	    (a->random).seed_file_name = dkstr_dup(s);
	    dkapp_debug_attempt_seed_file(a, s);
	    if(check_file_ownership_and_permissions(a, s)) {
	      res = RAND_load_file(s, -1L);
	      if(RAND_status() == 1) {
	        (a->random).prng_type = DK_RAND_TYPE_OPENSSL;
	      }
	    }
	  }
	}
	/* seed from EGD socket */
	if((a->random).prng_type == 0) {
	  if(dkapp_get_pref_env(a,key_egdsocket,sbuffer,mpl,0,str_egdsocket))
	  {
	    ptr = dkstr_start(sbuffer, NULL);
	    if(ptr) {
	      dkstr_chomp(ptr, NULL);
	      dkapp_debug_attempt_seed_egd(a, ptr);
	      RAND_egd(ptr);
	      if(RAND_status() == 1) {
	        (a->random).prng_type = DK_RAND_TYPE_OPENSSL;
	      }
	    }
	  }
	}
	/* seed from /dev/urandom, /dev/random */
#if DK_HAVE_DEV_URANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_urandom);
	  attempt_openssl_with_device(a, str_urandom);
	}
#endif
#if DK_HAVE_DEV_RANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_random);
	  attempt_openssl_with_device(a, str_random);
	}
#endif
	/* seed from screen */
#ifdef WIN32
        if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_screen(a);
	  RAND_screen();
	  if(RAND_status() == 1) {
	    (a->random).prng_type = DK_RAND_TYPE_OPENSSL;
	  }
	}
#endif
	/* seed from keyboard */
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_kbd(a);
	  buf_used = random_bytes_from_keyboard(a, buf, sizeof(buf));
	  if(buf_used >= 255) {
	    RAND_seed((void *)buf, buf_used);
	    if(RAND_status() == 1) {
	      (a->random).prng_type = DK_RAND_TYPE_OPENSSL;
	    }
	  }
	}
	if((a->random).prng_type == 0) {
	  dkapp_debug_failed_seed_openssl(a);
	}
#else
        
        /* ERROR: OpenSSL not available  */
	dkapp_info_no_openssl_support(a);
#endif
      }

      /* initstate, setstate, random */
      if((a->random).prng_type == 0) {
      if(ra & DK_RAND_TYPE_STATE) {
#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
	dkapp_debug_attempt_seed_random(a);
        if(allow_seed_file) {
	  s = NULL;
	  if(dkapp_get_pref_env(a, key_seedfile, sbuffer, mpl, 0, str_randfile)) {
	    ptr = dkstr_start(sbuffer, NULL);
	    if(ptr) {
	      dkstr_chomp(ptr, NULL);
	      if(dkapp_transform_string_ext1(a,sbuffer,mpl,ptr,1)) {
	        dksf_correct_fnsep(sbuffer);
	        s = sbuffer;
	      }
	    }
	  }
	  if(!(s)) {
	    if(dkapp_transform_string_ext1(a,sbuffer,mpl,default_seed_file,1))
	    {
	      dksf_correct_fnsep(sbuffer);
	      s = sbuffer;
	    }
	  }
	  if(s) {
	    if((strlen(s) + strlen(str_state_suffix)) < mpl) {
	      strcat(s, str_state_suffix);
	      if((a->random).seed_file_name) {
	        x = (a->random).seed_file_name;
		dk_delete(x);
		(a->random).seed_file_name = NULL;
	      }
	      (a->random).seed_file_name = dkstr_dup(s);
	      dkapp_debug_attempt_seed_file(a, s);
	      if(check_file_ownership_and_permissions(a, s)) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
		fipo = fopen64(s, "rb");
#else
	        fipo = fopen(s, "rb");
#endif
		if(fipo) {
		  if(fread((void *)(&ui), sizeof(unsigned int), 1, fipo) == 1)
		  {
		    if(fread((void *)random_state_buffer, 1, 256, fipo) == 256)
		    {
		      initstate(ui, random_state_buffer, 256);
		      setstate(random_state_buffer);
		      (a->random).prng_type = DK_RAND_TYPE_STATE;
		    }
		  }
		  fclose(fipo);
		}
	      }
	    }
	  }
	}
#if DK_HAVE_DEV_URANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_urandom);
	  if(bytes_from_device(
	       a, str_urandom, (void *)(&ui), sizeof(unsigned int))
	     == sizeof(unsigned int)) 
	  {
	    if(bytes_from_device(a, str_urandom, (void *)random_state_buffer, 256)
	       == 256)
	    {
	      initstate(ui, random_state_buffer, 256);
	      setstate(random_state_buffer);
	      (a->random).prng_type = DK_RAND_TYPE_STATE;
	    }
	  }
	}
#endif
#if DK_HAVE_DEV_RANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_random);
	  if(bytes_from_device(
	       a, str_random, (void *)(&ui), sizeof(unsigned int))
	     == sizeof(unsigned int)) 
	  {
	    if(bytes_from_device(a, str_random, (void *)random_state_buffer, 256)
	       == 256)
	    {
	      initstate(ui, random_state_buffer, 256);
	      setstate(random_state_buffer);
	      (a->random).prng_type = DK_RAND_TYPE_STATE;
	    }
	  }
	}
#endif
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_kbd(a);
	  buf_used = random_bytes_from_keyboard(
	    a, (unsigned char *)random_state_buffer, 256
	  );
	  if(buf_used == 256) {
	    buf_used = random_bytes_from_keyboard(
	      a, (unsigned char *)&ui, sizeof(unsigned int)
	    );
	    if(buf_used == sizeof(unsigned int)) {
	      initstate(ui, random_state_buffer, 256);
	      setstate(random_state_buffer);
	      (a->random).prng_type = DK_RAND_TYPE_STATE;
	    }
	  }
	}
        if((a->random).prng_type == 0) {
	  /* DEBUG: Failed to seed initstate/setstate/random */
	  dkapp_debug_failed_seed_random(a);
	}
#else
        /* DEBUG: initstate/setstate/random not available */
	dkapp_debug_unavailable_random(a);
#endif
      }
      }

      /* nrand48 */
      if((a->random).prng_type == 0) {
      if(ra & DK_RAND_TYPE_RAND48) {
#if DK_HAVE_NRAND48
	dkapp_debug_attempt_seed_rand48(a);
        if(allow_seed_file) {
	  s = NULL;
	  if(dkapp_get_pref_env(a, key_seedfile, sbuffer, mpl, 0, str_randfile)) {
	    ptr = dkstr_start(sbuffer, NULL);
	    if(ptr) {
	      dkstr_chomp(ptr, NULL);
	      if(dkapp_transform_string_ext1(a,sbuffer,mpl,ptr,1)) {
	        dksf_correct_fnsep(sbuffer);
		s = sbuffer;
	      }
	    }
	  }
	  if(!(s)) {
	    if(dkapp_transform_string_ext1(a,sbuffer,mpl,default_seed_file,1))
	    {
	      dksf_correct_fnsep(sbuffer);
	      s = sbuffer;
	    }
	  }
	  if(s) {
	    if((strlen(s) + strlen(str_48_suffix)) < mpl) {
	      strcat(s, str_48_suffix);
	      if((a->random).seed_file_name) {
	        x = (a->random).seed_file_name;
		dk_delete(x);
		(a->random).seed_file_name = NULL;
	      }
	      (a->random).seed_file_name = dkstr_dup(s);
	      dkapp_debug_attempt_seed_file(a, s);
	      if(check_file_ownership_and_permissions(a, s)) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
		fipo = fopen64(s, "rb");
#else
	        fipo = fopen(s, "rb");
#endif
		if(fipo) {
		  if(fread((void *)xi, sizeof(unsigned short), 3, fipo) == 3)
		  {
		    (a->random).prng_type = DK_RAND_TYPE_RAND48;
		  }
		  fclose(fipo);
		}
	      }
	    }
	  }
	}
#if DK_HAVE_DEV_URANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_urandom);
	  if(bytes_from_device(
	       a, str_urandom, (void *)xi, 3*sizeof(unsigned short 
	     )) == 3*sizeof(unsigned short))
	  {
	    (a->random).prng_type = DK_RAND_TYPE_RAND48;
	  }
	}
#endif
#if DK_HAVE_DEV_RANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_random);
	  if(bytes_from_device(
	       a, str_random, (void *)xi, 3*sizeof(unsigned short
	     )) == 3*sizeof(unsigned short))
	  {
	    (a->random).prng_type = DK_RAND_TYPE_RAND48;
	  }
	}
#endif
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_kbd(a);
	  buf_used = random_bytes_from_keyboard(
	    a, (unsigned char *)xi, 3*sizeof(unsigned short)
	  );
	  if(buf_used == 3*sizeof(unsigned short)) {
	    (a->random).prng_type = DK_RAND_TYPE_RAND48;
	  }
	}
	if((a->random).prng_type == 0) {
	  /* DEBUG: Failed to seed nrand48 */
	  dkapp_debug_failed_seed_rand48(a);
	}
#else
	/* DEBUG: nrand48 not available */
	dkapp_debug_unavailable_rand48(a);
#endif
      }
      }

      /* rand, srand */
      if((a->random).prng_type == 0) {
      if(ra & DK_RAND_TYPE_SIMPLE) {
#if DK_HAVE_RAND && DK_HAVE_SRAND
	dkapp_debug_attempt_seed_rand(a);
        if(allow_seed_file) {
	  s = NULL;
	  if(dkapp_get_pref_env(a, key_seedfile, sbuffer, mpl, 0, str_randfile)) {
	    ptr = dkstr_start(sbuffer, NULL);
	    if(ptr) {
	      dkstr_chomp(ptr, NULL);
	      if(dkapp_transform_string_ext1(a,sbuffer,mpl,ptr,1)) {
	        dksf_correct_fnsep(sbuffer);
		s = sbuffer;
	      }
	    }
	  }
	  if(!(s)) {
	    if(dkapp_transform_string_ext1(a,sbuffer,mpl,default_seed_file,1))
	    {
	      dksf_correct_fnsep(sbuffer);
	      s = sbuffer;
	    }
	  }
	  if(s) {
	    if((strlen(s) + strlen(str_simple_suffix)) < mpl) {
	      strcat(s, str_simple_suffix);
	      if((a->random).seed_file_name) {
	        x = (a->random).seed_file_name;
		dk_delete(x);
		(a->random).seed_file_name = NULL;
	      }
	      (a->random).seed_file_name = dkstr_dup(s);
	      dkapp_debug_attempt_seed_file(a, s);
	      if(check_file_ownership_and_permissions(a, s)) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
		fipo = fopen64(s, "rb");
#else
	        fipo = fopen(s, "rb");
#endif
		if(fipo) {
		  if(fread((void *)(&ui), sizeof(unsigned int), 1, fipo) == 1)
		  {
		    srand(ui);
		    (a->random).prng_type = DK_RAND_TYPE_SIMPLE;
		  }
		  fclose(fipo);
		}
	      }
	    }
	  }
	}
#if DK_HAVE_DEV_URANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_urandom);
	  if(bytes_from_device(
	       a, str_urandom, (void *)(&ui), sizeof(unsigned int
	     )) == sizeof(unsigned int))
	  {
	    srand(ui);
	    (a->random).prng_type = DK_RAND_TYPE_SIMPLE;
	  }
	}
#endif
#if DK_HAVE_DEV_RANDOM
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_device(a, str_random);
	  if(bytes_from_device(
	       a, str_random, (void *)(&ui), sizeof(unsigned int
	     )) == sizeof(unsigned int))
	  {
	    srand(ui);
	    (a->random).prng_type = DK_RAND_TYPE_SIMPLE;
	  }
	}
#endif
	if((a->random).prng_type == 0) {
	  dkapp_debug_attempt_seed_kbd(a);
	  buf_used = random_bytes_from_keyboard(
	    a, (unsigned char *)&ui, sizeof(unsigned int)
	  );
	  if(buf_used == sizeof(unsigned int)) {
	    srand(ui);
	    (a->random).prng_type = DK_RAND_TYPE_SIMPLE;
	  }
	}
	if((a->random).prng_type == 0) {
	 /* DEBUG: Failed to seed rand() */
	 dkapp_debug_failed_seed_rand(a);
	}
#else
	/* DEBUG: rand() not available */
	dkapp_debug_unavailable_rand(a);
#endif
      }
      }
  
    } else {	/* if(sbuffer) */
      dkapp_err_memory(a, 1, mpl);
    }		/* else if(sbuffer) */

  }

  if(sbuffer) {
    dk_delete(sbuffer);
  }
  back = (a->random).prng_type;
  if(back) {
    dkapp_debug_prng_seeded_successfully(a);
    if(ra & DK_RAND_TYPE_CRYPTO) {
      if(!(back & DK_RAND_TYPE_CRYPTO)) {
        /* Warning: The PRNG is not crypto strong */
	dkapp_warn_prng_not_crypto(a);
      }
    }
  } else {
    /* ERROR: No usable PRNG found */
    dkapp_err_no_prng_found(a);
  }
  
  return back;
}



#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
/**	Retrieve bytes from the initstate()/setstate()/random() function set.
	@param	ucb	Destination buffer.
	@param	sz	Size of \a ucb.
	@return	Number of bytes produced.
*/
static
size_t
bytes_state DK_P2(unsigned char *,ucb, size_t,sz)
{
  unsigned char *ptr;
  size_t i, bytes_taken;
  long ul;

  ptr = ucb;
  ul = (unsigned long)random();
  bytes_taken = 0;
  for(i = 0; i < sz; i++) {
    *(ptr++) = (unsigned char)(ul & 0x000000FFUL);
    ul = (ul >> 8);
    bytes_taken++;
    if(bytes_taken >= sizeof(unsigned long)) {
      bytes_taken = 0;
      ul = (unsigned long)random();
    }
  }
  return sz;
}
#endif



#if DK_HAVE_NRAND48
/**	Retrieve bytes from the nrand48() function.
	@param	ucb	Destination buffer.
	@param	sz	Size of \a ucb.
	@return	Number of bytes produced.
*/
static
size_t
bytes_48 DK_P2(unsigned char *,ucb, size_t,sz)
{
  size_t back, i, position;
  unsigned char *ptr;
  long lval;

  ptr = ucb;
  lval = nrand48(xi);
  lval = lval >> 8;
  position = 0;
  for(i = 0; i < sz; i++) {
    *(ptr++) = (unsigned char)((lval) & 0xFF);
    if(position >= sizeof(long) - 3) {
      lval = nrand48(xi);
      lval = lval >> 8;
      position = 0;
    } else {
      position++;
      lval = lval >> 8;
    }
  }
  back = sz;
  return back;
}
#endif



#if DK_HAVE_RAND && DK_HAVE_SRAND
/**	Retrieve random bytes from the simple random generator.
	@param	ucb	Destination buffer.
	@param	sz	Size of \a ucb.
	@return	Number of bytes produced.
*/
static
size_t
bytes_simple DK_P2(unsigned char *,ucb, size_t,sz)
{
#if !SLOW_RAND
  size_t back, i, shiftbits;
  unsigned char *ptr;
  back = sz;
  ptr = ucb;
  shiftbits = (sizeof(int)-1) * 8 - 1;
  for(i = 0; i < back; i++) {
    *(ptr++) = (unsigned char)((rand() >> shiftbits) & 0xFF);
  }
  return back;
#else
  /* as in the Linux rand() man-page */
  size_t back, i;
  unsigned char *ptr;

  back = sz;
  ptr = ucb;
  for(i = 0; i < back; i++) {
    *(ptr++) =
    (unsigned char)(((int)floor(256.0*rand()/(double)RAND_MAX + 1.0)) & 0xFF);
  }
  return back;
#endif
}
#endif



size_t
dkapp_rand_bytes DK_P3(dk_app_t *,a, void *,b, size_t,sz)
{
  size_t back = 0;
#if DK_HAVE_OPENSSL_RAND_H
  int v;
#endif
  
  if((a) && (b) && (sz)) {
    switch((a->random).prng_type) {
      case DK_RAND_TYPE_OPENSSL: {
#if DK_HAVE_OPENSSL_RAND_H
	
	v = RAND_bytes((unsigned char *)b, (int)sz);
	if(v == 1) {
	  back = sz;
	}
#endif
      } break;
      case DK_RAND_TYPE_STATE: {
#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
	
        back = bytes_state((unsigned char *)b, sz);
#endif
      } break;
      case DK_RAND_TYPE_RAND48: {
#if DK_HAVE_NRAND48
	back = bytes_48((unsigned char *)b, sz);
#endif
      } break;
      case DK_RAND_TYPE_SIMPLE: {
#if DK_HAVE_RAND && DK_HAVE_SRAND
	
        back = bytes_simple((unsigned char *)b, sz);
#endif
      } break;
    }
  }
  
  return back;
}



size_t
dkapp_rand_bytes_non_crypto DK_P3(dk_app_t *,a, void *,b, size_t,sz)
{
  size_t back = 0;
#if DK_HAVE_OPENSSL_RAND_H
  int v;
#endif
  
  if((a) && (b) && (sz)) {
    switch((a->random).prng_type) {
      case DK_RAND_TYPE_OPENSSL: {
#if DK_HAVE_OPENSSL_RAND_H
	v = RAND_pseudo_bytes((unsigned char *)b, (int)sz);
	if(v != -1) {
	  back = sz;
	}
#endif
      } break;
      case DK_RAND_TYPE_STATE: {
#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
#endif
      } break;
      case DK_RAND_TYPE_RAND48: {
#if DK_HAVE_NRAND48
	back = bytes_48((unsigned char *)b, sz);
#endif
      } break;
      case DK_RAND_TYPE_SIMPLE: {
#if DK_HAVE_RAND && DK_HAVE_SRAND
        back = bytes_simple((unsigned char *)b, sz);
#endif
      } break;
    }
  }
  
  return back;
}



/**	Ensure correct permissions for a file.
	The file permissions are set to 0600, the file will be created
	if it does not yet exist.
	@param	a	Application.
	@param	fn	File name.
*/
static
void
create_permissions DK_P2(dk_app_t *,a, char *,fn)
{
  FILE *fipo;
  fipo = dkapp_fopen(a, fn, "w");
  if(fipo) {
    fputc('\n', fipo);
    fclose(fipo);
  }
  dksf_chmod(fn, (DK_PERM_U_READ | DK_PERM_U_WRITE));
}



int
dkapp_rand_end DK_P1(dk_app_t *,a)
{
  int back = 0;
  FILE *fipo;		/* for saving random seed to file */
  unsigned int ui;	/* used to save random seed */
  char *x;		/* used to delete (a->random).seed_file_name */
  
  if(a) {
    switch((a->random).prng_type) {
      case DK_RAND_TYPE_OPENSSL: {
        if((a->random).seed_file_name) {
#if DK_HAVE_OPENSSL_RAND_H
	  char *x;
	  create_permissions(a, (a->random).seed_file_name);
	  RAND_write_file((a->random).seed_file_name);
	  x = (a->random).seed_file_name;
	  dk_delete(x); (a->random).seed_file_name = NULL;
	  (a->random).prng_type = 0;
	  back = 1;
#endif
	}
      } break;

      case DK_RAND_TYPE_STATE: {
#if DK_HAVE_INITSTATE && DK_HAVE_SETSTATE && DK_HAVE_RANDOM
        if((a->random).seed_file_name) {
	  unsigned int ui;
	  create_permissions(a, (a->random).seed_file_name);
          if(dkapp_rand_bytes(a, (void *)(&ui), sizeof(unsigned int))
	     == sizeof(unsigned int))
	  {
	    fipo = dkapp_fopen(a, (a->random).seed_file_name, "wb");
	    if(fipo) {
	      if(fwrite((void *)(&ui), sizeof(unsigned int), 1, fipo) == 1) {
	        if(fwrite((void *)random_state_buffer, 1, 256, fipo) == 256) {
		  back = 1;
		}
	      }
	      fclose(fipo);
	    }
	  }
	}
#endif
      } break;

      case DK_RAND_TYPE_RAND48: {
#if DK_HAVE_NRAND48
        if((a->random).seed_file_name) {
	  create_permissions(a, (a->random).seed_file_name);

	  fipo = dkapp_fopen(a, (a->random).seed_file_name, "wb");
	  if(fipo) {
#if DK_HAVE_FILENO && DK_HAVE_FCHMOD
	    dksf_fchmod(fileno(fipo), (DK_PERM_U_READ | DK_PERM_U_WRITE));
	    
#endif
	    if(fwrite((void *)xi, sizeof(unsigned short), 3, fipo) == 3)
	    {
	        back = 1;
	    }
	    fclose(fipo);
	  }
	  x = (a->random).seed_file_name;
	  dk_delete(x);
	  (a->random).seed_file_name = NULL;
	  (a->random).prng_type = 0;
	}
#endif
      } break;

      case DK_RAND_TYPE_SIMPLE: {
#if DK_HAVE_RAND && DK_HAVE_SRAND
        if((a->random).seed_file_name) {
	  create_permissions(a, (a->random).seed_file_name);
	  fipo = dkapp_fopen(a, (a->random).seed_file_name, "wb");
	  if(fipo) {
#if DK_HAVE_FILENO && DK_HAVE_FCHMOD
	    dksf_fchmod(fileno(fipo), (DK_PERM_U_READ | DK_PERM_U_WRITE));
	    
#endif
	    if(bytes_simple((unsigned char *)(&ui), sizeof(unsigned int))
	       == sizeof(unsigned int))
	    {
	      if(fwrite((void *)(&ui), sizeof(unsigned int), 1, fipo) == 1)
	      {
	        back = 1;
	      }
	    }
	    fclose(fipo);
	  }
	  x = (a->random).seed_file_name;
	  dk_delete(x);
	  (a->random).seed_file_name = NULL;
	  (a->random).prng_type = 0;
	}
#endif
      } break;

    }
  }
  
  return back;
}



int
dkapp_rand_types_from_string DK_P2(dk_app_t *,a, char *,s)
{
  int back = 0;
  char *p1, *p2;
  int i;
  
  p1 = s;
  while(p1) {		
    p2 = dkstr_next(p1, comma);
    p1 = dkstr_start(p1, NULL);
    if(p1) {		
      dkstr_chomp(p1, NULL);
      i = dkstr_array_index(prng_strings, p1, 0);
      if(i >= 0) {	
        switch(i) {
	  case 0: {
	    back |= DK_RAND_TYPE_ALL;
	  } break;
	  case 1: {
	    back |= DK_RAND_TYPE_OPENSSL;
	  } break;
	  case 2: {
	    back |= DK_RAND_TYPE_STATE;
	  } break;
	  case 3: {
	    back |= DK_RAND_TYPE_RAND48;
	  } break;
	  case 4: {
	    back |= DK_RAND_TYPE_SIMPLE;
	  } break;
	}
      } else {		
	dkapp_warn_unknown_prng(a, p1);
      }
    }
    p1 = p2;
  }
  if(back & DK_RAND_TYPE_OPENSSL) {
#if !DK_HAVE_OPENSSL_RAND_H
    back &= (~(DK_RAND_TYPE_OPENSSL));
    dkapp_debug_unavailable_openssl(a);
#endif
  }
  if(back & DK_RAND_TYPE_STATE) {
#if !DK_HAVE_RANDOM
    back &= (~(DK_RAND_TYPE_STATE));
    dkapp_debug_unavailable_random(a);
#endif
  }
  if(back & DK_RAND_TYPE_RAND48) {
#if !DK_HAVE_NRAND48
    back &= (~(DK_RAND_TYPE_RAND48));
    dkapp_debug_unavailable_rand48(a);
#endif
  }
  if(back & DK_RAND_TYPE_SIMPLE) {
#if !DK_HAVE_RAND
    back &= (~(DK_RAND_TYPE_SIMPLE));
    dkapp_debug_unavailable_rand(a);
#endif
  }
  if(!back) {	
    /* ERROR: No PRNG supported */
  } 
  return back;
}



