/******************************************************************************
 * program:     wp2latex                                                      *
 * function:    Decrypt encrypted WordPerfect 3.x, 4.x and 5.x files          *
 * modul:       pass1C45.cc                                                   *
 * description: This modul decrypts file and call a regular wpX.Y converter.  *
 * licency:     GPL		                                              *
 ******************************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "stringa.h"
#include "wp2latex.h"
#include "struct.h"


#if defined(__UNIX__)||defined(__DJGPP__)
 #include <unistd.h>
#endif


class TconvertedPass1_xWP5:public TconvertedPass1
     {
public:
     virtual int Convert_first_pass(void);
     };
class TconvertedPass1_xWP4:public TconvertedPass1
     {
public:
     virtual int Convert_first_pass(void);
     };
class TconvertedPass1_xWP3:public TconvertedPass1
     {
public:
     virtual int Convert_first_pass(void);
     };


/*Register translators here*/
TconvertedPass1 *Factory_xWP3(void) {return new TconvertedPass1_xWP3;}
TconvertedPass1 *Factory_xWP4(void) {return new TconvertedPass1_xWP4;}
TconvertedPass1 *Factory_xWP5(void) {return new TconvertedPass1_xWP5;}

FFormatTranslator FormatWP3encrypted("WP3.x encrypted",Factory_xWP3);
FFormatTranslator FormatWP4encrypted("WP4.x encrypted",Factory_xWP4); //also works for WP1.x
FFormatTranslator FormatWP5encrypted("WP5.x encrypted",Factory_xWP5);

/*----------Decrypting wp file--------------*/
extern string wpd_password;
void ObtainPassword(string & password);

const char *InputPasswordAgain="\nWarning! Password checksum does not match file checksum! Again Y/n/i?";


/** This function computes checksum for the given password. */
static uint16_t CheckSum(char *PassWord)
{
uint16_t csum;

csum=0;
if(PassWord==NULL) return(0);

while(*PassWord!=0)
	{
	if( *PassWord>='a' && *PassWord<='z')    /* convert to upper case */
			*PassWord -= 'a'-'A';

	csum = ( (csum >> 1) | ( csum << 15) ) ^ ( ((uint16_t)*PassWord)<<8 );
	PassWord++;
	}
return(csum);
}


/** Decrypt a block encrypted by WP simple encryption. */
static void DecryptWP345(FILE *Encrypted, FILE *Decrypted, const string & PassWord, FILE *ErrorFile)
{
char *buf;
uint16_t idx;                     /* index into buffer */
uint16_t pidx;                    /* index down password */
uint8_t xmask;                   /* XOR mask */
uint32_t len;
uint32_t pos = ftell(Encrypted);
uint32_t fsize = FileSize(Encrypted);

 if(ErrorFile==NULL) ErrorFile=stderr;

 if(PassWord.isEmpty())
 {
   if(ErrorFile)
     fprintf(ErrorFile,_("\nError: Password is empty."));
   return;
 }

 if((buf=(char *)malloc(4096))==NULL)
 {
   if(ErrorFile)
     fprintf(ErrorFile,_("\nFatal: No memory."));
   return;
 }

 pidx = 0;                      /* start of password */
 xmask = length(PassWord)+1;	/* start at password length+1 */

 if(Verbosing >= 1) printf(_("\nDecrypting ...\n\n"));

 while(pos<fsize)
	 {
	 if(fsize-pos > 4096) len = 4096;
			 else len = fsize-pos;

	 if(fread( buf, 1, len, Encrypted ) !=len)
		 {
                 if(ErrorFile)
		   fprintf(ErrorFile,_("\nError: Unexpected end of the input file."));
		 break;
		 }

	  for( idx=0; idx<len; idx++ )	/* now decrypt */
		 {
		 buf[idx] ^= PassWord()[pidx++] ^ xmask++;
		 if( pidx == length(PassWord) ) pidx = 0;
		 }

	   if(Decrypted)
		 {
		 if(fwrite( buf, 1, len, Decrypted ) !=len)
			 {
			 fprintf(ErrorFile,_("\nError: Couldn't write %lu bytes to the temporary file.\n"), (long unsigned)len);
			 break;
			 }
		 }
		 pos += len;
	 } /* while */

 if(buf!=NULL) free(buf);
}


