// Comentar la linea que corresponda
//#define WINDOWS 1
#define LINUX   2

// Includes de /usr/include
#include <stdlib.h>
#include <stdio.h> 
#include <string.h>
#ifdef WINDOWS
#include <windows.h>		//-- Para que password no sea visible
#endif
#ifdef LINUX
#include <unistd.h> 		//-- Para que password no sea visible
#endif


#define LONG_CRC	4

#define PARAM_OPERAC_ENCRIPTAR			       1
#define PARAM_OPERAC_DESENCRIPTAR_TO_FILE	   2
#define PARAM_OPERAC_DESENCRIPTAR_TO_STDOUT	   3

#define PARAM_ECHO_SI    1      
#define PARAM_ECHO_NO    2      // No hacer echo al introducir la password

#define PARAM_PASSWORD_SI     1 // La password va como argumento de entrada del programa
#define PARAM_PASSWORD_NO     2

#define VERSION "crenalme 1.0.0\n"
#define COPYLEFT "Copyright 2004 Eneko Alvarez Mendia for Free Software\n"
#define VERSION_CRC "NcbH"

// Para compilar en Linux
//	gcc hello.c -g -Wall -o hello 
////////////////////////////////////////


// Devolver CRC de una cadena
/////////////////////////////
void Crc (char* crc, char* password, unsigned long int* sum) {

int llen,li,lcrc;
char c;
//int ctoi;
unsigned long  int ctoi;
unsigned long int memoria=0;

	llen = strlen (password) - 1;

	// Calcular la suma de todos los caracteres de la password
	for (li=0; li<llen; li++) {
		*sum = *sum + (int) password [li];
	}

	// Calcular CRC (password)
	lcrc=0;
	for (li=0; li<llen; li++) {
		c = password [li];
		ctoi = (int) c;
		ctoi = ctoi + 155;
		memoria = memoria + ctoi;
		if (lcrc == LONG_CRC) lcrc=0;
		ctoi = ctoi + crc [lcrc] + *sum + memoria;


		// Esto calcula un CRC de 0 a 255
		if (ctoi > 255 ) {
			ctoi = ctoi % 256;
		}

		// Esto calcula un CRC de 65-90 y 97-122 (A-Z y a-z)
                // Son 26 letras mayusculas y 26 minusculas
                // Lo convierto a un numero entre 0 y 51 (26+26) (0-25, 26-51)
                // El hueco esta entre la 91 y 96 que convertido a 0 y 51 es
                // desde el 26 al 31
		ctoi = ctoi % ((90-65) + (122-97) + 1);
		if (ctoi <= (90-65) ) {
			ctoi = ctoi + 65;
		}
		else {
			ctoi = ctoi + 65 + (97-90) - 1;
		}

		crc [lcrc] = (char) ctoi;
		lcrc++;
	}	

}



