/*
 * XOR File Destroyer v.1 Beta 1
 *
 * AUTHOR:
 *  Robert Fahy <rfahy@ymail.com>
 *
 * DATE:
 *  May 2011 (initial release)
 *
 * ABOUT:
 *  - previous version was never released because it had entered the phase of "development hell",
 *   so I decided to begin anew and write a dumber version with the sole requirement that
 *   it "worked", whilst the process of improving robustness and security would follow
 *   only after a working version was out.
 *
 * TODO:
 *  - remove duplicate code
 *  - verify against using same file for input and output, in cases 2 and 3?
 *  - add wildcard operation
 *
 */

// include files
#include <stdio.h>
#include <stdlib.h>

// preprocessor definitions with obvious purposes
#define NAME_VERSION			"XOR-FD v.1 Beta 1"
#define GENERIC_FILENAME		"<xorcorrupt.elf>"
#define GENERIC_MESSAGE			"<Error text is unavailable.>"
#define BUFFER_SIZE				4096

// ABOUT: according to the manpages this "will turn off_t into a 64-bit type".
//  translation: it allows us to work with larger files
#define _FILE_OFFSET_BITS		64

// evil global variables
char *_global_padding_ = NULL;
size_t _global_padding_length_ = 0;

// declarations without definitions (aka prototypes)
char * encrypt_static(char *data, size_t data_length);
char * encrypt_normal(char *data, const char *padding, size_t length);
int compute(const char *argument1, const char *argument2, const char *argument3);
int main(int argc, char **argv); // for completeness of this "index"

char * encrypt_static(char *data, size_t length)
// ABOUT: uses XOR operation to pad contents of `data' with contents of `_global_padding_' for given `length'
// WARN: this version loops `_global_padding_' and it assumes that it doesn't change.
//  it also takes into account `_global_padding_length_'. this is a design flaw I'm aware of.
{
	static size_t i=0;

	if (data != NULL)
		while (length--)
			if (i < _global_padding_length_)
				*data++ ^= _global_padding_[i++];
			else
				i=0;

	return data;
} // ### char * encrypt_static(char *data, size_t data_length)

char * encrypt_normal(char *data, const char *padding, size_t length)
// ABOUT: uses XOR operation to pad contents of `data' with contents of `padding' for given `length'
// WARN: both arrays need to have same `length'
{
	if (data != NULL &&
		padding != NULL)
		while (length--)
			*data++ ^= *padding++;

	return data;
} // ### char * encrypt_normal(char *data, const char *padding, size_t length)

