/****************************************************************************
cipher.c
A ciphering tool package module
****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "kernel.h"
#include "swis.h"
#include "os_utils.h"
#include "cipher.h"
#include "md5.h"
#include "idea.h"

/* SWIs defined by module.  These are also defined in cmhg */

#define  SWI_MD5_Start          0
#define  SWI_MD5_Update         1
#define  SWI_MD5_End            2
#define  SWI_IDEA_KeyInit       3
#define  SWI_IDEA_Cipher        4
#define  SWI_IDEA_Decode        5

/* *Commands defined by module.  These are also defined in CModuleHdr */

#define  CMD_md5                0
#define  CMD_idea               1

/* Data structure etc for the cipher file */

#define  CIPHER_TYPE            0x180
#define  CIPHER_IDENT           0x52485043 /* "CPHR" */

struct fileheader
{
   unsigned int ident;
   unsigned int version;
   unsigned int size;
   unsigned int load;
   unsigned int exec;
};

/****************************************************************************idea_keyinit()
Initialises the keys for md5 for encrypt or decrypt
****************************************************************************/

void
idea_keyinit(word16 *keys, idea_keyblockptr context, int decrypt)
{
   idea_keyblock temp;

   if (decrypt)
   {
      en_key_idea(keys, temp);
      de_key_idea(temp, context);
   }
   else
   {
      en_key_idea(keys, context);
   }
}


/****************************************************************************
module_swi()
Is the SWI handler for the module
****************************************************************************/

_kernel_oserror *
module_swi(int swi_no, _kernel_swi_regs *r, void *private_word)
{
    UNUSED(private_word);

    switch (swi_no)
    {
       /* r0 = block pointer */
       case SWI_MD5_Start:
          MD5Init( (MD5_CTX *) r->r[0] );
          break;

       /* r0 = block pointer, r1 = text pointer, r2 = text length */
       case SWI_MD5_Update:
          MD5Update( (MD5_CTX *) r->r[0], (unsigned char *) r->r[1],
                   (unsigned int) r->r[2] );
          break;

       /* r0 = block pointer */
       case SWI_MD5_End:
          MD5Final( (MD5_CTX *) r->r[0] );
          r->r[0] = (int) &( (MD5_CTX *) r->r[0] )->digest[0];
          break;

       /* r0 = block pointer, r1 = key pointer, r2 = (boolean) decrypt*/
       case SWI_IDEA_KeyInit:
          idea_keyinit((word16 *) r->r[1], (idea_keyblockptr) r->r[0],
                       (int) r->r[2]);
          break;

       /* r0 = block pointer, r1 = in pointer (64 bits), r2 = out pointer */
       case SWI_IDEA_Cipher:
          cipher_idea((word16 *) r->r[1], (word16 *) r->r[2],
                      (idea_keyblockptr) r->r[0]);
          break;

       case SWI_IDEA_Decode:
          break;

       default:
          return &error_unknownswi;
          break;
    }

    return 0;
}

/****************************************************************************
md5_file_buffer()
does md5 on the file  supplied into the memory supplied
****************************************************************************/

#define	OLD_MD5	0
#if OLD_MD5
#  define MD5_FILE_BUFFER_SIZE	256
#else
#  define MD5_FILE_BUFFER_SIZE	(16*1024)
#endif

_kernel_oserror *
md5_file_buffer(char *file, void *pdigest)
{
   unsigned char *buffer = 0;
   MD5_CTX md5file;
   int bytesread, input, eofstatus;

   return_os_error( os_fopen(file, os_fopen_in, &input) );

   buffer = malloc(MD5_FILE_BUFFER_SIZE);
   if (buffer == 0)
   {
       _last_os_error = &error_nomemory;
       goto close;
   }

   MD5Init(&md5file);

   while (os_feof(input, &eofstatus), !eofstatus)
   {
      if_os_error( os_fread(buffer, MD5_FILE_BUFFER_SIZE, input, &bytesread) )
         goto close;
#if OLD_MD5
      for (; bytesread < MD5_FILE_BUFFER_SIZE; bytesread++)
         buffer[bytesread] = 0;
      MD5Update( &md5file, buffer, MD5_FILE_BUFFER_SIZE);
#else
      MD5Update( &md5file, buffer, bytesread);
#endif
   }

   MD5Final(&md5file);
   memcpy( pdigest, md5file.digest, sizeof(md5file.digest) );
close:
   if (buffer)
       free(buffer);
   ignore_os_error( os_fclose(input) );
   return_last_os_error;
   return 0;
}


