/* FILE        : mmpc_snd.c
** AUTHOR      : Jim Shapiro
** DATE        : 19 March, 1999
** DISCLAIMER  : No liability is assumed by the author for any use
**               made of this program.
** DISTRIBUTION: Any use may be made of this program, as long as the
**               clear acknowledgment is made to the author in code
**               and runtime executables.  The author retains
**               copyright.
*/

#define THIS_IS_MAIN

#include <stdio.h>
#include <stdlib.h>
#include <strings.h> /* bzero(), bcopy() */
#include <sys/stat.h>
#include <time.h>

#include "msg_dgst_rsa.h"
#include "blowfish.h"
#include "package.h"  /* must come after blowfish.h */
#include "mmpc_snd.h" /* must come after blowfish.h */
#include "clcg_rand.h"

/* (m)ulti-(m)essage (p)ackage (c)haffing - sender */

/* MD5 (the default) or SHA */
#define SHA

#ifndef SHA
const int Digest_bytes = 16;
const int Block_bytes  = 24;
#else
const int Digest_bytes = 20;
const int Block_bytes  = 28;
#endif

int main(int argc, char **argv)
{
  char buf[101];
  unsigned char chaff_p[Block_bytes];
  unsigned char in_block_p[8];
  unsigned char pack_block_p[8];
  unsigned char digest_p[Digest_bytes];
  int file_count;
  int rv = EXIT_FAILURE;
  unsigned long i;
  unsigned long j;
  unsigned long chaff_blocks;
  unsigned long total_blocks = 0lu;
  unsigned long n;
  unsigned long *block_indices_p;
  unsigned long *ran_array_p;
  FILE *cipher_p;
  FILE_INFO *fi_p;
  FILE_INFO *fi_r;
  
  do {
    if((argc == 2) && (strcmp(argv[1], "-h") == 0)) {
      help(argv[0]);
      break;
    }
#ifndef SHA
    (void)fputs("Using hmac_md5 for digest.\n", stderr);
#else
    (void)fputs("Using hmac_sha for digest.\n", stderr);
#endif
    (void)printf("How many files do you want to package-chaff   ? ");
    file_count = atoi(fgets(buf, sizeof buf, stdin));
    if((fi_p = (FILE_INFO *)malloc(file_count * sizeof(FILE_INFO)))
       == NULL) {
      (void)fprintf(stderr,
                    "Cannot malloc %d FILE_INFO structure%s!...\n",
                    file_count, file_count == 1 ? "" : "s");
      break;
    }
    /* set (internal) public arrays */
    package_one_time_set(time(NULL), 123456);
    for(i = 0; i < file_count; i++) {
       total_blocks += file_info(i, fi_p + i);
    }
    (void)printf("How many chaff blocks do you want to add      ? ");
    if((chaff_blocks = atol(fgets(buf, sizeof buf, stdin))) <
       Min_chaff_blocks) {
      (void)printf(
         "Increasing number of chaff blocks to %lu for security.\n",
                   chaff_blocks = Min_chaff_blocks);
    }
    /* n starts = total_blocks, but gets decremented by get_ran() */
    n = total_blocks += chaff_blocks;
    (void)printf("Total blocks = %lu\n", total_blocks);
    (void)printf("Enter name of ciphertext file                 : ");
    (void)fgets(buf, sizeof buf, stdin);
    buf[strlen(buf) - 1] = '\0';
    if((cipher_p = fopen(buf, "wb")) == NULL) {
      (void)printf("Cannot open chaff file \"%s\"!...\n", buf);
      break;
    }
    Endian = check_byte_order();
    if((ran_array_p = (unsigned long *)malloc(total_blocks *
                         sizeof(unsigned long))) == NULL) {
      (void)printf(
                 "Cannot malloc ran_array_p of %lu element%s!...\n",
                   total_blocks, total_blocks == 1lu ? "" : "s");
      break;
    }
    if((block_indices_p = (unsigned long *)malloc(total_blocks *
                             sizeof(unsigned long))) == NULL) {
      (void)printf(
             "Cannot malloc block_indices_p of %lu element%s!...\n",
                   total_blocks, total_blocks == 1lu ? "" : "s");
      break;
    }
    /* Load array for random data indices. */
    for(i = 0; i < total_blocks; i++) {
      ran_array_p[i] = i;
      block_indices_p[i] = file_count; /* flag for chaff */
    }
    for(i = 0, fi_r = fi_p; i < file_count; i++, fi_r++) {
      for(j = 0; j < fi_r->data_blocks; j++) {
        block_indices_p[get_ran(ran_array_p, &n)] = i;
      }
    }
    /* Done with array -- rans are in block_indices_p. */
    free(ran_array_p);
    for(i = 0; i < total_blocks; i++) {
      (void)sprintf(buf, "%lu", i);
      if((j = block_indices_p[i]) != file_count) { /* data block */
        if(++(fi_p[j].i) < fi_p[j].data_blocks - 1) {
          /* >= 2 blocks to go */
          if(fread(in_block_p, sizeof(unsigned char), 8,
                   fi_p[j].plain_p) != 8) {
            (void)printf("Read error on file \"%s\"!...\n",
                         fi_p[j].name_p);
            exit(EXIT_FAILURE);
          }
          package_transform(fi_p[j].ms_p, &(fi_p[j].pack_array),
                            fi_p[j].i, in_block_p, pack_block_p);
          if(fwrite(pack_block_p, sizeof(unsigned char), 8,
                    cipher_p) != 8) {
            (void)puts("Could not write data to chaff file!...");
            exit(EXIT_FAILURE);
          }
#ifndef SHA
          hmac_md5_string(pack_block_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#else
          hmac_sha_string(pack_block_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#endif
          if(fwrite(digest_p, sizeof(unsigned char), Digest_bytes,
                    cipher_p) != Digest_bytes) {
            (void)puts("Could not write HMAC to chaff file!...");
            exit(EXIT_FAILURE);
          }
        } else if((fi_p[j].i) == fi_p[j].data_blocks - 1) {
          /* partial? */
          if(fi_p[j].bytes_leftover == 0) { /* create block */
            /* bzero(in_block_p, 7); 0 pad, if desired */
            in_block_p[7] = 8;              /* sentinel count */
          } else {                          /* partial block */
            int extra = fi_p[j].bytes_leftover;
            if(fread(in_block_p, sizeof(unsigned char), extra,
                     fi_p[j].plain_p) != extra) {
              (void)printf("Read error on file \"%s\"!...\n",
                           fi_p[j].name_p);
              exit(EXIT_FAILURE);
            }
            /*
            if(extra != 7) { 0 pad, if desired
               bzero(in_block_p + extra, 7 - extra); 
            }
            */
            in_block_p[7] = 8 - extra; /* sentinel count */
          }
          package_transform(fi_p[j].ms_p, &(fi_p[j].pack_array),
                            fi_p[j].i, in_block_p, pack_block_p);
          if(fwrite(pack_block_p, sizeof(unsigned char), 8,
                    cipher_p) != 8) {
            (void)puts("Could not write data to chaff file!...");
            exit(EXIT_FAILURE);
          }
#ifndef SHA
          hmac_md5_string(pack_block_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#else
          hmac_sha_string(pack_block_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#endif
          if(fwrite(digest_p, sizeof(unsigned char), Digest_bytes,
                    cipher_p) != Digest_bytes) {
            (void)puts("Could not write HMAC to chaff file!...");
            exit(EXIT_FAILURE);
          }
        } else {                       /* last pseudo-block */
          if(fwrite(fi_p[j].ms_p, sizeof(unsigned char), 8,
                    cipher_p) != 8) {
            (void)puts("Could not write data to chaff file!...");
            exit(EXIT_FAILURE);
          }
#ifndef SHA
          hmac_md5_string(fi_p[j].ms_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#else
          hmac_sha_string(fi_p[j].ms_p, 8, fi_p[j].key_p,
                          fi_p[j].key_len, digest_p);
#endif
          if(fwrite(digest_p, sizeof(unsigned char), Digest_bytes,
                    cipher_p) != Digest_bytes) {
            (void)puts("Could not write HMAC to chaff file!...");
            exit(EXIT_FAILURE);
          }
        }
      } else { /* chaff block */
        for(j = 0; j < Block_bytes; j++) {
          chaff_p[j] = 256 * combined_lcg();
        }
        if(fwrite(chaff_p, sizeof(unsigned char), Block_bytes,
                  cipher_p) != Block_bytes) {
          (void)puts("Could not write chaff to file!...");
          exit(EXIT_FAILURE);
        }
      }
    }
    for(i = 0; i < file_count; i++) {
      (void)fclose(fi_p[i].plain_p);
    }
    free(fi_p);
    free(block_indices_p);
    (void)fclose(cipher_p);
    rv = EXIT_SUCCESS;
  } while(0);
  return rv;
}

static unsigned long file_info(int index, FILE_INFO *fi_p)
{
  char buf[101];
  char byte_s[3] = { '\0', '\0', '\0' };
  int i;
  int j;
  int len;
  unsigned long data_bytes;
  unsigned long data_blocks;
  struct stat stat_struct;
  
  (void)printf("Enter name of file                           %d: ",
               index + 1);
  (void)fgets(buf, sizeof buf, stdin);
  buf[strlen(buf) - 1] = '\0';
  if((fi_p->plain_p = fopen(buf, "rb")) == NULL) {
    (void)printf("Cannot open plaintext file \"%s\"!...\n", buf);
    exit(EXIT_FAILURE);
  }
  if(stat(buf, &stat_struct) == -1) {
    (void)printf("Cannot stat \"%s\"!...\n", buf);
    exit(EXIT_FAILURE);
  }
  fi_p->name_p = strdup(buf);
  fi_p->data_bytes = data_bytes = stat_struct.st_size;
  /* possible extra sentinel block and always 1 extra for key */
  fi_p->data_blocks = data_blocks =
                      (data_bytes + 16) / BYTES_PER_BLOCK;
  fi_p->bytes_leftover = data_bytes % BYTES_PER_BLOCK;
  (void)printf("Data file \"%s\" (%lu bytes) requires %lu (%d) "
               "byte data blocks.\n",
               buf, data_bytes, data_blocks, BYTES_PER_BLOCK);
  (void)printf("Enter key (in hexadecimal) for %-15s: ",
               fi_p->name_p);
  (void)fgets(buf, sizeof buf, stdin);
  len = strlen(buf);
  buf[--len] = '\0';
  if((len % 2) != 0) {
    (void)puts(
       "Sorry, you did not enter an even number of characters!...");
    exit(EXIT_FAILURE);
  }
  fi_p->key_len = len >> 1; /* convert from hex to byte count */
  if((fi_p->key_p = (unsigned char *)malloc(fi_p->key_len *
                       sizeof(unsigned char))) == NULL) {
    (void)fprintf(stderr,
                  "Could not malloc %d bytes for key!...\n", len);
    exit(EXIT_FAILURE);
  }
  for(i = j = 0; i < len; i += 2) {
    bcopy(buf + i, byte_s, 2);
    fi_p->key_p[j++] = (unsigned char)strtol(byte_s, (char **)NULL,
                                             16);
  }
  fi_p->i = 0; /* starting index (-1) for package transform */
  package_initialize(&(fi_p->pack_array), fi_p->ms_p);
  return data_blocks;
}

static unsigned long get_ran(unsigned long *a_p, unsigned long *n_p)
{
  unsigned long i  = (*n_p * combined_lcg());
  unsigned long rv = a_p[i];
  
  a_p[i] = a_p[--*n_p];
  return rv;
}

static void help(char *program)
{
  (void)printf(
"\n\"%s\" will intermix any number of files, storing them\n"
"package-transformed as 8 byte data plus either 16 byte MD5\n"
"or 20 byte SHA-1 HMAC pairs. For each file a key is entered\n"
"as a string of hexadecimal characters.  If the user chooses,\n"
"any number of pseudo-random chaff blocks can be inter-\n"
"spersed into the data blocks.  The companion program\n"
"\"mmpc_rec\" is used for decryption.\n\n",
program);
}