// Comprobar que nos pasan los parámetros correctos
//	1:    -e	-- Encriptar
//	      -d	-- Desencriptar
//	      -D	-- Desencriptar directamente a pantalla
//  2:  [-n]    -- No echo password
//  2/3:  [-p]    -- Password
//  3/4:   [p]    -- Password
//	2/3/5:   f	-- Fichero
//
//  Combinaciones distintas de parámetros
//      OPCION_1    -e fichero
//      OPCION_2    -e -n fichero
//      OPCION_3    -e -p password fichero
//
///////////////////////////////////////////////////////
int ErrorParametros (int argc, char* argv[], int* paramOperac, int* paramEcho, int* paramHayPassword, char* paramPassword, char* nombrefichero) {

int lError=0;
int lposFichero;    // Posición donde está el fichero en la entrada
FILE *fichero; 
char controlversion[128];
char crc [LONG_CRC+1] = "0000\0";
unsigned long int sum = 0;

    *paramEcho = PARAM_ECHO_SI;         // Por defecto, se hace echo de la password
    *paramHayPassword = PARAM_PASSWORD_NO; // Por defecto, no hay password por parametro

    strcpy (controlversion, VERSION);
    strcat (controlversion, COPYLEFT);
    Crc (crc, controlversion, &sum);
    if (strcmp (crc, VERSION_CRC)) return 1;

	// Número de argumentos mínimos
	if ( argc <= 1 ) lError = 1;

	// Si quiere ver la version le dejo y me salgo
	if ( lError == 0 ) {
    	if (! strcmp (argv[1], "--version")) return 0;
    }    

	// Argumento 1
	if ( lError == 0 ) {
		if (   strcmp (argv[1], "-e") 
            && strcmp (argv[1], "-d") 
		    && strcmp (argv[1], "-D")  ) lError = 1;
	}

	// Argumento 2
	if ( lError == 0 ) {
        if ( argc <= 2 ) {
            lError = 1;
        } else {
	        if (   strcmp (argv[2], "-n") 
		        && strcmp (argv[2], "-p")  ) {
                // OPCION_1 (-e fichero)
               	if ( argc != 3 ) {
                    lError = 1;
                } else {
                    lposFichero = 2;
                }    
    	    } else {
    	    	if ( ! strcmp (argv[2], "-n")) {
    	    	    // OPCION_2 (-e -n fichero)
    	    	    *paramEcho = PARAM_ECHO_NO;
                   	if ( argc != 4 ) {
                        lError = 1;
                    } else {
                        lposFichero = 3;
                    }    
                } else {
    	    	    // OPCION_3 (-e -p password fichero)
    	    	    *paramHayPassword = PARAM_PASSWORD_SI;
                   	if ( argc != 5 ) {
                        lError = 1;
                    } else {
                        lposFichero = 4;
                        // Devoler la password
                        strcpy (paramPassword, argv[3]);
                    }    
                }    
            }    
        }    
	}

	// El fichero debe existir
	if ( lError == 0 ) {
		fichero = fopen(argv[lposFichero], "rb" );
		if(!fichero) lError = 2;
			else fclose(fichero);
	}

	// Visualizar el mensaje de error
	if ( lError == 1 ) {
		printf("%s%s%s\n", "usage: ", argv[0], " -[edD] [[-n] | [-p password]] file");
		printf("            -e: Encrypt file\n");
		printf("            -d: Decrypt file\n");
		printf("            -D: Decrypt to stdout\n");
		printf("            -n: No password's echo\n");
		printf("            -p: Password as argument\n");
		printf("     --version: Program's version\n");
	}
	if ( lError == 2 ) {
		printf("unable to open %s\n", argv[lposFichero]);
	}

	// Devolver la operacion a realizar
	if ( lError == 0 ) {
		if ( !strcmp (argv[1], "-e") ) *paramOperac = PARAM_OPERAC_ENCRIPTAR;
		if ( !strcmp (argv[1], "-d") ) *paramOperac = PARAM_OPERAC_DESENCRIPTAR_TO_FILE;
		if ( !strcmp (argv[1], "-D") ) *paramOperac = PARAM_OPERAC_DESENCRIPTAR_TO_STDOUT;

        // Devolver el nombre del fichero
    	strcpy (nombrefichero, argv[lposFichero]);
	}

	return lError;
}