/****************************************************************************
md5_buffer_buffer()
does md5 on the buffer supplied into the memory supplied
****************************************************************************/

_kernel_oserror *
md5_buffer_buffer(void *inbuffer, int inbuffersize, void *outbuffer)
{
   MD5_CTX md5file;

   MD5Init(&md5file);
   MD5Update(&md5file, inbuffer, inbuffersize );
   MD5Final(&md5file);
   memcpy( outbuffer, md5file.digest, sizeof(md5file.digest) );
   return 0;
}

/****************************************************************************
md5_file()
does md5 on the file supplied into the file supplied
****************************************************************************/

_kernel_oserror *
md5_file(char *inputfile, char *outputfile)
{
   int output;
   char buffer[16];

   md5_file_buffer( inputfile, buffer );
   return_os_error( os_fopen(outputfile, os_fopen_out, &output) );
   if_os_error( os_fwrite(buffer, 16, output) )
      goto close;
close:
   ignore_os_error( os_fclose(output) );
   return_last_os_error;
   return 0;
}


/****************************************************************************
md5_sum()
does md5 on the file supplied and prints the result in a similar fashion to md5sum
****************************************************************************/

_kernel_oserror *
md5_sum(char *inputfile)
{
   char buffer[16];
   int i;

   md5_file_buffer( inputfile, buffer );

   for (i = 0; i < 16; i++)
       printf("%02x", buffer[i]);
   printf("  %s\n", inputfile);

   return 0;
}


/****************************************************************************
idea_file()
Does IDEA encryption decryption for one file
****************************************************************************/

_kernel_oserror *
idea_file(char *input, char *output, char *password, char *file, int encrypt, int decrypt)
{
   char inpassword[256], pdigest[16], block[8];
   int temporary, decrypting, load, exec, size, type;
   idea_keyblock context;
   int inputchannel, outputchannel, eofstatus, bytesread;
   struct fileheader header;

   if (input == 0)
      return &error_badargs;
   temporary = (output == 0);
   if (temporary)
      output = CIPHER_TEMP;
   if (encrypt !=0 && decrypt !=0)
      return &error_noteandd;
   if (password != 0 && file != 0)
      return &error_notbothpasswords;

   if (password == 0 && file == 0)
   {
      return_os_error( read_line("Enter Password: ", inpassword,
                                 sizeof(inpassword)) );
      password = inpassword;
   }

   /* BUT if no password, but file, the filename will be in password
      unless -file specified ... when it gives 'Command line buffer full'
      Seems to give same error if -p and -f both specified - should err
      Ahah! Found & fixed error in c.os_utils buffer overflow check.
      Also forced password & file to have keywords specified.      v0.08  */

   if (password != 0)
   {
      return_os_error( md5_buffer_buffer(password, strlen(password),
                                         pdigest) );
   }
   else
   {
      return_os_error( md5_file_buffer(file, pdigest) );
   }

   return_os_error( readfileinfo(input, &load, &exec, &size, &type) );

   if (encrypt == 0 && decrypt == 0)
      decrypting = (type == CIPHER_TYPE);
   else
      decrypting = (decrypt != 0);

/* open files */

   return_os_error( os_fopen(input, os_fopen_in, &inputchannel) );
   if_os_error( os_fopen(output, os_fopen_out, &outputchannel) )
      goto close1;

   if (decrypting)
   {
      if_os_error( os_fread(&header, sizeof(header), inputchannel,
                            &bytesread) )
         goto close;
      if (bytesread != sizeof(header) || header.ident != CIPHER_IDENT ||
          header.version > 0)
      {
         if (header.version > 0)
            _last_os_error = &error_unknownheader;
         else
            _last_os_error = &error_badheader;
         goto close;
      }
      size = header.size;
   }
   else
   {
      header.ident = CIPHER_IDENT;
      header.version = 0;
      header.size = size;
      header.load = load;
      header.exec = exec;
      if_os_error( os_fwrite(&header, sizeof(header), outputchannel) )
         goto close;
   }


/* Now do the ciphering */

   idea_keyinit((word16 *) pdigest, context, decrypting);

   while (os_feof(inputchannel, &eofstatus), !eofstatus)
   {
      if_os_error( os_fread(block, sizeof(block), inputchannel, &bytesread) )
         goto close;
      for (; bytesread < sizeof(block); bytesread++)
         block[bytesread] = 0;
      cipher_idea((word16 *) block, (word16 *) block, context);
      if_os_error( os_fwrite(block, sizeof(block), outputchannel) )
         goto close;
   }

/* set extent if necessary */
   if (decrypting)
      if_os_error( os_setextent(outputchannel, header.size) )
         goto close;

/* close files */
close:
   ignore_os_error( os_fclose(outputchannel) );
close1:
   ignore_os_error( os_fclose(inputchannel) );
   if (_last_os_error != 0)
   {
      /* if we had an error, remove the output */
      ignore_os_error( os_remove(output) );
      return _last_os_error;
   }

   if (decrypting)
   {
      /* need to set type of file (and load exec) to original */
      return_os_error( setfileloadexec( output, header.load, header.exec ) );
   }
   else
   {
      /* need to set type of file to Cipher */
      return_os_error( setfiletype( output, CIPHER_TYPE ) );
   }


/* if temporary then delete input and rename output to input */

   if (temporary)
   {
      if_os_error( os_remove(input) )
         return &error_cantdeletesource;
      if_os_error( os_rename(output, input) )
         return &error_rename;
   }

   return 0;
}