/**WP5.x decryptor*/
int TconvertedPass1_xWP5::Convert_first_pass(void)
{
#ifdef DEBUG
  fprintf(log,"\n#Convert_pass1_WP5_encrypted() ");fflush(log);
#endif
uint16_t encryption;
string temp_filename;
uint32_t DocumentStart;	/* position in file */
FILE *DecryptedFile = NULL;
uint8_t SmallBuf[16];
TconvertedPass1 *cqPass1;
int RetVal=0;

  cqPass1 = GetConverter("WP5.x");
  if(cqPass1==NULL)
	{
	RunError(0x202,"WP5.x");  /* unsupported module wp5 */
	return(-1);
	}

  DocumentStart = ftell(wpd);
  fseek(wpd,12,SEEK_SET);	//check for encrypted documents

  RdWORD_HiEnd(&encryption, wpd);
  if(encryption!=0)
	{
	if(CheckSum(wpd_password())!=encryption)
	   {
           if(length(wpd_password)<=0) 
	       ObtainPassword(wpd_password);	// 1st asking for password, when it is not given in command line.

	   while(CheckSum(wpd_password())!=encryption)
	      {
              if(Verbosing<=-1) return -1;	// Silence mode selected, return immediatelly.

	      switch(YesNoIgnore(_(InputPasswordAgain)))
		 {
		 case 'Y':break;
		 case 'N':return -1;
		 case 'I':goto Ignore;
		 }
              ObtainPassword(wpd_password);
	      }
	   }
Ignore:
	temp_filename = copy(wpd_filename,0,
		GetExtension(wpd_filename())- wpd_filename()) + ".$$$";

	if((DecryptedFile=OpenWrChk(temp_filename,"wb",err))==NULL)
	{
	  RunError(6,temp_filename);
	  return -1;
	}

	fseek(wpd,0l,SEEK_SET);
	fread( SmallBuf, 1, 16, wpd);
	SmallBuf[12] = SmallBuf[13] = 0;		/* reset encryption=0 */
	fwrite( SmallBuf, 1, 16, DecryptedFile );	/* write out first part of WP5.1 file */

	DecryptWP345(wpd,DecryptedFile,wpd_password,err);
	} /* ver5 */


  if(DecryptedFile!=NULL) /* Now perform normal conversion */
  {
    fclose(DecryptedFile);
    if((DecryptedFile=fopen(temp_filename,"rb"))==NULL) return(0);

    fseek(DecryptedFile,DocumentStart,SEEK_SET);
    cqPass1->InitMe(DecryptedFile, table, strip, log, err);
    RetVal=cqPass1->Convert_first_pass();
    fclose(DecryptedFile);

#ifndef DEBUG
    if(EraseStrip) unlink(temp_filename);
#endif		// DEBUG
  }
  delete cqPass1;

return(RetVal);
}


/**WP3.x decryptor*/
int TconvertedPass1_xWP3::Convert_first_pass(void)
{
#ifdef DEBUG
  fprintf(log,"\n#Convert_pass1_WP3_encrypted() ");fflush(log);
#endif
uint16_t encryption;
string temp_filename;
uint32_t DocumentStart;	/* position in file */
FILE *DecryptedFile=NULL;
uint8_t SmallBuf[16];
TconvertedPass1 *cqPass1;
int RetVal=0;

  cqPass1 = GetConverter("WP3.x");
  if(cqPass1==NULL)
	{
	RunError(0x202,"WP3.x");  /* unsupported module wp3 */
	return(-1);
	}

  DocumentStart=ftell(wpd);
  fseek(wpd,12,SEEK_SET);	//check for encrypted documents

  RdWORD_LoEnd(&encryption, wpd);
  if(encryption!=0)
	{
	if(CheckSum(wpd_password())!=encryption)
	   {
           if(length(wpd_password)<=0) 
	       ObtainPassword(wpd_password);

	   while(CheckSum(wpd_password())!=encryption)
	      {
              if(Verbosing<=-1) return -1;
	      switch(YesNoIgnore(_(InputPasswordAgain)))
		 {
		 case 'Y':break;
		 case 'N':return -1;
		 case 'I':goto Ignore;
		 }
              ObtainPassword(wpd_password);
	      }
	   }
Ignore:
	temp_filename = copy(wpd_filename,0,
		GetExtension(wpd_filename())- wpd_filename()) + ".$$$";

	if((DecryptedFile=OpenWrChk(temp_filename,"wb",err))==NULL)
	{
	  RunError(6,temp_filename);
	  return -1;
	}

	fseek(wpd,0l,SEEK_SET);
	for(unsigned i=0; i<(DocumentStart/16); i++) /* copy everythink till to DocumentStart */
          {
	  fread(SmallBuf, 1, 16, wpd);
	  if(i==0)
	    SmallBuf[12] = SmallBuf[13] = 0;        /* reset encryption=0 */
	  fwrite(SmallBuf, 1, 16, DecryptedFile);
          }
	fread( SmallBuf, 1, DocumentStart%16, wpd);
	fwrite( SmallBuf, 1, DocumentStart%16, DecryptedFile);

	DecryptWP345(wpd,DecryptedFile,wpd_password,err);
	} /* ver3 */

  if(DecryptedFile!=NULL)	/* Now perform a normal conversion. */
    {
    fclose(DecryptedFile);
    if((DecryptedFile=fopen(temp_filename,"rb"))==NULL) return(0);

    fseek(DecryptedFile,DocumentStart,SEEK_SET);
    cqPass1->InitMe(DecryptedFile, table, strip, log, err);
    RetVal = cqPass1->Convert_first_pass();
    fclose(DecryptedFile);

#ifndef DEBUG
    if(EraseStrip) unlink(temp_filename);
#endif		// DEBUG
    }
  delete cqPass1;

return(RetVal);
}


