/****************************************************************************
 *									    *
 *									    *
 *   VMS Randomness-Gathering Code					    *
 *									    *
 *									    *
 ****************************************************************************/

/* This module is part of the cryptlib continuously seeded pseudorandom
   number generator.  For usage conditions, see lib_rand.c

   [Here is the notice from lib_rand.c:]

   This module and the misc/rnd*.c modules represent the cryptlib
   continuously seeded pseudorandom number generator (CSPRNG) as described in
   my 1998 Usenix Security Symposium paper "The generation of random numbers
   for cryptographic purposes".

   The CSPRNG code is copyright Peter Gutmann (and various others) 1996,
   1997, 1998, 1999, all rights reserved.  Redistribution of the CSPRNG
   modules and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice
      and this permission notice in its entirety.

   2. Redistributions in binary form must reproduce the copyright notice in
      the documentation and/or other materials provided with the distribution.

   3. A copy of any bugfixes or enhancements made must be provided to the
      author, <pgut001@cs.auckland.ac.nz> to allow them to be added to the
      baseline version of the code.

  ALTERNATIVELY, the code may be distributed under the terms of the GNU
  General Public License, version 2 or any later version published by the
  Free Software Foundation, in which case the provisions of the GNU GPL are
  required INSTEAD OF the above restrictions.

  Although not required under the terms of the GPL, it would still be nice if
  you could make any changes available to the author to allow a consistent
  code base to be maintained */



/* General includes */

#include <config.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <cmbdef.h>
#include <descrip.h>
#include <lib$routines.h>
#include <psldef.h>
#include <starlet.h>
#include <stsdef.h>


#include "types.h"  /* for byte and u32 typedefs */
#include "algorithms.h"
#include "util.h"


/* The structure containing information on random-data sources.  Each
 * record contains the source and a relative estimate of its usefulness
 * (weighting) which is used to scale the number of kB of output from the
 * source (total = data_bytes / usefulness).  Usually the weighting is in the
 * range 1-3 (or 0 for especially useless sources), resulting in a usefulness
 * rating of 1...3 for each kB of source output (or 0 for the useless
 * sources).
 *
 * If the source is constantly changing (certain types of network statistics
 * have this characteristic) but the amount of output is small, the weighting
 * is given as a negative value to indicate that the output should be treated
 * as if a minimum of 1K of output had been obtained.  If the source produces
 * a lot of output then the scale factor is fractional, resulting in a
 * usefulness rating of < 1 for each kB of source output.
 *
 * In order to provide enough randomness to satisfy the requirements for a
 * slow poll, we need to accumulate at least 20 points of usefulness (a
 * typical system should get about 30 points).
 *
 *
 * In order to maximise use of the buffer, the code performs a form of run-
 * length compression on its input where a repeated sequence of bytes is
 * replaced by the occurrence count mod 256.  Some commands output an awful
 * lot of whitespace, this measure greatly increases the amount of data we
 * can fit in the buffer.
 *
 * When we scale the weighting using the SC() macro, some preprocessors may
 * give a division by zero warning for the most obvious expression
 * 'weight ? 1024 / weight : 0' (and gcc 2.7.2.2 dies with a division by zero
 * trap), so we define a value SC_0 which evaluates to zero when fed to
 * '1024 / SC_0' */

#define SC( weight )	( 1024 / weight )	/* Scale factor */
#define SC_0			16384	/* SC( SC_0 ) evalutes to 0 */


#define G_BUFSIZE 32767         /* Size of command-output buffer. */


static struct RI {
    const char *cmd;            /* Command */
    const int usefulness;       /* Usefulness of source */
    int length;                 /* Quantity of output produced */
} dataSources[] = {

    { "show system", SC(1), 0 },
    { "show device /full d", SC(1), 0 },
    { "show network /full", SC(2), 0 },
    {  NULL, 0, 0 }
};

/* Buffer for gathering "random" noise. */

static char g_buffer[ G_BUFSIZE+ 1024];
static int g_byte_count = 0;
static int g_byte_used_count = 0;

static int src_ndx = 0;          /* Next command to spawn. */
static int spwn_pid;
static char lnm_mbx[ 32];
static struct dsc$descriptor_s cmd_dscr =
 { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, (char *) NULL };

static $DESCRIPTOR( lnm_mbx_dscr, lnm_mbx);
static $DESCRIPTOR( nla0_dscr, "NLA0:");


static short chan_mbx = 0;
static int fd_mbx = -1;
static char *dbgfn;
static FILE *dbgfp = NULL;

#define GR_MIN( a, b) ((a > b) ? b : a)


/*    add:       function to transfer random bytes generated here
 *               to the consumer's buffer.
 *    requester: passed through to add() as "source".
 *    length:    bytes requested?
 *    level:     source code.  (See random.c: add_randomness().)
 */

#define MBX_BASE_NAME "MBX_GNUPG_ENTROPY_"