// Pedir la Password
////////////////////
int AskPassword (char* password, int paramOperac, int paramEcho, int paramHayPassword, char* paramPassword) {

    // Si me han metido la password por parámetro no la pido, sino que la devuelvo
    if (paramHayPassword == PARAM_PASSWORD_SI) {
        strcpy (password, paramPassword);
        return 0;
    }    

#ifdef WINDOWS
HANDLE h;
int md;
char verifypassword [129];

    // Quitar el echo de la consola sólo si no quiere que haya echo
    if (  paramEcho == PARAM_ECHO_NO) {
        h = GetStdHandle(STD_INPUT_HANDLE);
        GetConsoleMode(h, &md);
        SetConsoleMode(h, md & ~ENABLE_ECHO_INPUT);
    }    

    // Pedir la Password
    printf("Password: "); fflush(stdout);
    fgets(password, 128, stdin);
        
    // Si no hay echo de la password, la pido 2 veces y la verifico
    if (paramEcho == PARAM_ECHO_NO) {
        printf("\nReenter Password: "); fflush(stdout);
        fgets(verifypassword, 128, stdin);
        if (strcmp (password, verifypassword)) {
            printf("\nPassword does not match\n"); fflush(stdout);
            return 1;
        }    
    }    

    // Volver a poner el echo
    if ( paramEcho == PARAM_ECHO_NO) {
        SetConsoleMode(h, md);
    }        
    //printf("\n"); fflush(stdout);

    // Al pedir la password, se mete también el caracter de INTRO
    // Lo quito para que sea igual el pedirla que el meterla por parámetro
    password [strlen (password) - 1] = '\0';
    
#endif

#ifdef LINUX
char* cp;
char* verifypassword;

    // Quitar el echo de la consola sólo si no quiere que haya echo
    if ( paramEcho == PARAM_ECHO_NO) {
        // Pedir la Password sin que se vea el echo
        cp = getpass("Password: ");
        strcpy (password, cp);
    }        
    else {
        // Pedir la password viendose el echo
    	printf( "Password: ");
    	fgets(password, 128, stdin);
    	fflush(stdin);
    }    

    // Si no hay echo de la password, la pido 2 veces y la verifico
    if (paramEcho == PARAM_ECHO_NO) {
        verifypassword = getpass("Reenter Password: ");
        if (strcmp (password, verifypassword)) {
            printf("Password does not match\n"); fflush(stdout);
            return 1;
        }    
    }    

    // Al pedir la password, se mete también el caracter de INTRO
    // Lo quito para que sea igual el pedirla que el meterla por parámetro
    // Sólo lo hago si la password se ha visualizado por pantalla 
    // Si no se ha visualizado por pantalla no hace falta quitarle el último caracter (esto sólo en Linux. En Windows sí hace falta)
    if ( ! (paramEcho == PARAM_ECHO_NO)) {
            password [strlen (password) - 1] = '\0';
    }


#endif

	// Que la password sea como mínimo de 1 caracter
	if (strlen (password) < 1) {
		printf("Invalid password\n");
		return 1;
	}

    return 0;

}

// Encriptar cada caracter
//////////////////////////
char EncripChar (char c, char* password, int* posinpass, long int posinfile, unsigned long int sum ) {

long int l=0;
int lposinpass;
int cencriptado=0;

	// Reposicionar dentro de la password
	if (*posinpass == strlen (password)) lposinpass=0;
		else lposinpass = *posinpass;

	// Obtener numero aleatorio (caracter, password, pos in passw, pos in file, sum password)

	l = lposinpass + posinfile;
	l = l + (int) c;
	l = l + (int) password [lposinpass];
	l = l + sum;
	l = l + 23 + 6 + 2004;

	if (l > 255 ) {
		cencriptado = l % 256;
	} 
	else	cencriptado = (int) l;

	lposinpass++;

	// Devolver la posicion de la password
	*posinpass = lposinpass;

	return (char) cencriptado;

}

// Encriptar el fichero
///////////////////////
int Encript (char* file, char* password, unsigned long int sum ) {

char ficheronombre [255] = "";
FILE *fichero, *fichero2;
int c;
int cencriptado, lposinpassword = 0;
long int position;

	strcpy (ficheronombre, file);
	fichero = fopen(ficheronombre, "rb" );

	if(!fichero) {
		printf( "Error al abrir el fichero\n" );
		return 1;
	}

	fichero2 = fopen(ficheronombre, "r+b" );
	//fichero2 = fopen("prueba.txt", "r+b" );

	if(!fichero2) {
		printf( "Error al abrir el fichero\n" );
		return 1;
	}


	// Abrir el fichero y modificarlo segun se va leyendo
	position = 0;
	while(!feof(fichero)) {
//	while(!feof(fichero) && position != 10) {


		if (position == 8158)
			position = 8158;

		c=getc(fichero);
		if(!feof(fichero)) {

			cencriptado =  EncripChar (c, password, &lposinpassword, position, sum);

			putc(cencriptado,fichero2);
			position++;

		}
	} 

	if( fclose(fichero) ) {
		printf( "Error al cerrar el fichero\n" );
		return 1;
	}

	if( fclose(fichero2) ) {
		printf( "Error al cerrar el fichero\n" );
		return 1;
	}

	return 0;

}

