

/* noiz.c
 *
 *  Adapted for Amiga computers by C.R. 'Magius' Hahn from noiz-0.5 by
 *  Henry Strickland <strick@yak.net>.  MD5 implementation by Colin Plumb
 *
 *  This is essentially an integrated form of noiz-0.5 for Amiga personal
 *  computers running OS 2.0 or greater
 *
 */

/*  Usage: NOIZ [FILE <noizfile>] [TO <file>] [STIR] [ASCII] [INIT]
 *  If 'TO' keyword is not given, standard output is used, and output
 *  is ASCII, regardless of the absence of the 'ASCII' switch
 *
 *  FILE keyword specifies noiz state file to use.  If not given,
 *      default is "s:noiz".  If the file does not exist, it is
 *      created and initialized as per INIT switch described below.
 *      The environment variable, NOIZFILE, is also recognized, and
 *      overrides the default, but is overridden by the FILE keyword.
 *  TO keyword specifies output file
 *  STIR invokes the noizstir function
 *  ASCII switch causes output to be produced in human-readable form.
 *  INIT switch causes noise state file to be created and initialized
 *      before any other action is performed.  If the state file already
 *      exists, a warning is printed, and the file is left unchanged.
 *      NOTE: As the state file will be automatically created if it does
 *      not exist, this switch is not really necessary.
 *
 *  Examples:
 *  NOIZ
 *      prints 16 bytes of noise, in ASCII.
 *      equivalent to: noizspin | noizout | od
 *  NOIZ STIR
 *      'stirs' the noise state file.
 *      equivalent to: noizspin | noizstir
 *  NOIZ TO outfile STIR
 *      'stirs' the noise state file and writes 16 bytes of noise to
 *      outfile.
 *      equivalent to: noizspin | noizstir
 *                     noizspin | noizout >outfile
 *  NOIZ TO outfile ASCII
 *      prints 16 bytes of noise, in ASCII, to outfile.
 *      equivalent to: noizspin | noizout | od >outfile
 *  NOIZ FILE=sys:t/noizyfile INIT
 *      Creates and initializes sys:t/noizyfile, provided it is not
 *      already present.
 *      equivalent to: noizinit
 *          (but acts on sys:t/noizyfile instead of /etc/noiz)
 *
 */

#include <exec/types.h>
#include <exec/alerts.h>
#include <dos/dos.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <time.h>
#include "md5.h"

/* ARG numbers - we use these to index into Args[] (see below) */
#define ARG_FILE    0
#define ARG_TO      1
#define ARG_STIR    2
#define ARG_ASCII   3
#define ARG_INIT    4

/* standard AmigaDOS version string */
/* Note that the correct form for the date is (dd.mm.yy) */
UBYTE *VerS = "$VER: NOIZ 0.5 (11.01.96) by Henry Strickland <strick@yak.net>  Amiga version by C.R. 'Magius' Hahn";

/* Command-line template, used by ReadArgs system call */
UBYTE *Template = "FILE/K,TO/K,STIR/S,ASCII/S,INIT/S";

struct RDargs *rda=NULL;    /* 'placeholder' used by ReadArgs */
UBYTE *Args[5] = {          /* ReadArgs fills this in for us */
    NULL,   /* FILE/K keyword arg */
    NULL,   /* TO/K keywork arg */
    NULL,   /* STIR/S switch arg */
    NULL,   /* ASCII/S switch arg */
    NULL    /* INIT/S switch arg */
};

/* The following two BOOL's are used instead of their */
/* equivalents in Args[] to implement some rather */
/* obscure behavior (see docs) */
BOOL Produce_Output = FALSE;
BOOL ASCII_Output = FALSE;

/* What's the name of the noise state file? */
UBYTE *StateFile = "S:noiz";
UBYTE sfBuf[80];

/* Exec Library base pointer - this is set up by the starup code, */
/* but we need to be able to examine it to determine what version */
/* of the OS we are running under */
extern struct Library *SysBase;