int
rndvms_gather_random( void (*add)(const void*, size_t, int),
                      int requester, size_t length, int level )
{
    char *gbp1;
    char *gbp2;
    char *gbpe;
    char gbc;
    int gbc_count;
    int n;
    int sts;
    int spwn_flgs = 1;                  /* NOWAIT. */

/****************
 * Using a level of 0 should never block and better add nothing
 * to the pool.  So this is just a dummy for this gatherer.
 */
    if (level == 0)
        return 0;


    /* Create the mailbox (once for the process). */
    if (chan_mbx == 0)
    {
        /* Open debug file, if specified. */
        dbgfn = getenv("GNUPG_RND_DEBUG");
        if (dbgfn != NULL)
        {
            if (strcmp( dbgfn, "-") == 0)
            {
                dbgfp = stdout;
            }
            else
            {
                dbgfp = fopen( dbgfn, "w");
                if (dbgfp == NULL)
                {
                    g10_log_info(
                     "rndvms()  Can't open (write) rnd debug file \"%s\".\n",
                     dbgfn);
                    g10_log_info( " %s\n", strerror( errno));
                }
            }
        }
    
        /* Form the (process-unique) mailbox logical name. */
        sprintf( lnm_mbx, "%s%08x", MBX_BASE_NAME, getpid());
        lnm_mbx_dscr.dsc$w_length = sizeof MBX_BASE_NAME- 1+ 8;

        /* If target mailbox already exists (left-over), read/waste any
           pending data, and close and delete the old mailbox.
        */
        fd_mbx = open( lnm_mbx, O_RDONLY, 0);
        if (fd_mbx >= 0)
        {
            if (dbgfp)
            {
                fprintf( dbgfp,
                 "rndvms()  Process mailbox (%s) unexpectedly exists.\n",
                 lnm_mbx);
                fprintf( dbgfp, "rndvms()  Wasting data ...");
                fflush( dbgfp);
            }

            while (read( fd_mbx, g_buffer, 1024));
            close( fd_mbx);
            if (dbgfp)
            {
                fprintf( dbgfp, " done.\n");
                fflush( dbgfp);
            }
        }

        sts = sys$crembx( 0,       	    /* Temporary mailbox. */
                          &chan_mbx,        /* Channel. */
                          0,                /* Max msg size (default). */
                          0,                /* Msg buf quota (default). */
                          0x00f0,           /* Prot = O:LPWR. */
                          PSL$C_USER,       /* Access mode, */
                          &lnm_mbx_dscr,    /* Logical name. */
                          CMB$M_READONLY,   /* Flags. */
                          0);               /* Reserved. */

        if (dbgfp)
        {
            fprintf( dbgfp,
             "rndvms()  Create process mailbox (%s).  sts = %%x%08x .\n",
             lnm_mbx, sts);
            fflush( dbgfp);
        }

        if ((sts& STS$M_SEVERITY) != STS$K_SUCCESS)
        {
            errno = EVMSERR;
            vaxc$errno = sts;
            return -1;
        }
    }

    if (dbgfp)
    {
        fprintf( dbgfp,
         "rndvms() req = %d, len = %d, lev = %d.  Bytes avail = %d\n",
         requester, length, level, (g_byte_count- g_byte_used_count));
        fflush( dbgfp);
    }

    /* While more data remain to be supplied, supply them. */
    while (length > 0)
    {
        if (g_byte_count > g_byte_used_count)
        {
            /* Data are available (left-over?).  Use them (first). */
            n = GR_MIN( length, (g_byte_count- g_byte_used_count));

            /* Call the consumer's buffer-stuffer. */
            if (dbgfp)
            {
                fprintf( dbgfp,
                 "rndvms()  Adding %d \"random\" bytes.\n", n);
                fflush( dbgfp);
            }

            (*add)( &g_buffer[ g_byte_used_count], n, requester );

            g_byte_used_count += n;
            length -= n;
        }
        else
        {
            /* Need more data.  Reset byte counts and buffer pointer. */
            g_byte_count = 0;
            g_byte_used_count = 0;
            gbp1 = g_buffer;

            if (fd_mbx > 0)
            {
                /* Mailbox still open.  Re-stock the gather buffer. */
                while ((fd_mbx > 0) && (g_byte_count < G_BUFSIZE))
                {
                    /* Read data while there are more data to read,
                       and space is available in the buffer.
                    */
                    while (((sts = read( fd_mbx,
                     &g_buffer[ g_byte_count],          /* gbp1 */
                     (G_BUFSIZE- g_byte_count))) > 0) &&
                     (G_BUFSIZE- g_byte_count > 0))
                    {
                        gbc_count = sts;
                        /* Strip off the terminal newline character. */
                        if (g_buffer[ g_byte_count+ gbc_count- 1] == '\n')
                        {
                            gbc_count--;
                        }

                        /* Collapse repeated characters to a byte count
                           (mod 256).  gbp1 = dest, gbp2 = source.
                        */
                        gbp2 = gbp1;
                        gbpe = gbp2+ gbc_count;
                        while (gbp2 < gbpe)
                        {
                            gbc = *gbp2;
                            if (gbc != *(gbp2+ 1))
                            {
                                /* Next byte differs.  Use this one. */
                                *(gbp1++) = *(gbp2++);
                            }
                            else
                            {
                                gbc_count = 0;
                                while ((*gbp2 == gbc) && (gbp2 < gbpe))
                                {
                                    gbc_count++;
                                    gbp2++;
                                }
                                *(gbp1++) = gbc_count;
                            }
                        }
                        g_byte_count = gbp1- g_buffer;
                    }
                    /* If the current data source is exhausted,
                       close the mailbox.
                    */
                    if (sts <= 0)
                    {
                        sts = close( fd_mbx);
                        fd_mbx = 0;
                    }
                }
            }
            else
            {
                /* Mailbox closed.  Set the command descriptor to the
                   next command.
                */
                cmd_dscr.dsc$a_pointer = (char *) dataSources[ src_ndx].cmd;
                cmd_dscr.dsc$w_length = strlen( cmd_dscr.dsc$a_pointer);


                /* Run the next command, and open the mailbox. */
                if (dbgfp)
                {
                    fprintf( dbgfp,
                     "rndvms()  Spawning: %.*s\n",
                     cmd_dscr.dsc$w_length, cmd_dscr.dsc$a_pointer);
                    fflush( dbgfp);
                }

                sts = lib$spawn( &cmd_dscr,     /* Command. */
                                 &nla0_dscr,    /* SYS$INPUT */
                                 &lnm_mbx_dscr, /* SYS$OUTPUT */
                                 &spwn_flgs,    /* Flags.  1 = NOWAIT. */
                                 0,             /* ??? */
                                 &spwn_pid,     /* Process ID. */
                                 0,
                                 0,
                                 0,
                                 0);

                if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
                {
                    fd_mbx = open( lnm_mbx, O_RDONLY, 0);
                    if (fd_mbx < 0)
                    {
                        return -1;
                    }
                    src_ndx++;
                    if (dataSources[ src_ndx].cmd == NULL)
                    {
                        /* Command list exhausted.  Start over. */
                        src_ndx = 0;
                    }
                }
                else
                {
                    errno = EVMSERR;
                    vaxc$errno = sts;
                    return -1;
                }
            }
        }
    }
    return 0;
}


