/* @(#)krypt.c	11.3 13 May 1995 17:37:03 */
/*
 * krypt - Noll Lightning Cypher based on SHS
 *
 * This file was written by:
 *
 *	 Landon Curt Noll  (chongo@toad.com)	chongo <was here> /\../\
 *
 * This code has been placed in the public domain.  Please do not
 * copyright this code.
 *
 * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH  REGARD  TO
 * THIS  SOFTWARE,  INCLUDING  ALL IMPLIED WARRANTIES OF MER-
 * CHANTABILITY AND FITNESS.  IN NO EVENT SHALL  LANDON  CURT
 * NOLL  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM  LOSS  OF
 * USE,  DATA  OR  PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR  IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * WARNING: This code, and the algorithm that it claims to implement
 *	    are under-going peer review.  During peer review, both
 *	    source and algorithm may be modified extensively.
 *
 * WARNING: Certain US Regulations may restrict and/or prohibit 
 *	    the exportation of this software.  Please consult the
 *	    proper Federal authorities and/or seek legal advice
 *	    if you desire to export this software!
 *
 * NOTE: This code is the raw interface to NLC.  See krypton.c and
 *	 kryptoff.c for a more user friendly interface.  (These progs
 *	 are works in process and are not ready for release in 2.11) - XXX
 *
 ***
 *
 * NOTE: The version information below refers to all krypt code, not
 *	 just this file.
 *
 * Version 2.8: 22 Jan 1994		Landon Curt Noll   (chongo@toad.com)
 *     initial code based on shs version 2.8 from 22 Jan 1993.
 *     removed inode (-i) mode (not useful to feedback cypher)
 *     removed dual (-d) digest mode (not useful to feedback cypher)
 *     removed C style output (-c) mode (not useful to feedback cypher)
 *     removed quiet mode (-q) mode (not useful to feedback cypher)
 *     handle read errors and EOF better
 *     renumbered exit codes
 *     byte sex swapping is now controlled thru the SHS_TRANSFORM macro
 *     shsTransform() is now called via the SHS_TRANSFORM macro
 *     renamed -p to -k, -P or -K
 *     pad last partial data chunk with sum_digest expansion ring to end
 *     store 64 bit count in a final chunk
 *
 * Version 2.9: 11 Feb 1994		Landon Curt Noll    (chongo@toad.com)
 *     now a pure cypher, always writes chunks to stdout
 *     twist after the key is pressed and if partial text chunk is read
 *     will silently zero pad on decryption of a final partial block
 *
 * Version 2.10: 11 Mar 1994		Landon Curt Noll    (chongo@toad.com)
 *     use new ring formation process
 *     performance improvements
 *     renamed SHS to NLC, and shs to nlc
 *     eliminated the 64 bit count
 *
 * Version 2.11: 13 May 1995		Landon Curt Noll    (chongo@toad.com)
 *     Very minor comment changes
 *     Limited distribution for peer review
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "cypher.h"
#include "endian.h"


/* size of test in megabytes */
#define TEST_MEG 16

/* number of chunks to process */
#define TEST_CHUNKS (TEST_MEG*1024*1024/READSIZE)