int compute(const char *argument1, const char *argument2, const char *argument3)
// ABOUT: verifies the arguments and takes the correct course of action
{
	FILE // file streams
		*input_file_ = NULL,
		*output_file_ = NULL,
		*key_file_ = NULL;

	char
		*rw_buffer_ = NULL,		// read/write file buffer
		*key_buffer_ = NULL;	// key file buffer

	size_t // to keep track of how many bytes we read or wrote
		rw_length_ = 0,
		key_length_ = 0;

	if (argument1 == NULL)
	{
		//! PRINT THE ERROR: "Nice try."
		return EXIT_FAILURE;
	}
	else
		if (argument2 == NULL)
		{
			if (argument3 == NULL)	// NOTE: case 1: input file overwritten, key from standard input
			{
				input_file_ = fopen(argument1, "r+b"); //! CHECK FOR ERROR
				_global_padding_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR
				rw_buffer_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR

				// NOTE: we don't check this for errors because the program will jam waiting anyway,
				//  if no data is fed through standard input (stdin)
				_global_padding_length_ = fread(_global_padding_, sizeof (char), BUFFER_SIZE, stdin);

				while (!feof(input_file_))
					if ((rw_length_ = fread(rw_buffer_, sizeof (char), BUFFER_SIZE, input_file_))) //! CHECK FOR ERROR
					// did we read something? yes...
					{
						encrypt_static(rw_buffer_, rw_length_); //! CHECK FOR ERROR
						fseeko(input_file_, (off_t)-rw_length_, SEEK_CUR); //! CHECK FOR ERROR
						fwrite(rw_buffer_, sizeof (char), rw_length_, input_file_); //! CHECK FOR ERROR + ferror(rw_buffer_)
						fflush(input_file_);
					}
					else // no... we read 0 bytes
						break;

				fclose(input_file_); //! MOVE TO SPOT
				free(_global_padding_); //! MOVE TO SPOT
				_global_padding_ = NULL; //! MOVE TO SPOT
				free(rw_buffer_); //! MOVE TO SPOT
				rw_buffer_ = NULL; //! MOVE TO SPOT
			}
			else
			{
				//! PRINT THE ERROR: "Nice try."
				return EXIT_FAILURE;
			}
		}
		else
			if (argument3 == NULL)	// NOTE: case 2: input file, output file, key from standard input
			{
				input_file_ = fopen(argument1, "rb"); //! CHECK FOR ERROR
				output_file_ = fopen(argument2, "wb"); //! CHECK FOR ERROR
				_global_padding_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR
				rw_buffer_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR

				// NOTE: we don't check this for errors because the program will jam waiting anyway,
				//  if no data is fed through standard input (stdin)
				_global_padding_length_ = fread(_global_padding_, sizeof (char), BUFFER_SIZE, stdin);

				while (!feof(input_file_))
				{
					rw_length_ = fread(rw_buffer_, sizeof (char), BUFFER_SIZE, input_file_); //! CHECK FOR ERROR
					encrypt_static(rw_buffer_, rw_length_); //! CHECK FOR ERROR
					fwrite(rw_buffer_, sizeof (char), rw_length_, output_file_); //! CHECK FOR ERROR
				}

				fclose(input_file_); //! MOVE TO SPOT
				fclose(output_file_); //! MOVE TO SPOT
				free(_global_padding_); //! MOVE TO SPOT
				_global_padding_ = NULL; //! MOVE TO SPOT
				free(rw_buffer_); //! MOVE TO SPOT
				rw_buffer_ = NULL; //! MOVE TO SPOT
			}
			else	// NOTE: case 3: input file, output file, key file
			{
				input_file_ = fopen(argument1, "rb"); //! CHECK FOR ERROR
				output_file_ = fopen(argument2, "wb"); //! CHECK FOR ERROR
				key_file_ = fopen(argument3, "rb"); //! CHECK FOR ERROR
				key_buffer_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR
				rw_buffer_ = (char *)calloc(BUFFER_SIZE, sizeof (char)); //! CHECK FOR ERROR

				while (!feof(input_file_))
				{
					rw_length_ = fread(rw_buffer_, sizeof (char), BUFFER_SIZE, input_file_); //! CHECK FOR ERROR
					key_length_ = fread(key_buffer_, sizeof (char), BUFFER_SIZE, key_file_); //! CHECK FOR ERROR

					if (feof(key_file_))	// if key_file_ was read entirely...
						rewind(key_file_);	// ... restart from the beginning

					while (key_length_ < rw_length_)
					// if we don't have enough padding data, read some more
					{
						key_length_ += fread(key_buffer_ + key_length_, sizeof (char), BUFFER_SIZE, key_file_); //! CHECK FOR ERROR

						if (feof(key_file_))	// if key_file_ was read entirely...
							rewind(key_file_);	// ... restart from the beginning
					}

					encrypt_normal(rw_buffer_, key_buffer_, rw_length_); //! CHECK FOR ERROR
					fwrite(rw_buffer_, sizeof (char), rw_length_, output_file_); //! CHECK FOR ERROR
				}

				fclose(input_file_); //! MOVE TO SPOT
				fclose(output_file_); //! MOVE TO SPOT
				fclose(key_file_); //! MOVE TO SPOT
				free(key_buffer_); //! MOVE TO SPOT
				key_buffer_ = NULL; //! MOVE TO SPOT
				free(rw_buffer_); //! MOVE TO SPOT
				rw_buffer_ = NULL; //! MOVE TO SPOT
			}

	return EXIT_SUCCESS;
} // ### int compute(const char *argument1, const char *argument2, const char *argument3)

// ### MAIN FUNCTION ###
int main(int argc, char **argv)
// ### MAIN FUNCTION ###
{
	if (argc > 4)
	{
		//! PRINT THE ERROR: "Too many arguments."
		return EXIT_FAILURE;
	}
	else
		if (argc < 2)
		{
			if (argc < 1)
			// NOTE: this shouldn't normally be possible except with a
			//  specially crafted exec() call from another program
			{
				//! PRINT THE ERROR: "Nice try."
				return EXIT_FAILURE;
			}
			else
			{
				//! PRINT THE ERROR: "The program needs at least one argument."
				return EXIT_FAILURE;
			}
		}
		else
			switch (argc)
			{
				case 2: return compute(argv[1], NULL, NULL);		// case 1: input file overwritten, key from standard input
				case 3: return compute(argv[1], argv[2], NULL);		// case 2: input file, output file, key from standard input
				case 4: return compute(argv[1], argv[2], argv[3]);	// case 3: input file, output file, key file
				default:
					//! PRINT THE ERROR: "Unknown. Email [rfahy@ymail.com] and include detail, please."
					return EXIT_FAILURE;
			}
} // ### MAIN FUNCTION ###
