/*
 * Copyright (C) 1998 Jonas Borgstrm <jonas_b@bitsmart.com>
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include "../zlib/zlib.h"

#if USE_PVM
#include <pvm3.h>
#include "pvm.h"
#endif

#include "zipcracker.h"
#include "crc_table.h"

#define DEFLATED 8
#define STORED   0

unsigned char zip_sig[] = {0x50, 0x4b, 0x03, 0x04}; /* Sign of zip header */
char header[12];             /* Encrypted header */
unsigned char zipheader[28]; /* Local zip header */
unsigned char crc;           /* encrypted header CRC */
unsigned long crc2;          /* Target CRC*/

unsigned long org_size, comp_size;
unsigned long file_offset;
FILE *zipfd;

/*****************************************************************************/
/*    Sends error message and error code to the master */
/*****************************************************************************/
void error_msg(char *errormsg, int error_code) {
#if USE_PVM
	pvm_initsend(PvmDataDefault);
	pvm_pkstr(errormsg);
	pvm_pkint(&error_code, 1, 1);
	pvm_send(ptid, PVM_ERROR);
	pvm_exit();
#else
	printf("Error: (%d) %s\n", error_code, errormsg);
#endif
	exit(error_code);
}
/*****************************************************************************/
/*    Parse zipfile to get CRC value */
/*****************************************************************************/
void parse_zipfile() {
	unsigned char buffer[1024], msg[1024];
       	int i, j, end;
	/* Uses "rb" for compability with msdos */
	if((zipfd = fopen(zipname, "rb")) == NULL) {
		error_msg("Can't open zipfile", RETURN_CANT_OPEN_ZIPFILE);
	}

	i = 0;
	end=0;

   	for(j=0;j<strlen(targetname);j++) {

		targetname[j] = toupper(targetname[j]);
	}

	while(!end) {
		if(fread(buffer, 1, 1, zipfd)) {

			if(buffer[0] == zip_sig[i])
				i = i + 1;
			else
				i = 0;

			if (i == 4) {

				if(fread(zipheader + 2, 1, 26, zipfd) != 26) {
					error_msg("fread fail", RETURN_IO);
				}
				if(fread(buffer, 1, SH(zipheader + 24), zipfd)) {
				   	/* Convert filename to UPPER CASE */
				   	for(j=0;j<SH(zipheader + 24);j++) {

					   	buffer[j] = toupper(buffer[j]);
					}
				   	/* Check if we found the file */
					if(memcmp(targetname, buffer,
						  strlen(targetname)) == 0 && (zipheader[24] == strlen(targetname)))
						end=1;
					else {
						i=0;
						/* Read local header if any */
						fread(buffer, 1, SH(zipheader+26), zipfd);
						comp_size = LG(zipheader + 16);
						fseek(zipfd, comp_size, SEEK_CUR);
					}
				}
				else {
					error_msg("fread failed", RETURN_IO);
				}
			}
		}
		else
			end = 2;
	}

	if(end == 2) {
	   	sprintf((char*) msg, "Couldn't find '%s' in '%s'\n", targetname, zipname);
		error_msg((char*) msg, RETURN_TARGET_NOT_FOUND);
	}

	if(!(zipheader[4] & 1)) {
		error_msg("File not encrypted", RETURN_TARGET_NOT_FOUND);
	}
	/* Read local header */
	fread(buffer, 1, zipheader[26], zipfd);
	/* Read random header */
        fread(header, 1, 12, zipfd);
	/* Get crc and original & compressed size */
	crc2      = LG(zipheader + 12);
	comp_size = LG(zipheader + 16) - 12;
	org_size  = LG(zipheader + 20);

	/* Have extended header? */
	if(zipheader[4] & 8)
		crc = zipheader[9];
	else
		crc = zipheader[12 + 3];

	file_offset = ftell(zipfd);

	buffer[12]=0;
}
/*****************************************************************************/
/*    Tries to decrypt header and checks if CRC match, if so return 1 */
/*****************************************************************************/
int decript_head(unsigned char *buffer, unsigned long *key) {
	int i;
	char c;

	for(i = 0; i<12;i++) {
		c = buffer[i] ^ decrypt_byte(key);
		update_keys(key,c);
		buffer[i] = c;
	}

	return buffer[11] == crc;
}
/*****************************************************************************/
/*    update_keys() */
/*****************************************************************************/
void update_keys(unsigned long *key, char val) {

	key[0] = Crc32(key[0], val);
	key[1] = (key[1] + (key[0] & 0xff)) * 134775813L + 1;
	key[2] = Crc32(key[2], (key[1] >> 24));
}
/*****************************************************************************/
/*    decrypt_byte */
/*****************************************************************************/
unsigned char decrypt_byte(unsigned long *key) {
	unsigned short temp;

	temp = key[2] | 2;
	return((temp *( temp^1)) >> 8);
}
/*****************************************************************************/
/*    CRC rutine */
/*****************************************************************************/
unsigned long Crc32(unsigned long oldcrc, char newchar) {
	register unsigned long Crc = oldcrc;

        Crc = ((Crc >> 8) & 0x00ffffffL) ^
		crc_table[(Crc ^ newchar) & 0xffL];

	return Crc;            /* return a 1's complement */
}
/*****************************************************************************/
/*    Test if the password is correct, by unpacking and testing CRC */
/*****************************************************************************/
int test_password(unsigned long *key){
	char inbuffer[BUF_SIZE];
	char outbuffer[BUF_SIZE];
	int err = Z_OK, i, n;
	struct z_stream_s zstrm;
	char c;
	unsigned long newcrc;
	long bl;

	/* Initilize CRC */
	newcrc = crc32(0L, Z_NULL, 0);

	/* Check compresion method */
	if(zipheader[6] == DEFLATED) {

		fseek(zipfd, file_offset, SEEK_SET);

		zstrm.avail_in = fread(inbuffer, 1,
				       (comp_size < BUF_SIZE) ? comp_size : BUF_SIZE, zipfd);

		for(i = 0; i<zstrm.avail_in;i++) {
			c = inbuffer[i] ^ decrypt_byte(key);
			update_keys(key,c);
			inbuffer[i] = c;
		}

		zstrm.next_out = (Bytef*) outbuffer;
		zstrm.avail_out = BUF_SIZE;
		zstrm.next_in = (Bytef*) inbuffer;
		zstrm.zalloc = (alloc_func)Z_NULL;
		zstrm.zfree = (free_func)Z_NULL;
		zstrm.opaque = (char*) (free_func)Z_NULL;

		/* Initilize zlib */
		if((err = inflateInit2(&zstrm, -15)) != Z_OK ) {

			error_msg("inflateInit() failed\n", err);
		}

		while(err != Z_STREAM_END) {
			while (zstrm.avail_out > 0) {

				err = inflate(&zstrm, Z_NO_FLUSH);

				/* Not correct password? */
				if (err != Z_OK && err != Z_STREAM_END) {

					err = inflateEnd(&zstrm);
					if (err != Z_OK) {
						if(zstrm.msg)
							error_msg(zstrm.msg, err);
						else
							error_msg("inflateEnd2", err);
					}
					return 0;
				}

				if (err == Z_STREAM_END)
					break;

				if(zstrm.avail_in <= 0)
				{
					zstrm.avail_in = fread(inbuffer, 1,
							       ((comp_size - zstrm.total_in)< BUF_SIZE) ? (comp_size - zstrm.total_in) : BUF_SIZE, zipfd);
					for(i = 0; i<zstrm.avail_in;i++) {
						c = inbuffer[i] ^ decrypt_byte(key);
						update_keys(key,c);
						inbuffer[i] = c;
					}
					zstrm.next_in = (Bytef*) inbuffer;
				}
			}
			newcrc = crc32(newcrc, (Bytef*) outbuffer,
				       (zstrm.avail_out == 0) ?
				       BUF_SIZE : (BUF_SIZE - zstrm.avail_out));
			zstrm.next_out = (Bytef*) outbuffer;
			zstrm.avail_out = BUF_SIZE;
		}
		err = inflateEnd(&zstrm);
		if (err != Z_OK)
			error_msg("inflateEnd() Oops", err);

	} else if(zipheader[6] == STORED) {

		fseek(zipfd, file_offset, SEEK_SET);
		bl = org_size;

		while((n = fread(inbuffer, 1, (bl > BUF_SIZE) ? BUF_SIZE : bl, zipfd))) {
			bl -= n;
			/* Decrypt */
			for(i = 0; i<n;i++) {
				c = inbuffer[i] ^ decrypt_byte(key);
				update_keys(key,c);
				inbuffer[i] = c;
			}
			newcrc = crc32(newcrc, (Bytef*) inbuffer, n);
		};
	}

	return(crc2 == newcrc);
}
/****************************************************************************/
/*    Main cracking procedure */
/****************************************************************************/
void crack(char *guess, char *end, unsigned long *rate) {
	int i;
	unsigned long count = 0;
	time_t sec;
	unsigned char hackbuff[19];
	char realpw[PW_LEN];
	unsigned long key[3];

	/* Reset variables */
	sec = time(NULL);
	*rate = 0;

	while(1) {
		format_passwd(guess, realpw);

		/* Checking speed */
		count++;
		if(time(NULL) > sec) {
			*rate = count; /* Save value */
			count = 0;
			sec = time(NULL);
		}
		memcpy(hackbuff, header,12);
		key[0] = 305419896L;
		key[1] = 591751049L;
		key[2] = 878082192L;

		for(i=0; i<strlen(realpw);i++)
			update_keys(key,realpw[i]);

		/* Is password a posible password */
		if(decript_head(hackbuff,key)) {

			if(test_password(key)) {
#if USE_PVM
				pvm_initsend(PvmDataDefault);
				pvm_pkstr(guess);
				pvm_send(ptid, PVM_FOUND);
#else
				printf("\7-- Found correct password '%s' --\n", realpw);
				ctrl_c(0);
#endif
				/* Return to exit */
				return;
			}
		}
		/* Finished with our block? */
		if(strcmp(guess, end) == 0)
			return;

		i = 0;
		guess[i] = *(strchr(string, guess[i]) + 1);
		while(guess[i] == 0) {
			if(guess[i+1] == 0) {
				guess[i] = string[0];
 				guess[i + 1] = string[0];
				guess[++i+1] = 0;
				break;
			}
			else {
				guess[i] = string[0];
				i++;
				guess[i] = *(strchr(string, guess[i]) + 1);
			}
		}
	}
}