// DesEncriptar cada caracter
/////////////////////////////
char DesEncripChar (char c, char* password, int* posinpass, long int posinfile, unsigned long int sum ) {

long int l=0;
int lposinpass;
int cencriptado=0;

	// Reposicionar dentro de la password
	if (*posinpass == strlen (password)) lposinpass=0;
		else lposinpass = *posinpass;

	// Deshacer numero aleatorio (caracter, password, pos in passw, pos in file, sum password)
	l = (int) c - lposinpass - (int) password [lposinpass] - posinfile - sum - 23 - 6 - 2004;

	if (l < 0 ) {
		cencriptado = l + 256;
	} 
	else	cencriptado = (int) l;

	lposinpass++;

	// Devolver la posicion de la password
	*posinpass = lposinpass;

	return (char) cencriptado;
}


// DesEncriptar el fichero
//////////////////////////
int DesEncript (char* file, char* password, unsigned long int sum, int pstdout ) {

char ficheronombre [255] = "";
FILE *fichero, *fichero2;
int c;
int cencriptado, lposinpassword = 0;
long int position;

	strcpy (ficheronombre, file);
	fichero = fopen(ficheronombre, "rb" );

	if(!fichero) {
		printf( "Error al abrir el fichero\n" );
		return 1;
	}

	// Si me he pasado stdout no abro el fichero sino que lo imprimo por pantalla
	if (pstdout == PARAM_OPERAC_DESENCRIPTAR_TO_FILE) {
		fichero2 = fopen(ficheronombre, "r+b" );
		if(!fichero2) {
			printf( "Error al abrir el fichero\n" );
			return 1;
		}
	}


	// Abrir el fichero y modificarlo segun se va leyendo
	position = 0;
	while(!feof(fichero)) {

		c=getc(fichero);
		if(!feof(fichero)) {

			cencriptado =  DesEncripChar (c, password, &lposinpassword, position, sum);

			// Imprimir o no por pantalla
			if (pstdout == PARAM_OPERAC_DESENCRIPTAR_TO_FILE) {
				putc(cencriptado,fichero2);
				position++;
			} else {
				printf("%c", cencriptado);
				position++;
			}

		}
	} 

	if( fclose(fichero) ) {
		printf( "Error al cerrar el fichero\n" );
		return 1;
	}

	// Imprimir o no por pantalla
	if (pstdout == PARAM_OPERAC_DESENCRIPTAR_TO_FILE) {
		if( fclose(fichero2) ) {
			printf( "Error al cerrar el fichero\n" );
			return 1;
		}
	}

	return 0;

}

// Main
///////
int main(int argc, char **argv)
{

char crc [LONG_CRC+1] = "0000\0";
char password [129];
unsigned long int sum = 0;
int paramOperac, paramEcho, paramHayPassword;
char paramPassword[129];
char nombrefichero[129];

	// Comprobar que los parámetros son correctos
	if (ErrorParametros (argc, argv, &paramOperac, &paramEcho, &paramHayPassword, paramPassword, nombrefichero)) return 1;

        // Mostrar versión del programa
	if ( argc == 2 ) {
	    if (! strcmp (argv[1], "--version")) {
            printf (VERSION);
            printf (COPYLEFT);
            return 0;
            }    
        }    
    
	// Pedir la Password
	if (AskPassword (password, paramOperac, paramEcho, paramHayPassword, paramPassword)) {
        return 1;
        }    

	// CRC de password
	Crc (crc, password, &sum);

	// Operaciones a Realizar
	switch (paramOperac) {

		// Encriptar el fichero
		case PARAM_OPERAC_ENCRIPTAR:
			Encript (nombrefichero, password, sum);
			break;

		// Desencriptar el fichero
		case PARAM_OPERAC_DESENCRIPTAR_TO_FILE:
			DesEncript (nombrefichero, password, sum, PARAM_OPERAC_DESENCRIPTAR_TO_FILE);
			break;

		// Desencriptar el fichero
		case PARAM_OPERAC_DESENCRIPTAR_TO_STDOUT:
			DesEncript (nombrefichero, password, sum, PARAM_OPERAC_DESENCRIPTAR_TO_STDOUT);
			break;
	}
	
	return 0;
} 