#if 0

	/* One of the sources has data available, read it into the buffer */
	for (i = 0; dataSources[i].path != NULL; i++) {
	    if( dataSources[i].pipe && FD_ISSET(dataSources[i].pipeFD, &fds)) {
		size_t noBytes;

		if ((noBytes = fread(gather_buffer + bufPos, 1,
				     gather_buffer_size - bufPos,
				     dataSources[i].pipe)) == 0) {
		    if (my_pclose(&dataSources[i]) == 0) {
			int total = 0;

			/* Try and estimate how much entropy we're getting
			 * from a data source */
			if (dataSources[i].usefulness) {
			    if (dataSources[i].usefulness < 0)
				total = (dataSources[i].length + 999)
					/ -dataSources[i].usefulness;
			    else
				total = dataSources[i].length
					/ dataSources[i].usefulness;
			}
			if( dbgfp )
			    fprintf(dbgfp,
			       "%s %s contributed %d bytes, "
			       "usefulness = %d\n", dataSources[i].path,
			       (dataSources[i].arg != NULL) ?
				       dataSources[i].arg : "",
				      dataSources[i].length, total);
			if( dataSources[i].length )
			    usefulness += total;
		    }
		    dataSources[i].pipe = NULL;
		}
		else {
		    int currPos = bufPos;
		    int endPos = bufPos + noBytes;

		    /* Run-length compress the input byte sequence */
		    while (currPos < endPos) {
			int ch = gather_buffer[currPos];

			/* If it's a single byte, just copy it over */
			if (ch != gather_buffer[currPos + 1]) {
			    gather_buffer[bufPos++] = ch;
			    currPos++;
			}
			else {
			    int count = 0;

			    /* It's a run of repeated bytes, replace them
			     * with the byte count mod 256 */
			    while ((ch == gather_buffer[currPos])
				    && currPos < endPos) {
				count++;
				currPos++;
			    }
			    gather_buffer[bufPos++] = count;
			    noBytes -= count - 1;
			}
		    }

		    /* Remember the number of (compressed) bytes of input we
		     * obtained */
		    dataSources[i].length += noBytes;
		}
	    }
	}

	/* Check if there is more input available on any of the sources */
	moreSources = 0;
	FD_ZERO(&fds);
	for (i = 0; dataSources[i].path != NULL; i++) {
	    if (dataSources[i].pipe != NULL) {
		FD_SET(dataSources[i].pipeFD, &fds);
		moreSources = 1;
	    }
	}
    }

    if( dbgfp ) {
	fprintf(dbgfp, "Got %d bytes, usefulness = %d\n", bufPos, usefulness);
	fflush(dbgfp);
    }
    *nbytes = bufPos;
    return usefulness;
}

#endif /* 0 */