void safecleanup(void);
BOOL statefile_exists(void);
BOOL init_statefile(void);

int main(void)
{

    /* First, make sure we are running under release 2.0 (v36) or */
    /* better, because ReadArgs is not present on earlier systems */
    /* (also, we want to use buffered IO FGets, FPutc, and so on */
    /* which are not present pre-v36 */
    if(SysBase->lib_Version < 36)
    {
        /* OS is too primitive for us, tell user to get a life. */
        /* Write is used here to avoid having to bring in */
        /* stdio from c.lib - makes the prog smaller */
        Write(Output(),"AmigaOS release 2.0 or higher required.\n",40);
        return(RETURN_FAIL);
    }


    /* great, now we register our 'cleanup' function with atexit, so */
    /* we don't have to worry about freeing resources if we have to */
    /* bail out later */
    if(atexit(safecleanup))
    {
        /* oh-oh, this should never happen - if it does, the */
        /* machine is probably so screwed up it'll never be */
        /* able to execute any code we might put here.. */
        /* The system call, Alert(), probably has the best */
        /* shot here, so we'll try that..  Since we don't */
        /* know what actually caused us to get screwed up */
        /* we'll pass AN_Unknown as an alert code */
        Alert(AN_Unknown);
        return(RETURN_FAIL);
    }

    rda = ReadArgs(Template, Args, NULL);
    if(!rda)
    {
        /* Again, this should not happen, */
        Alert(AN_Unknown);
        return(RETURN_FAIL);
    }

    /* Now, set up our additional globals */
    /* produce output only if neither of INIT or STIR are given, OR */
    /* if output is implicitly enabled via TO or ASCII */
    Produce_Output = (!(Args[ARG_STIR] || Args[ARG_INIT])) || (Args[ARG_TO] || Args[ARG_ASCII]);
    ASCII_Output = Args[ARG_ASCII] || (!Args[ARG_TO]);

    /* Now decide where the noise state file can be found.  This */
    /* would be /etc/noiz under UNIX, but we default to S:noiz */
    /* We also allow the user to specify a different file via the */
    /* command line, or an environment variable */
    if(Args[ARG_FILE]) StateFile = Args[ARG_FILE];
    else
    {
        BPTR sfile;

        sfile = Open("ENV:NOIZFILE",MODE_OLDFILE);
        if(sfile)
        {
            FGets(sfile,sfBuf,80);
            StateFile = sfBuf;
            Close(sfile);
        }
    }


    if(statefile_exists())
    {
        if(Args[ARG_INIT])
        {
            /* Rather that print our own error message here, I */
            /* have DOS perform the task, since DOS (2.1 or better) */
            /* knows how to print an error message in the user's */
            /* preferred language.   *Objekt existiert bereits* */
            PrintFault(ERROR_OBJECT_EXISTS,StateFile);
        }
    }
    else
    {
        /* If the statefile doesn't exist, create and initialize it. */
        if(!init_statefile())
        {
            /* DAMN.. can't create statefile - maybe the user */
            /* gave a faulty filename.  Let DOS handle this */
            PrintFault(IoErr(),StateFile);
            return(RETURN_FAIL);
        }
    }

    if(Args[ARG_STIR])
    {
        BYTE noiz[256];
        BYTE pad[256];
        UBYTE in_hash[16];
        UBYTE out_hash[16];

        struct MD5Context context;
        int e,i,seconds,row;
        long pid;
        long ppid;
        BPTR fh;


        /* Seed the c.lib's pseudorandom number generator */
        /* which we use because we don't really have */
        /* anything better (this replaces noizspin) */
        srand(time(NULL)^(ULONG)FindTask(NULL));

        /* This stuff is basically the same as the original noizstir */
        /* code except that we use AmigaDOS calls instead of stdio */
        fh = Open(StateFile,MODE_OLDFILE);
        if(!fh)
        {
            PrintFault(IoErr(),StateFile);
            return(RETURN_FAIL);
        }

        Read(fh,noiz,256);

        MD5Init( &context );
        MD5Update(&context, noiz, 256);
        sprintf(pad,"%d",rand());
        MD5Update(&context, pad, 256);
        time( &seconds );
        pid= rand();
        ppid= FindTask(NULL);
        MD5Update( &context, (UBYTE *) &seconds, sizeof seconds );
        MD5Update( &context, (UBYTE *) &pid, sizeof pid );
        MD5Update( &context, (UBYTE *) &ppid, sizeof ppid );
        MD5Final( in_hash, &context );

        for (row=0; row<16; row++ ) {
                MD5Init( &context );
                MD5Update( &context, noiz+16*row, 16 );
                MD5Update( &context, in_hash, 16 );
                MD5Update( &context, in_hash, row+1 );
                MD5Final( out_hash, &context );
                for ( i=0; i<16; i++ ) {
                        noiz[16*row+i] ^= out_hash[i];
                }
        }

        Seek(fh,0,OFFSET_BEGINNING);
        e = Write(fh, noiz, 256);
        if(e == -1)
        {
            PrintFault(IoErr(),StateFile);
        }
        Close(fh);
    }
    if(Produce_Output)
    {
        BYTE noiz[256];
        BYTE pad[256];
        UBYTE out_hash[16];
        struct MD5Context context;
        int seconds, pid, ppid;
        BPTR fh;

        /* Seed the c.lib's pseudorandom number generator */
        /* which we use because we don't really have */
        /* anything better */
        srand(time(NULL)^(LONG)FindTask(NULL));

        /* This stuff is basically the same as the original noizout */
        /* code except that we use AmigaDOS calls instead of stdio */
        fh = Open(StateFile,MODE_OLDFILE);
        if(!fh)
        {
            PrintFault(IoErr(),StateFile);
            return(RETURN_FAIL);
        }

        Read(fh, noiz, 256);
        Close(fh);
        sprintf(pad,"%d\n",rand());

        MD5Init( &context );
        MD5Update(&context, noiz, 256);
        MD5Update(&context, pad, 256);
        time( &seconds );
        pid= rand();
        ppid= FindTask(NULL);
        MD5Update( &context, (UBYTE *) &seconds, sizeof seconds );
        MD5Update( &context, (UBYTE *) &pid, sizeof pid );
        MD5Update( &context, (UBYTE *) &ppid, sizeof ppid );
        MD5Final( out_hash, &context );

        if(Args[ARG_TO])
        {
            fh = Open(Args[ARG_TO],MODE_NEWFILE);
            if(!fh)
            {
                PrintFault(IoErr(),Args[ARG_TO]);
                return(RETURN_FAIL);
            }
            if(Args[ARG_ASCII])
            {
                char out[20];
                int j;

                FPuts(fh,"0x");
                for(j=0;j<16;j++)
                {
                    sprintf(out,"%02X",out_hash[j]);
                    FPuts(fh,out);
                }
                FPuts(fh,"\n");
            }
            else
            {
                Write(fh,out_hash,16);
            }
            Close(fh);
        }
        else
        {
            char out[20];
            int j;

            PutStr("0x");
            for(j=0;j<16;j++)
            {
                sprintf(out,"%02X",out_hash[j]);
                PutStr(out);
            }
            PutStr("\n");
        }
    }
    return(RETURN_OK);
}


BOOL statefile_exists(void)
{
    BPTR lock;
    BOOL rv;

    lock = Lock(StateFile,SHARED_LOCK);
    if(lock)
    {
        rv = TRUE;
        UnLock(lock);
    }
    else rv = FALSE;
    return(rv);
}


BOOL init_statefile(void)
{
    BPTR file;
    int i, e;

    file = Open(StateFile,MODE_NEWFILE);
    if(!file)
    {
        return(FALSE);
    }
    for(i=0; i<256; i++)
    {
        e = FPutC(file,i);
        if(e < 0)
        {
            Close(file);
            return(FALSE);
        }
    }
    Close(file);
    return(TRUE);
}


void safecleanup(void)
{
    if(rda) FreeArgs(rda);
}