/* NLC test suite strings */
#define ENTRY(str) {(BYTE *)str, NULL, sizeof(str)-1}
struct nlc_test {
    BYTE *ro_data;	/* read only string data or NULL to test */
    BYTE *data;		/* data or NULL to test */
    int len;		/* length of data */
} nlc_test_data[] = {
    ENTRY(""),
    ENTRY("a"),
    ENTRY("abc"),
    ENTRY("message digest"),
    ENTRY("abcdefghijklmnopqrstuvwxyz"),
    ENTRY("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
    ENTRY("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
};
#define MAX_NLC_TEST_DATA (sizeof(nlc_test_data)/sizeof(nlc_test_data[0]))

/* nlc test filenames */
char *nlc_test_file[] = {
    "file1",
    "file2",
};
#define MAX_NLC_TEST_FILE (sizeof(nlc_test_file)/sizeof(nlc_test_file[0]))

/* where the test files are located by default */
#if !defined(TLIB)
#define TLIB "."
#endif

/* global variables */
char *program;			/* our name */

/* static variables */
static int debug = 0;		/* 1 => debug mode */
static int r_flag = 0;		/* 1 => reverse cypher mode */
static int t_flag = 0;		/* 1 => time performance test suite */
static int x_flag = 0;		/* 1 => data test suite */
static int output_sex = BYTE_ORDER;	/* output byte sex */

/* forward declare functions */
static void nlcStream P((BYTE*, UINT, FILE*, NLC_INFO*));
static void nlcFile P((BYTE*, UINT, char*, NLC_INFO*));
static int nlcKeyFileRead P((char*, BYTE**));
static void nlcHelp P((void));
void main P((int, char**));


/*
 * nlcStream - process an open file
 *
 * input:
 *	key_str		pointer to the NLC key (not a string, may contain \0's)
 *	key_len		length of key_str
 *	stream		file stream to process
 *	dig		NLC state machine
 *
 * This function will process and NLC key, read data from stream and possibly
 * write it to stdout.
 */
static void
nlcStream(key_str, key_len, stream, dig)
    BYTE *key_str;		/* key or NULL */
    UINT key_len;		/* length of key_str */
    FILE *stream;		/* the stream to process */
    NLC_INFO *dig;		/* current digest */
{
    ULONG data[READWORDS];	/* our read buffer */
    int bytes;			/* bytes last read */
    int ret;			/* partial fread return value */

    /*
     * perform post pre-fix processing
     */
    nlcKey(dig, key_str, key_len);

    /*
     * process the contents of the file
     */
    clearerr(stream);
    while ((bytes = fread((char *)data, 1, READSIZE, stream)) > 0) {

	/*
	 * if we got a partial read, try to read up to a full chunk
	 */
	while (bytes < READSIZE) {
	    /* try to read more */
	    ret = fread((char *)data+bytes, 1, READSIZE-bytes, stream);

	    /* carefully examine the result */
	    if (ret < 0 || ferror(stream)) {
	    	/* error processing */
	    	fprintf(stderr, "%s: ", program);
	    	perror("read #1 error");
	    	exit(1);
	    } else if (ret == 0 || feof(stream)) {
	    	/* EOF processing */
	    	nlcCypher(dig, (BYTE *)data, bytes);
	    	return;
	    }

	    /* note that we have more bytes */
	    bytes += ret;
	}

	/*
	 * digest the read
	 */
	nlcFullCypher(dig, (BYTE *)data, bytes);
    }

    /*
     * watch for errors
     */
    if (bytes < 0 || ferror(stream)) {
	/* error processing */
	fprintf(stderr, "%s: ", program);
	perror("read #2 error");
	exit(2);
    }
    return;
}


/*
 * nlcFile - process a file
 *
 * input:
 *	key_str		pointer to the NLC key (not a string, may contain \0's)
 *	key_len		length of key_str
 *	stream		file stream to process
 *	dig		NLC state machine
 *
 * This function will open a file and call nlcStream().
 */
static void
nlcFile(key_str, key_len, filename, dig)
    BYTE *key_str;		/* key or NULL */
    UINT key_len;		/* length of key_str */
    char *filename;		/* the filename to process */
    NLC_INFO *dig;		/* current digest */
{
    FILE *inFile;		/* the open file stream */

    /*
     * open the file
     */
    inFile = fopen(filename, "rb");
    if (inFile == NULL) {
	fprintf(stderr, "%s: cannot open %s: ", program, filename);
	perror("");
	return;
    }

    /*
     * process the data stream
     */
    nlcStream(key_str, key_len, inFile, dig);
    fclose(inFile);
}


/*
 * nlcTimeTrial - measure the speed of NLC
 *
 * Measures user time required to process TEST_MEG megabytes of characters.
 * Time is measured in user cpu seconds.
 */
static void
nlcTimeTrial()
{
    ULONG data[READWORDS];	/* test buffer */
    NLC_INFO nlcInfo;		/* hash state */
    struct rusage start;	/* test start time */
    struct rusage stop;		/* test end time */
    double usrsec;		/* duration of test in user seconds */
    unsigned int i;

    /*
     * initialize test data
     */
    for (i = 0; i < READSIZE; i++) {
	((BYTE *)data)[i] = (BYTE)(i & 0xFF);
    }

    /*
     * announce test
     */
    printf("krypt%s time trial for %d megs of test data ...",
      (r_flag ? " -r" : ""), TEST_MEG);
    fflush(stdout);

    /*
     * digest data in READSIZE byte chunks
     */
    getrusage(RUSAGE_SELF, &start);
    nlcInitCypher(&nlcInfo, r_flag, output_sex, stdout);
    for (i=0; i < TEST_CHUNKS; ++i) {
	nlcFullCypher(&nlcInfo, (BYTE *)data, READSIZE);
    }
    if (!r_flag) {
	nlcFinalCypher(&nlcInfo);
    }
    getrusage(RUSAGE_SELF, &stop);

    /*
     * announce the test results
     */
    usrsec = (stop.ru_utime.tv_sec - start.ru_utime.tv_sec) +
    	   (double)(stop.ru_utime.tv_usec - start.ru_utime.tv_usec)/1000000.0;
    printf("\nuser seconds to process test data: %.2f\n", usrsec);
    printf("characters processed per user second: %d\n",
	(int)((double)TEST_MEG*1024.0*1024.0/usrsec));
}


/*
 * nlcKeyFileRead - read a file as if it were a key
 *
 * given:
 *	key_file	filename of the file containing the key
 *	buf		pointer to the key pointer
 */
static int
nlcKeyFileRead(key_file, buf)
    char *key_file;		/* form key_str from file key_file */
    BYTE **buf;			/* pointer to key_str pointer */
{
    struct stat statbuf;	/* stat for key_file */
    int key_len;		/* length of key_file to be used */
    int bytes;			/* bytes read from key_file */
    FILE *pre;			/* key_file descriptor */

    /* obtain the length that we will use */
    if (stat(key_file, &statbuf) < 0) {
	fprintf(stderr, "%s: unpable to find prepend file %s\n",
	    program, key_file);
	exit(4);
    }
    key_len = statbuf.st_size;
    if (key_len > MAX_PRE_FILE) {
	/* don't use beyond MAX_PRE_FILE in size */
	key_len = MAX_PRE_FILE;
    }

    /* malloc our key */
    *buf = (BYTE *)malloc(key_len+1);
    if (*buf == NULL) {
	fprintf(stderr, "%s: malloc #1 failed\n", program);
	exit(5);
    }

    /* open our key_file */
    pre = fopen(key_file, "rb");
    if (pre == NULL) {
	fprintf(stderr, "%s: unable to open prepend file %s\n",
	  program, key_file);
	exit(6);
    }

    /* read our key_file data */
    bytes = fread((char *)(*buf), 1, key_len, pre);
    if (bytes != key_len) {
	fprintf(stderr,
	  "%s: unable to read %d bytes from prepend file %s\n",
	  program, key_len, key_file);
	exit(7);
    }

    /* return our length */
    return (key_len);
}


/*
 * nlcHelp - print nlc help message and exit
 */
static void
nlcHelp()
{
#if defined(DEBUG)
    fprintf(stderr,
      "%s [-bDhlt][-k key][-k kfile][-s str] [file]\n", program);
#else
    fprintf(stderr,
      "%s [-bhlt][-k key][-k kfile][-s str] [file]\n", program);
#endif
    fprintf(stderr,
      "    -b          force big endian mode\n");
#if defined(DEBUG)
    fprintf(stderr,
      "    -D          debug mode\n");
#endif
    fprintf(stderr,
      "    -h          prints this message\n");
    fprintf(stderr,
      "    -k key      key is str\n");
    fprintf(stderr,
      "    -K kfile    key is up to the first %dk bytes of kfile\n",
      MAX_PRE_FILE/1024);
    fprintf(stderr,
      "    -l          force little endian mode\n");
    fprintf(stderr,
      "    -r          reverse feedback mode (default is forward)\n");
    fprintf(stderr,
      "    -s str      prints digest and contents of string\n");
    fprintf(stderr,
      "    -t          prints time statistics for %dM chars\n", TEST_MEG);
    fprintf(stderr,
      "    -v          print version\n");
    fprintf(stderr,
      "    file        print digest and name of a single file\n");
    fprintf(stderr,
      "    (no args)   print digest of stdin\n");
    exit(0);
}


/*
 * main - nlc main
 */
void
main(argc, argv)
    int argc;			/* arg count */
    char **argv;		/* the args */
{
    NLC_INFO digest;		/* our current digest */
    BYTE *key_str = NULL;	/* pre-process this data first */
    char *key_file = NULL;	/* pre-process this file first */
    char *data_str = NULL;	/* data is this string, not a file */
    UINT key_str_len;		/* length of key_str or key_file */
    UINT data_str_len;		/* length of data_str */
    extern char *optarg;	/* argument to option */
    extern int optind;		/* option index */
    int c;

    /*
     * parse args
     */
    program = argv[0];
    while ((c = getopt(argc, argv, "bDhk:K:lrs:tv")) != -1) {
        switch (c) {
	case 'b':
	    output_sex = BIG_ENDIAN;
	    break;
	case 'D':
#if defined(DEBUG)
	    debug = 1;
#else
	    fprintf(stderr, "%s: not compiled with -DDEBUG\n", program);
	    exit(8);
	    /*NOTREACHED*/
#endif
	    break;
	case 'h':
	    nlcHelp();
	    /*NOTREACHED*/
	    break;
	case 'k':
	    key_str = (BYTE *)optarg;
	    break;
	case 'K':
	    key_file = optarg;
	    break;
	case 'l':
	    output_sex = LITTLE_ENDIAN;
	    break;
	case 'r':
	    r_flag = 1;
	    break;
        case 's':
            data_str = optarg;
            break;
	case 't':
	    t_flag = 1;
	    break;
	case 'v':
	    printf("%s: version 2.%s.%s%s %s\n",
	        program, "11", "3",
	        (strcmp(nlc_what,"@(#)") == 0 &&
	         strcmp("@(#)","@(#)") == 0 &&
	         strcmp(NLC_H_WHAT,"@(#)") == 0) ? "" : "+",
	        "95/05/13");
	    exit(0);
	default:
	    nlcHelp();
	    break;
        }
    }
    /* arg checking */
    if (data_str && optind != argc) {
	fprintf(stderr, "%s: -s is not compatible with digesting files\n",
	    program);
	exit(9);
    }
    if (optind+1 < argc) {
	fprintf(stderr, "%s: only one file may be processed at a time\n",
	    program);
	exit(10);
    }

    /*
     * process -t if needed
     */
    if (t_flag) {
	nlcTimeTrial();
	exit(0);
    }

    /*
     * process -P or -p if needed
     */
    if (key_str && key_file) {
	fprintf(stderr, "%s: -p and -P conflict\n", program);
	exit(11);
    }
    if (key_file) {
	key_str_len = nlcKeyFileRead(key_file, &key_str);
    } else if (key_str) {
        key_str_len = strlen((char *)key_str);
    } else {
        key_str_len = 0;
    }
    if (key_str_len > MAX_PRE_FILE) {
	fprintf(stderr, "%s: key may not be longer than %d bytes\n",
	    program, MAX_PRE_FILE);
    	exit(12);
    }

    /*
     * case: process a string
     */
    if (data_str != NULL) {
	data_str_len = strlen(data_str);
	nlcInitCypher(&digest, r_flag, output_sex, stdout);
	nlcKey(&digest, key_str, key_str_len);
	nlcCypher(&digest, (BYTE *)data_str, data_str_len);
	nlcFinalCypher(&digest);

    /*
     * case: process stdin
     */
    } else if (optind == argc) {
	nlcInitCypher(&digest, r_flag, output_sex, stdout);
	nlcStream(key_str, key_str_len, stdin, &digest);
	nlcFinalCypher(&digest);

    /*
     * case: process files
     */
    } else {
	nlcInitCypher(&digest, r_flag, output_sex, stdout);
	nlcFile(key_str, key_str_len, argv[optind], &digest);
	nlcFinalCypher(&digest);
    }

    /* all done */
    exit(0);
}
