/*
 * 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 <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pvm3.h>
#include "zipcracker.h"

int nhost = 0; /* Number of slaves to spawn */
int ptid;      /* The Master's tid */

/*****************************************************************************/
/*    Returns a free slot. Returns NULL on error */
/*****************************************************************************/
struct crack_slave *find_free() {
	int i;
	
	for(i=0;i<MAX_SLAVES;i++) {
		if(slaves[i].busy == 0) {
			slaves[i].busy = 1;
			return &slaves[i];
		}
		
	}
	return NULL;
}
/*****************************************************************************/
/*    Returns the slaves slot. Returns NULL on error */
/*****************************************************************************/
struct crack_slave *find_slave(int tid) {
	int i;
	
	for(i=0;i<MAX_SLAVES;i++) {
		if((slaves[i].tid == tid) && (slaves[i].busy == 1))
			return &slaves[i];
	}
	return NULL;
}
/*****************************************************************************/
/*    Waits for the clients and sends ack. */
/*****************************************************************************/
void master_control() {
	struct crack_slave *slave;
	char buf[256], buf2[256], error_msg[512], hostname[64]; /* temporary buffer*/
	struct timeval wait_time;
	struct crack_todo *todo_tmp;
	unsigned int msgtag;
	unsigned long version, rate;
	int tid, cc, error_code;
        char start[PW_LEN];
	char end[PW_LEN];
	
	/* 10 minutes timeout */
	wait_time.tv_sec = 600;
	wait_time.tv_usec = 0;
	
	printf("-- Cracking '%s' --\n", zipname);
	while(1) {
		if((cc = pvm_trecv(-1, -1, &wait_time)) == 0) {
			printf("Master timed out waiting for slave\n");
			pvm_exit();
			exit(RETURN_MASTER_TIMED_OUT);
		}	
		pvm_bufinfo(cc, NULL, &msgtag, &tid);
		slave = find_slave(tid);
		
		switch(msgtag) {
		case PVM_INIT:
			pvm_upkulong(&version, 1, 1);
			pvm_upkstr(hostname);
			printf("%d: Slave %s, Version = %lu Found\n",
			       num_slaves,
			       hostname, 
			       version);
			/* Init entry */
			if((slave = find_free()) == NULL) {
				printf("To many slaves\n");
				pvm_exit();
				exit(RETURN_TO_MANY_SLAVES);
			}
			slave->start[0] = 0;
			slave->end[0] = 0;
			slave->tid = tid;
			slave->start_t = 0;
			strcpy(slave->hostname, hostname);
			num_slaves++;
			/* Sending variables */
			pvm_initsend(PvmDataDefault);
			pvm_pkstr(zipname);
			pvm_pkstr(targetname);
			pvm_pkstr(string);
			pvm_pkstr(format);
			pvm_send(slave->tid, PVM_INIT);
			break;
		case PVM_REQUEST:
			pvm_upkulong(&rate, 1, 1);
			printf("New block to %s: ", slave->hostname);
			if(todo) {
				/* Using saved blocks */
				strcpy(start, todo->start);
				strcpy(slave->start, start);
				strcpy(end, todo->end);
				strcpy(slave->end, end);
				todo_tmp = todo;
				todo = todo->next;
				free(todo_tmp);
			} else {
				/* Using new block */
				strcpy(start, passwd);
				strcpy(slave->start, passwd);
				inc_much_passwd(passwd);
				strcpy(end, passwd);
				strcpy(slave->end, passwd);
				inc_passwd(passwd);
			}
			slave->needsave = 1;
			slave->start_t = time(NULL);
			/* Display block on screen */
			printf("'%s'->",
			       format_passwd(start, buf));
			printf("'%s'. ",
			       format_passwd(end, buf));
			if(rate) {
				printf("Rate = %lu combs/s\n",rate);
			} else
				puts(""); /* New line */
			
			/* Sending ack */
			pvm_initsend(PvmDataDefault);
			pvm_pkstr(start);
			pvm_pkstr(end);
			pvm_send(slave->tid, PVM_REQUEST);
			break;
		case PVM_ERROR:
			pvm_upkstr(error_msg);
			pvm_upkint(&error_code, 1, 1);
			printf("Slave error: (%d) %s\n", error_code, error_msg);
			pvm_exit();
			exit(error_code);
		case PVM_FOUND:
			pvm_upkstr(buf2);
			printf("\7-- Host %s found correct password '%s' --\n", 
			       slave->hostname,
			       format_passwd(buf2, buf));
			/* Saves and exit */
			ctrl_c(0);
		};
	}
}
/*****************************************************************************/
/*    Program main-loop PVM version */
/*****************************************************************************/
void pvm_start() {
	int cc;
	int ctid[MAX_SLAVES];
	char slave_filename[512];
	char *arg[2];
	
	arg[0] = "--slave";
	arg[1] = NULL;
	
	/* Disable error messages */
	pvm_setopt(PvmAutoErr, 0);
	
	if(pvm_mytid() < 0) {
		
		printf("You need to start the pvmd before you start zipcracker\n");
		exit(RETURN_PVMD_NOT_FOUND);
	}
	
	
	printf("---------------\n");
	printf("PVM Zip Cracker\n");
	printf("---------------\n");
	printf("Master ID t%x\n", pvm_mytid());
	
	if(strlen(passwd) == 0)
		strncpy(passwd, string, 1);

	init_passwd();
	/* Check number of hosts in virtual machine */
	if(nhost == 0) {
		pvm_config(&nhost, NULL, NULL);
	}
	sprintf(slave_filename, "%s/%s", BINDIR, "zipcracker");
	if(nhost > 1) 
		printf("-- Spawning %d '%s' slaves --\n", nhost, slave_filename);
	else
		printf("-- Spawning one '%s' slave --\n", slave_filename);
	cc = pvm_spawn(slave_filename, arg, 0, "", nhost, ctid);
	
	if (cc == nhost) {
		signal(SIGTERM, ctrl_c);
		signal(SIGINT, ctrl_c);
		
		master_control();
		
	} else {
		printf("can't spawn %d crack_clients\n", nhost);
		pvm_exit();
		exit(RETURN_CANT_SPAWN);
	}
	pvm_exit();
	exit(RETURN_OK);
}
/****************************************************************************/
/*    Slave entry point */
/****************************************************************************/
void slave_mainloop() {
	int cc;
	unsigned long version, rate = 0; /* Combinations / sec */
	struct timeval wait_time;
	unsigned int msgtag;
	char hostname[64];
        char start[PW_LEN];
	char end[PW_LEN];
	
	/* 1/2 minute timeout */
	wait_time.tv_sec = 30;
	wait_time.tv_usec = 0;
	
	pvm_initsend(PvmDataDefault);
	
	ptid = pvm_parent();
	/* Have PVM notify us if the host zipcracker process is killed
	 * or the local PVM daemon dies */
	pvm_notify(PvmTaskExit, PVM_KILL_SLAVE, 1, &ptid);
	pvm_setopt(PvmAutoErr, 2);
	
	/* Init structure */
	version = 001;
	gethostname(hostname, 63);
	/* Sending Info */
	pvm_pkulong(&version, 1, 1);
	pvm_pkstr(hostname);
	pvm_send(ptid, PVM_INIT);
	/* Reading ack */
	if((cc = pvm_trecv(ptid, -1, &wait_time)) == 0) {
		printf("Slave timed out waiting for master\n");
		pvm_exit();
		exit(RETURN_SLAVE_TIMED_OUT);
	}	
	pvm_bufinfo(cc, NULL, &msgtag, &ptid);
	if(msgtag != PVM_INIT) {
		printf("Unknown msgtag %u from master\n", msgtag);
		pvm_exit();
		exit(RETURN_UNKNOWN_MSGTAG);
	}	
	pvm_upkstr(zipname);
	pvm_upkstr(targetname);
	pvm_upkstr(string);
	pvm_upkstr(format);
	/* Init */
	parse_zipfile();
	
	while(1) {
		/* Requesting new block */
		pvm_initsend(PvmDataDefault);
		pvm_pkulong(&rate, 1, 1);
		pvm_send(ptid, PVM_REQUEST);
		/* Reading ack */
		if((cc = pvm_trecv(ptid, -1, &wait_time)) == 0) {
			printf("Slave timed out waiting for master\n");
			pvm_exit();
			exit(RETURN_SLAVE_TIMED_OUT);
		}	
		pvm_bufinfo(cc, NULL, &msgtag, &ptid);
		switch(msgtag) {
		case PVM_EXIT:
		case PVM_KILL_SLAVE:
			/* Server want us to quit? */
			pvm_exit();
			exit(RETURN_OK);
			break;
		case PVM_REQUEST:
			pvm_upkstr(start);
			pvm_upkstr(end);
			crack(start, end, &rate);
			break;
		};
	};
}
