/**********************************************************************/
/*                                                                    */
/*   Dieses Programm erfordert zur Produktion die sechs Dateien       */
/*                                                                    */
/*   md5.c                                                            */
/*   md5.h                                                            */
/*   sha1.c                                                           */
/*   sha1.h                                                           */
/*   sha256.c                                                         */
/*   sha256.h                                                         */
/*                                                                    */
/*   die in                                                           */
/*                                                                    */
/*   http://www.cr0.net:8040/code/crypto/md5/                         */
/*   http://www.cr0.net:8040/code/crypto/sha1/                        */
/*   http://www.cr0.net:8040/code/crypto/sha256/                      */
/*                                                                    */
/*   zu finden sind und im selben Directory sein muessen.             */
/*   Der Code muss dazu nicht veraendert werden.                      */
/*                                                                    */
/*   Bei der Uebernahme dieser Sourcen von den erwaehnten URLs -      */
/*   Cave: Die meisten C-Compiler vertragen HTML-Entities             */
/*   nicht besonders gut!                                             */
/*                                                                    */
/*   Dieses Programm "csum.c" ist, wie die o.a. Sourcen,              */
/*   selbst Open Source.                                              */
/*                                                                    */
/*   Autor: Herwig Huener                                             */
/*          GruberStrasse 10 A                                        */
/*          85655 GrossHelfenDorf                                     */
/*          Bayern / EU                                               */
/*                                                                    */
/*   Das makefile sieht dann so aus: (Watch the TAB!)                 */
/*                                                                    */
/*   csum : csum.c  envtest.c  md5.c  md5.h  sha1.c  \                */
/*          sha1.h  sha256.c  sha256.h                                */
/*           gcc csum.c md5.c sha1.c sha256.c -o csum                 */
/*                                                                    */
/**********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "md5.h"
#include "sha1.h"
#include "sha256.h"

#define HEADER "csum V1.0 2004-08-19 20:15:00 MESZ .. 2004-09-03 22:55:44 MESZ"

#define PASSPHRASELENGTH    2000
#define CHECKFILELINELENGTH 2000

typedef enum {none, md5, sha1, sha256} hash_type;



int           debug             = 0;
int           verbose           = 0;
int           standard_behavior = 0;
int           i_have_passphrase = 0;
int           file_missing      = 0;
int           file_match        = 0;
int           file_missmatch    = 0;
int           number_warnings   = 0;
hash_type     hash              = none;
unsigned char passphrase [PASSPHRASELENGTH];
unsigned char check_file_line [CHECKFILELINELENGTH];


int digest_input (char *the_file, char *compare_hash);

void help (int detail);

int verify_against_check_file (char *the_file);

void tell_comparison (unsigned char *the_file,
                      unsigned char *computed_hash,
                      unsigned char *compare_hash,
                      int           comp_length);



main (int argc, char *argv[])

{
   int q                 = 0;
   int expect_filename   = 0;
   int number_of_files   = 0;
   int expect_check_file = 0;

   if (getenv ("CSUMPASS"))
   {
       strcpy (passphrase, getenv ("CSUMPASS"));
       i_have_passphrase = 1;
       if (debug) printf (">>> passphrase = %s\n", passphrase);
   }

   for (q = 0; q < argc; q++)
   {
      if (debug) printf (">>> arg %i = %s\n", q, argv[q]);
      if (0 == q)
      {
         if (!strcmp (argv[0], "cmd5sum"))
         {
            hash = md5;
         }
         if (!strcmp (argv[0], "csha1sum"))
         {
            hash = sha1;
         }
         if (!strcmp (argv[0], "csha256sum"))
         {
            hash = sha256;
         }
         if (!strcmp (argv[0], "md5sum"))
         {
            hash = md5;
            standard_behavior = 1;
         }
         if (!strcmp (argv[0], "sha1sum"))
         {
            hash = sha1;
            standard_behavior = 1;
         }
         if (!strcmp (argv[0], "sha256sum"))
         {
            hash = sha256;
            standard_behavior = 1;
         }
      }
      else /* (0 != q) */
      {
         expect_filename = 1;

         if (!strcmp (argv[q], "-d"))
         {
            debug++;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--debug"))
         {
            debug++;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "-v"))
         {
            verbose++;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--verbose"))
         {
            verbose++;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--md5"))
         {
            hash = md5;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--sha1"))
         {
            hash = sha1;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--sha256"))
         {
            hash = sha256;
            expect_filename = 0;
         }
         if (!strcmp (argv[q], "--md5sum"))
         {
            hash = md5;
            expect_filename = 0;
            standard_behavior = 1;
         }
         if (!strcmp (argv[q], "--sha1sum"))
         {
            hash = sha1;
            expect_filename = 0;
            standard_behavior = 1;
         }
         if (!strcmp (argv[q], "--sha256sum"))
         {
            hash = sha256;
            expect_filename = 0;
            standard_behavior = 1;
         }
         if ((!strcmp (argv[q], "--passphrase"))
         ||  (!strcmp (argv[q], "-p")))
         {
	    q++;
            expect_filename = 0;
            if (debug) printf (">>> arg %i = %s\n", q, argv[q]);
            strcpy (passphrase, argv[q]);
            i_have_passphrase = 1;
            if (debug) printf (">>> passphrase = %s (%i)\n", passphrase, strlen (passphrase));
	 }
         if (!strcmp (argv[q], "--ask-passphrase"))
         {
            fprintf (stderr, "   passphrase please : ");
	    fgets (passphrase, PASSPHRASELENGTH, stdin);
            i_have_passphrase = 1;
            expect_filename = 0;
	    passphrase [strlen (passphrase)-1] = 0; /* do not want line end in here */
            if (debug) printf (">>> passphrase = %s (%i)\n", passphrase, strlen (passphrase));
	 }
         if ((!strcmp (argv[q], "-c")) || (!strcmp (argv[q], "--check")))
         {
            expect_check_file = 1;
            expect_filename = 0;
         }
         if ((!strcmp (argv[q], "-h")) || (!strcmp (argv[q], "--help")))
         {
             help (0);
             exit (0);
         }

         /*********************************************************************************/
         /*                                                                               */
         /*   If, at this place, expect_filename is true, that means that the current     */
         /*   parameter is a functional parameter and not a filename. If, additionally,   */
         /*   expect_check_file is true, the file is expected to have a list of           */
         /*   hash-sums and file-names.                                                   */
         /*                                                                               */
         /*********************************************************************************/

         if (expect_filename)
         {
	    if (expect_check_file)
	    {
               verify_against_check_file (argv[q]);
            }
	    else
	    {
               (void) /* for now */ digest_input (argv[q], 0);
	    }
            number_of_files++;
         }
      } /* (0 != q) */
   }

   if (expect_check_file)
   {
      if ( 1 < number_of_files) printf ("   number of check-files       =%5i\n", number_of_files);
      if ( 0 < file_missing)    printf ("   number of missing files     =%5i\n", file_missing);
      if ( 0 < file_match)      printf ("   number of file matches      =%5i\n", file_match);
      if ( 0 < file_missmatch)  printf ("   number of FILE MISSMATCHES! =%5i\n", file_missmatch);
      if ( 0 < number_warnings) printf ("   number of warnings          =%5i\n", number_warnings);
   }

   if (expect_check_file) exit (0); /* all done */

   if (0 == number_of_files)
   {
      if (none == hash)
      {
         printf (">>> error: you must provide a hash type.\n");
         exit (1);
      }
      (void) /* for now */ digest_input ("/dev/stdin", 0);
      exit (0);
   }

   if (debug)
   {
      printf ("\n");
      printf (">>> CSUMHASH = %s\n", getenv ("CSUMHASH"));
      printf (">>> CSUMPASS = %s\n", getenv ("CSUMPASS"));
      printf (">>> hash = ");
      switch (hash)
      {
         case none   : printf ("none");   break;
         case md5    : printf ("md5");    break;
         case sha1   : printf ("sha1");   break;
         case sha256 : printf ("sha256"); break;
         default     : printf ("???");    break;
      }
      printf ("\n");
      printf ("\n");
   }
   if (none == hash)
   {
      printf (">>> error: you must provide a hash type.\n");
      exit (1);
   }
}



int digest_input (char *the_file, char *compare_hash)

{
    FILE *f;
    int f_is_open = 0;
    int i, j, lx;
    char output [65]; /* maximized for sha256 */
    unsigned char buf[1000];

    md5_context ctx_md5;
    unsigned char md5sum[16];
    sha1_context ctx_sha1;
    unsigned char sha1sum[20];
    sha256_context ctx_sha256;
    unsigned char sha256sum[32];

   if (debug) printf (">>> checking file    %s\n", the_file);
   if (debug && i_have_passphrase) printf (">>> using passphrase %s\n", passphrase);
   if (!standard_behavior && !i_have_passphrase)
   {
      fprintf (stderr, ">>> Warning: With non-standard behaviour, there should be a passphrase.\n");
      number_warnings++;
   }
   if (standard_behavior && i_have_passphrase)
   {
      fprintf (stderr, ">>> Warning: With standard behaviour, there should be NO passphrase.\n");
      number_warnings++;
   }
   switch (hash)
   {
      case none   : printf ("none");
         printf (">>> error: you must really provide a hash type.\n");
         exit (1);
         break;
      case md5    :
         if (debug) printf (">>> using md5\n");
/**********************************************************************/

    {
	if (!strcmp ("/dev/stdin", the_file))
	{
	   f = stdin;
        }
	else
	{
           if( ! ( f = fopen( the_file, "rb" ) ) )
           {
              printf (">>> serious warning: fopen %s - no such file\n", the_file);
              number_warnings++;
	      file_missing++;
              return( 1 );
           }
        }
        f_is_open = 1;

        md5_starts( &ctx_md5 );

        /**************************************************************/
	if (i_have_passphrase)
	{
	   if (debug) printf (">>> prefixing passphrase\n");
	   strcpy ( buf, passphrase );
           lx = strlen (passphrase);
           if ( ( i = fread( &buf [lx], 1, sizeof( buf ) - lx, f ) ) > 0 )
           {
               md5_update( &ctx_md5, buf, i+lx );
           }
	}
        /**************************************************************/

        while( ( i = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
        {
            md5_update( &ctx_md5, buf, i );
        }

        md5_finish( &ctx_md5, md5sum );

	if (compare_hash)
	{
	   tell_comparison (the_file, md5sum, compare_hash, 16);
	}
        else
	{
           for( j = 0; j < 16; j++ )
           {
              printf( "%02x", md5sum[j] );
           }

           printf( "  %s\n", the_file );
	}
    }
/**********************************************************************/
         break;
      case sha1   :
         if (debug) printf (">>> using sha1\n");
/**********************************************************************/
    {
	if (!strcmp ("/dev/stdin", the_file))
	{
	   f = stdin;
        }
	else
	{
           if( ! ( f = fopen( the_file, "rb" ) ) )
           {
              printf (">>> serious warning: fopen %s - no such file\n", the_file);
              number_warnings++;
              file_missing++;
              return( 1 );
           }
        }
        f_is_open = 1;

        sha1_starts( &ctx_sha1 );

        /**************************************************************/
	if (i_have_passphrase)
	{
	   if (debug) printf (">>> prefixing passphrase\n");
	   strcpy ( buf, passphrase );
           lx = strlen (passphrase);
           if ( ( i = fread( &buf [lx], 1, sizeof( buf ) - lx, f ) ) > 0 )
           {
               sha1_update( &ctx_sha1, buf, i+lx );
           }
	}
        /**************************************************************/

        while( ( i = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
        {
            sha1_update( &ctx_sha1, buf, i );
        }

        sha1_finish( &ctx_sha1, sha1sum );

	if (compare_hash)
	{
	   tell_comparison (the_file, sha1sum, compare_hash, 20);
	}
        else
	{
           for( j = 0; j < 20; j++ )
           {
              printf( "%02x", sha1sum[j] );
           }

           printf( "  %s\n", the_file );
       }
    }
/**********************************************************************/
         break;
      case sha256 :
         if (debug) printf (">>> using sha256\n");
/**********************************************************************/
    {
	if (!strcmp ("/dev/stdin", the_file))
	{
	   f = stdin;
        }
	else
	{
           if( ! ( f = fopen( the_file, "rb" ) ) )
           {
              printf (">>> serious warning: fopen %s - no such file\n", the_file);
              number_warnings++;
              file_missing++;
              return( 1 );
           }
        }
        f_is_open = 1;

        sha256_starts( &ctx_sha256 );

        /**************************************************************/
	if (i_have_passphrase)
	{
	   if (debug) printf (">>> prefixing passphrase\n");
	   strcpy ( buf, passphrase );
           lx = strlen (passphrase);
           if ( ( i = fread( &buf [lx], 1, sizeof( buf ) - lx, f ) ) > 0 )
           {
               sha256_update( &ctx_sha256, buf, i+lx );
           }
	}
        /**************************************************************/

        while( ( i = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
        {
            sha256_update( &ctx_sha256, buf, i );
        }

        sha256_finish( &ctx_sha256, sha256sum );

	if (compare_hash)
	{
	   tell_comparison (the_file, sha256sum, compare_hash, 32);
	}
        else
	{
           for( j = 0; j < 32; j++ )
           {
              printf( "%02x", sha256sum[j] );
           }

           printf( "  %s\n", the_file );
       }
    }
/**********************************************************************/
         break;
      default     : printf ("???");
         printf (">>> error: you must really provide a hash type.\n");
         exit (1);
         break;
   }
   if (f_is_open) fclose (f);
}



void help (int detail) /* detail not used, so far - maybe we do not need it */

{
   printf ("\n");
   printf ("+--------------------------------------------------------------------+\n");
   printf ("|                                                                    |\n");
   printf ("|   %s   |\n", HEADER);
   printf ("|                                                                    |\n");
   printf ("|   usage:                                                           |\n");
   printf ("|                                                                    |\n");
   printf ("|   csum <hash-option> <passphrase-option> <file-list>               |\n");
   printf ("|                                                                    |\n");
   printf ("|   hash-option : --md5    --sha1    --sha256                        |\n");
   printf ("|                                                                    |\n");
   printf ("|   traditional behaviour                                            |\n");
   printf ("|   hash-option : --md5sum --sha1sum --sha256sum                     |\n");
   printf ("|                                                                    |\n");
   printf ("|   passphrase-option : -p <passphrase> --passprase <passphrase>     |\n");
   printf ("|                       --ask-passphrase                             |\n");
   printf ("|                                                                    |\n");
   printf ("|   If no file-list is given, stdin is assumed                       |\n");
   printf ("|                                                                    |\n");
   printf ("+--------------------------------------------------------------------+\n");
   printf ("|                                                                    |\n");
   printf ("|   csum ... -c <check-file>        checks against hashes            |\n");
   printf ("|   csum ... --check <check-file>   in check-file                    |\n");
   printf ("|                                                                    |\n");
   printf ("+--------------------------------------------------------------------+\n");
   printf ("|                                                                    |\n");
   printf ("|   Further possible options:                                        |\n");
   printf ("|                                                                    |\n");
   printf ("|   -h --help                 this help-info                         |\n");
   printf ("|   -v --verbose              csum is more talkative                 |\n");
   printf ("|   -d --debug                debug info                             |\n");
   printf ("|                                                                    |\n");
   printf ("+--------------------------------------------------------------------+\n");
   printf ("|                                                                    |\n");
   printf ("|   If called under the name of md5sum or sha1sum or sha256sum,      |\n");
   printf ("|   traditional behaviour is assumed.                                |\n");
   printf ("|                                                                    |\n");
   printf ("|   If called under the name of cmd5sum or csha1sum or csha256sum,   |\n");
   printf ("|   passphrase behaviour is assumed.                                 |\n");
   printf ("|                                                                    |\n");
   printf ("+--------------------------------------------------------------------+\n");
   printf ("\n");
}



int verify_against_check_file (char *the_file)

{
    FILE *f;
    int f_is_open = 0;
    int cl = 0;
    unsigned char *start_hashsum;
    unsigned char *start_filename;

   if( ! ( f = fopen ( the_file, "rb" ) ) )
   {
      printf (">>> serious warning: fopen %s - no such file\n", the_file);
      number_warnings++;
      file_missing++;
      return( 1 );
   }
   f_is_open = 1;

   while (!feof (f))
   {
      fgets (check_file_line, CHECKFILELINELENGTH, f);
   /* if (debug) printf (">>> check_file_line (a) = %s\n", check_file_line); */

      if (!feof (f))
      {

	 /*************************************************************/
         /*                                                           */
         /* A very primitive lexer indeed! And a bad one, at that.    */
	 /* Whether the hash consists of hex-digits only is checked   */
         /* here, but whether the following field is a valid path and */
         /* file name is determined by fopen. And whether there is a  */
         /* path and file at all is checked creatively.               */
         /*                                                           */
	 /*************************************************************/

         start_hashsum = check_file_line;

         while ((' ' != check_file_line [cl]) && (10 != check_file_line [cl]))
         {
	    switch (check_file_line [cl])
	    {
	       case '0' :
	       case '1' :
	       case '2' :
	       case '3' :
	       case '4' :
	       case '5' :
	       case '6' :
	       case '7' :
	       case '8' :
	       case '9' :
	       case 'a' :
	       case 'b' :
	       case 'c' :
	       case 'd' :
	       case 'e' :
	       case 'f' : break;
	       default  :
                  printf (">>> error: expected hex-digit in checkfile.\n");
                  exit (1);
            }
            cl++;
	 }
         check_file_line [cl] = 0;
         switch (cl)
	 {
	    case 32 :
	       if (md5 != hash)
	       {
	          if (debug) printf (">>> check_file conforms to md5\n");
		  fprintf (stderr, ">>> Warning: switch to md5\n"); number_warnings++;
                  number_warnings++;
		  hash = md5;
	       }
	       break;
	    case 40 :
	       if (sha1 != hash)
	       {
	          if (debug) printf (">>> check_file conforms to sha1\n");
		  fprintf (stderr, ">>> Warning: switch to sha1\n"); number_warnings++;
                  number_warnings++;
		  hash = sha1;
	       }
	       break;
	    case 64 :
	       if (sha256 != hash)
	       {
	          if (debug) printf (">>> check_file conforms to sha256\n");
		  fprintf (stderr, ">>> Warning: switch to sha256\n"); number_warnings++;
                  number_warnings++;
		  hash = sha256;
	       }
	       break;
	    default :
	       printf (">>> error: check_file conforms to nothing\n");
               exit (1);
	       break;
	 }
         cl++;
         while (' ' == check_file_line [cl]) cl++;
         if ('*' ==  check_file_line [cl]) cl++; /* compatibility to other ***sum programs */
         if (10 ==  check_file_line [cl])
	 {
	    printf (">>> error: missing file name in check_file\n");
            exit (1);
	 }
         start_filename = &(check_file_line [cl]);
         while ((0 != check_file_line [cl]) && (cl < CHECKFILELINELENGTH)) cl++;
         if (10 == check_file_line [cl - 1]) /* because we do not desire the line end */
         {
            check_file_line [cl - 1] = 0;
         }
         else
         {
            check_file_line [cl] = 0;
         }

         /* now we have start_hashsum and start_filename well prepared */

         if (debug) printf (">>> check_file_line (b) = %s  %s\n", start_hashsum, start_filename);

         (void) /* for now */ digest_input (start_filename, start_hashsum);

         cl = 0;
      }
   }

   if (f_is_open) fclose (f);
}



/**************************************************************************************************/
/*                                                                                                */
/*   Parameter     :                                                                              */
/*                                                                                                */
/*   the_file      : The file name in ASCI                                                        */
/*   computed_hash : The hash value, just computed, in raw bits                                   */
/*   compare_hash  : The hash value from the check-file, in ASCII                                 */
/*   comp_length   : How much is to be compared                                                   */
/*                                                                                                */
/**************************************************************************************************/

void tell_comparison (unsigned char *the_file,
                      unsigned char *computed_hash,
                      unsigned char *compare_hash,
                      int           comp_length)

{
   unsigned char comp_str [CHECKFILELINELENGTH];
   int j;

   for ( j = 0; j < comp_length; j++ )
   {
      sprintf (&(comp_str [2 * j]), "%02x", computed_hash [j] );
   }

   /* sprintf (comp_str, "  %s\n", the_file ); */

   if (debug)
   {
       printf (">>> -------------------------------------\n");
       printf (">>> comp_length * 2 = %i\n", comp_length * 2);
       printf (">>> %s\n", comp_str);
       printf (">>> %s\n", compare_hash);
       printf (">>> -------------------------------------\n");
   }

   if (strncmp (comp_str, compare_hash, comp_length * 2))
   {
       if (0 < verbose) printf ("   bullshit : ");
       file_missmatch++;
   }
   else
   {
       if (0 < verbose) printf ("   OK       : ");
       file_match++;
   }

   if (0 < verbose) printf ("%s\n", the_file);
}