/****************************************************************************
module_cmd()
Is the command handler for the module
****************************************************************************/

/* Added /k to password and file, because if password is not used and file
   is used without keyword, it is assumed to be the password, NOT the file
   name!                                                               v0.08
char idea_arg_string[] = "input/a,output,password,file,decrypt/s,encrypt/s";
*/
char idea_arg_string[] = "input/a,output,password/k,file/k,decrypt/s,encrypt/s"; /* ~v0.08 */
enum idea_args { idea_input, idea_output, idea_password, idea_pfile,
                idea_decrypt, idea_encrypt };

char md5_arg_string[] = "input/a,output";
enum md5_args { md5_input, md5_output };

_kernel_oserror *
module_cmd(char *arg_string, int argc, int cmd_no, void *pw)
{
    int buffer[256];

    UNUSED(pw);
    UNUSED(argc);

    switch (cmd_no)
    {
       case CMD_md5:
          return_os_error( readargs(md5_arg_string, arg_string,
                                    buffer, sizeof(buffer)) );
          if (buffer[md5_output])
          {
              return_os_error( md5_file( (char *) buffer[md5_input],
                                         (char *) buffer[md5_output]) );
          }
          else
          {
              /* if no ouput file then print to the screen */
              return_os_error( md5_sum( (char *) buffer[md5_input] ) );
          }
          break;

       case CMD_idea:
          return_os_error( readargs(idea_arg_string, arg_string,
                                    buffer, sizeof(buffer)) );
          return_os_error( idea_file( (char *) buffer[idea_input],
                                      (char *) buffer[idea_output],
                                      (char *) buffer[idea_password],
                                      (char *) buffer[idea_pfile],
                                      (int)    buffer[idea_encrypt],
                                      (int)    buffer[idea_decrypt]) );
          break;
    }
    return 0;
}


/****************************************************************************
module_service()
Is the service handler for the module
****************************************************************************/

/* A service handler is not needed by this module
void
module_service(int service_number, _kernel_swi_regs *r, void *private_word){}
*/


/****************************************************************************
module_initialise()
Is the initialise code for the module on plugging in the module
****************************************************************************/

int
module_initialise(char *cmd_tail, int podule_base, void *private_word)
{
    UNUSED(cmd_tail);
    UNUSED(podule_base);
    UNUSED(private_word);
    return 0;
}


/****************************************************************************
main()
This is run when the module is entered or run
****************************************************************************/

int
main(int argc, char *argv[])
{
    UNUSED(argc);
    UNUSED(argv);

    printf("%s module version %s loaded.\nCompiled on %s at %s.\n",
           MODULE_NAME, VERSION, __DATE__, __TIME__);
    return 0;
}