/** WP1.x and WP4.x decryptor. */
int TconvertedPass1_xWP4::Convert_first_pass(void)
{
uint16_t encryption, checksum;
string temp_filename;
FILE *DecryptedFile=NULL;
TconvertedPass1 *cqPass1;
int RetVal=0;
const char *Module;
  
  fseek(wpd,4,SEEK_CUR);	// FF FE 61 61
  RdWORD_HiEnd(&encryption, wpd);
  if(encryption!=0)
  {
    checksum = CheckSum(wpd_password());
    if(checksum!=encryption && ((checksum>>8)|((checksum<<8)&0xFF00))!=encryption)
    {
      if(length(wpd_password)<=0) 
          ObtainPassword(wpd_password);

      checksum = CheckSum(wpd_password());
      while(checksum!=encryption && ((checksum>>8)|((checksum<<8)&0xFF00))!=encryption)
      {
        if(Verbosing<=-1) return -1;	// Silence mode selected, return immediatelly.

        switch(YesNoIgnore(_(InputPasswordAgain)))
        {
           case 'Y':ObtainPassword(wpd_password);
			  checksum = CheckSum(wpd_password());
			  break;
          case 'N':return -1;
          case 'I':goto Ignore;
        }
      }
    }
Ignore:
    temp_filename = copy(wpd_filename,0,
		     GetExtension(wpd_filename)- wpd_filename()) + ".$$$";
    if((DecryptedFile=OpenWrChk(temp_filename,"wb",err))!=NULL)
    DecryptWP345(wpd,DecryptedFile,wpd_password,err);
  } /* ver4 */
  else
  {
    RunError(0x203);  /* something gets wrong - no idea */
    return -1;
  }

  if(DecryptedFile!=NULL)
  {
    fclose(DecryptedFile);
    if((DecryptedFile=fopen(temp_filename,"rb"))==NULL)   //reopen file for reading
	return(-1);
  }
  else
  {
    RunError(6,temp_filename());
    return -1;
  }

  if((checksum>>8) == (checksum&0xFF))
  {
    CheckFileFormat(DecryptedFile,FilForD);
    fseek(DecryptedFile,0,SEEK_SET);	    //rewind document
    //printf("\nDocumentType: %s, Converter %s, Probability %g",chk(FilForD.DocumentType),chk(FilForD.Converter),FilForD.Probability);
    Module = FilForD.Converter;
    if(Module==NULL || *Module==0)
    {
      Module="WP4.x";	// WP4 has lo endian
      if(err!=NULL)
      {   /**/
        fprintf(err,_("\nAutodetection is unsure, default: %s."), chk(Module));
      }
    }
  }
  else
  {
    if(checksum==encryption)
      Module="WP4.x";	// WP4 has lo endian
    else
      Module="WP1.x";	// WP1 has swapped bytes - high endian
  }

  cqPass1=GetConverter(Module);
  if(cqPass1==NULL)		// module is not present
	{
	if(DecryptedFile!=NULL)
		{fclose(DecryptedFile);DecryptedFile=NULL;}
#ifndef DEBUG
        if(EraseStrip) unlink(temp_filename);
#endif		// DEBUG
	RunError(0x202,Module);  /* unsupported module WP1 or WP4 */
	return -1;
	}
    
  if(DecryptedFile!=NULL) /* Now perform normal conversion */
    {
    cqPass1->InitMe(DecryptedFile, table, strip, log, err);
    RetVal = cqPass1->Convert_first_pass();
    fclose(DecryptedFile);
#ifndef DEBUG
    if(EraseStrip) unlink(temp_filename);
#endif		// DEBUG
    }
  delete cqPass1;

return(RetVal);
}

/*----- End of pass1c345. -----*/
