/*
 * This file is part of John the Ripper password cracker,
 * Copyright (c) 1996-2004,2006,2009-2011 by Solar Designer
 *
 * ...with changes in the jumbo patch, by various authors
 */

#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

#include "arch.h"
#include "misc.h"
#include "params.h"
#include "path.h"
#include "memory.h"
#include "list.h"
#include "tty.h"
#include "signals.h"
#include "common.h"
#include "idle.h"
#include "formats.h"
#include "loader.h"
#include "logger.h"
#include "status.h"
#include "options.h"
#include "config.h"
#include "bench.h"
#include "charset.h"
#include "single.h"
#include "wordlist.h"
#include "inc.h"
#include "mkv.h"
#include "external.h"
#include "batch.h"
#include "md5_gen.h"
#ifdef HAVE_MPI
#include "john-mpi.h"
#ifdef _OPENMP
#include <omp.h>
#endif /* _OPENMP */
#endif /* HAVE_MPI */
#include <openssl/opensslv.h>
#include "unicode.h"

#if CPU_DETECT
extern int CPU_detect(void);
#endif

extern struct fmt_main fmt_DES, fmt_BSDI, fmt_MD5, fmt_BF;
extern struct fmt_main fmt_AFS, fmt_LM;
#ifdef HAVE_CRYPT
extern struct fmt_main fmt_crypt;
#endif
extern struct fmt_main fmt_dummy;

extern struct fmt_main fmt_MD5gen;

#if OPENSSL_VERSION_NUMBER >= 0x00908000
extern struct fmt_main fmt_rawSHA224;
extern struct fmt_main fmt_rawSHA256;
extern struct fmt_main fmt_rawSHA384;
extern struct fmt_main fmt_rawSHA512;

extern struct fmt_main fmt_hmailserver;
extern struct fmt_main fmt_SybaseASE;
#endif

#ifdef HAVE_SKEY
extern struct fmt_main fmt_SKEY;
#endif

#ifndef _MSC_VER
extern struct fmt_main fmt_ssh;
extern struct fmt_main fmt_pdf;
extern struct fmt_main rar_fmt;
extern struct fmt_main zip_fmt;
#endif

#include "fmt_externs.h"

extern int unique(int argc, char **argv);
extern int unshadow(int argc, char **argv);
extern int unafs(int argc, char **argv);
extern int undrop(int argc, char **argv);
#ifndef _MSC_VER
extern int ssh2john(int argc, char **argv);
extern int pdf2john(int argc, char **argv);
extern int rar2john(int argc, char **argv);
extern int zip2john(int argc, char **argv);
#endif

static struct db_main database;
static struct fmt_main dummy_format;

static int exit_status = 0;

static void john_register_one(struct fmt_main *format)
{
	if (options.format)
	if (strcmp(options.format, format->params.label)) return;

	fmt_register(format);
}

static void john_register_all(void)
{
	int i, cnt;
	struct fmt_main *pFmts;

	if (options.format) strlwr(options.format);

	// NOTE, this MUST happen, before ANY format that links a 'thin' format to md5-gen.
	// Since gen(27) and gen(28) are MD5 and MD5a formats, we build the
	// generic format first
	cnt = md5_gen_Register_formats(&pFmts);

	john_register_one(&fmt_DES);
	john_register_one(&fmt_BSDI);
	john_register_one(&fmt_MD5);
	john_register_one(&fmt_BF);
	john_register_one(&fmt_AFS);
	john_register_one(&fmt_LM);

#include "fmt_registers.h"

	for (i = 0; i < cnt; ++i)
		john_register_one(&(pFmts[i]));

#if OPENSSL_VERSION_NUMBER >= 0x00908000
	john_register_one(&fmt_rawSHA224);
	john_register_one(&fmt_rawSHA256);
	john_register_one(&fmt_rawSHA384);
	john_register_one(&fmt_rawSHA512);

	john_register_one(&fmt_hmailserver);
	john_register_one(&fmt_SybaseASE);
#endif

#ifdef HAVE_CRYPT
	john_register_one(&fmt_crypt);
#endif

#ifdef HAVE_SKEY
	john_register_one(&fmt_SKEY);
#endif

#ifndef _MSC_VER
	john_register_one(&fmt_ssh);
	john_register_one(&fmt_pdf);
	john_register_one(&rar_fmt);
	john_register_one(&zip_fmt);
#endif

	john_register_one(&fmt_dummy);

	if (!fmt_list) {
#ifdef HAVE_MPI
		if (mpi_id == 0)
#endif
		fprintf(stderr, "Unknown ciphertext format name requested\n");
		error();
	}
}

static void john_log_format(void)
{
	int min_chunk, chunk;

#ifdef HAVE_MPI
	if (mpi_p > 1)
		log_event("- MPI mode: %u nodes, this one running on %s", mpi_p, mpi_name);
#endif
	log_event("- Hash type: %.100s (lengths up to %d%s)",
		database.format->params.format_name,
		database.format->params.plaintext_length,
		(database.format == &fmt_DES || database.format == &fmt_LM) ?
		", longer passwords split" : "");

	log_event("- Algorithm: %.100s",
		database.format->params.algorithm_name);

	chunk = min_chunk = database.format->params.max_keys_per_crypt;
	if (options.flags & (FLG_SINGLE_CHK | FLG_BATCH_CHK) &&
	    chunk < SINGLE_HASH_MIN)
			chunk = SINGLE_HASH_MIN;
	if (chunk > 1)
		log_event("- Candidate passwords %s be buffered and "
			"tried in chunks of %d",
			min_chunk > 1 ? "will" : "may",
			chunk);
	if (options.utf8 && database.format->params.flags & FMT_UTF8)
		log_event("- UTF-8 conversion enabled");
}

static char *john_loaded_counts(void)
{
	static char s_loaded_counts[80];

	if (database.password_count == 1)
		return "1 password hash";

	sprintf(s_loaded_counts,
		database.salt_count > 1 ?
		"%d password hashes with %d different salts" :
		"%d password hashes with no different salts",
		database.password_count,
		database.salt_count);

	return s_loaded_counts;
}

static void john_load(void)
{
	struct list_entry *current;

#ifndef _MSC_VER
	umask(077);
#endif

	if (options.flags & FLG_EXTERNAL_CHK)
		ext_init(options.external);

	if (options.flags & FLG_MAKECHR_CHK) {
		options.loader.flags |= DB_CRACKED;
		ldr_init_database(&database, &options.loader);

		if (options.flags & FLG_PASSWD) {
			ldr_show_pot_file(&database, options.loader.activepot);

			database.options->flags |= DB_PLAINTEXTS;
			if ((current = options.passwd->head))
			do {
				ldr_show_pw_file(&database, current->data);
			} while ((current = current->next));
		} else {
			database.options->flags |= DB_PLAINTEXTS;
			ldr_show_pot_file(&database, options.loader.activepot);
		}

		return;
	}

	if (options.flags & FLG_STDOUT) {
		ldr_init_database(&database, &options.loader);
		database.format = &dummy_format;
		memset(&dummy_format, 0, sizeof(dummy_format));
		dummy_format.params.plaintext_length = options.length;
		dummy_format.params.flags = FMT_CASE | FMT_8_BIT;
	}

	if (options.flags & FLG_PASSWD) {
		int total;

		if (options.flags & FLG_SHOW_CHK) {
			options.loader.flags |= DB_CRACKED;
			ldr_init_database(&database, &options.loader);

			ldr_show_pot_file(&database, options.loader.activepot);

			if ((current = options.passwd->head))
			do {
				ldr_show_pw_file(&database, current->data);
			} while ((current = current->next));

			printf("%s%d password hash%s cracked, %d left\n",
				database.guess_count ? "\n" : "",
				database.guess_count,
				database.guess_count != 1 ? "es" : "",
				database.password_count -
				database.guess_count);

			return;
		}

		if (options.flags & (FLG_SINGLE_CHK | FLG_BATCH_CHK) &&
		    status.pass <= 1)
			options.loader.flags |= DB_WORDS;
		else
		if (mem_saving_level) {
			options.loader.flags &= ~DB_LOGIN;
			options.loader.max_wordfile_memory = 0;
		}
		ldr_init_database(&database, &options.loader);

		if ((current = options.passwd->head))
		do {
			ldr_load_pw_file(&database, current->data);
		} while ((current = current->next));

		// Unicode (UTF-16) formats may lack UTF-8 support (initially)
		if (options.utf8 && database.password_count &&
		    database.format->params.flags & FMT_UNICODE &&
		    !(database.format->params.flags & FMT_UTF8)) {
			fprintf(stderr, "This format does not yet support UTF-8 conversion\n");
				error();
		}

		if ((options.flags & FLG_CRACKING_CHK) &&
		    database.password_count) {
			log_init(LOG_NAME, NULL, options.session);
			if (status_restored_time)
				log_event("Continuing an interrupted session");
			else
				log_event("Starting a new session");
			log_event("Loaded a total of %s", john_loaded_counts());
			printf("Loaded %s (%s [%s])\n",
				john_loaded_counts(),
				database.format->params.format_name,
				database.format->params.algorithm_name);
		}

		total = database.password_count;
		ldr_load_pot_file(&database, options.loader.activepot);
		ldr_fix_database(&database);

		if (!database.password_count) {
			log_discard();
			printf("No password hashes %s (see FAQ)\n",
			    total ? "left to crack" : "loaded");
		} else
		if (database.password_count < total) {
			log_event("Remaining %s", john_loaded_counts());
			printf("Remaining %s\n", john_loaded_counts());
		}

		if ((options.flags & FLG_PWD_REQ) && !database.salts) exit(0);
	}
}

static void john_init(char *name, int argc, char **argv)
{
	int make_check = (argc == 2 && !strcmp(argv[1], "--make_check"));
	if (make_check) {
		argv[1] = "--test=0";
#if defined (__MINGW32__) || defined (__CYGWIN32__) || defined (_MSC_VER)
		argv[2] = "--conf=../run/john.conf";
		argv[3] = NULL;
		++argc;
#endif
	}

#if CPU_DETECT
	if (!CPU_detect()) {
#if CPU_REQ
#if CPU_FALLBACK
#if defined(__DJGPP__) || defined(__CYGWIN32__)
#error CPU_FALLBACK is incompatible with the current DOS and Win32 code
#endif
		if (!make_check) {
#define CPU_FALLBACK_PATHNAME JOHN_SYSTEMWIDE_EXEC "/" CPU_FALLBACK_BINARY
			execv(CPU_FALLBACK_PATHNAME, argv);
			perror("execv: " CPU_FALLBACK_PATHNAME);
		}
#endif
		fprintf(stderr, "Sorry, %s is required\n", CPU_NAME);
		if (make_check)
			exit(0);
		error();
#endif
	}
#endif

	// This if is busted. If it is there, then make check will NOT work.
	//if (!make_check) {
		path_init(argv);

    status_init(NULL, 1);

    if (argc < 2)
	    john_register_all();    // auto format list in opt_init needs this
    opt_init(name, argc, argv); // this will then exit if argc < 2

    if (options.flags & FLG_CONFIG_CLI)
    {
      cfg_init(options.config, 1);
      cfg_init(CFG_ALT_NAME, 0);
    }
    else
    {
#if JOHN_SYSTEMWIDE
		cfg_init(CFG_PRIVATE_FULL_NAME, 1);
		cfg_init(CFG_PRIVATE_ALT_NAME, 1);
#endif
		cfg_init(CFG_FULL_NAME, 1);
		cfg_init(CFG_ALT_NAME, 0);
	}
	//}

	/* Init the unicode system, even if we are NOT doing a unicode format.
	 * There are functions / data which is used without needing the full
	 * unicode conversion.  The upper/lowercasing of 8 byte encodings is
	 * an example
	 */
	initUnicode();

	john_register_all();
	common_init();

	sig_init();

	john_load();
}

static void john_run(void)
{
	if (options.flags & FLG_TEST_CHK)
		exit_status = benchmark_all() ? 1 : 0;
	else
	if (options.flags & FLG_MAKECHR_CHK)
		do_makechars(&database, options.charset);
	else
	if (options.flags & FLG_CRACKING_CHK) {
		int remaining = database.password_count;

		if (!(options.flags & FLG_STDOUT)) {
			status_init(NULL, 1);
			log_init(LOG_NAME, options.loader.activepot, options.session);
			john_log_format();
			if (idle_requested(database.format))
				log_event("- Configured to use otherwise idle "
					"processor cycles only");
		}
		tty_init(options.flags & FLG_STDIN_CHK);

#if defined(HAVE_MPI) && defined(_OPENMP)
		if (database.format->params.flags & FMT_OMP &&
		    omp_get_max_threads() > 1 && mpi_p > 1) {
			if(cfg_get_bool(SECTION_OPTIONS, NULL, "MPIOMPmutex", 1)) {
				if(cfg_get_bool(SECTION_OPTIONS, NULL, "MPIOMPverbose", 1) &&
				   mpi_id == 0)
					fprintf(stderr, "MPI in use, disabling OMP (see doc/README.mpi)\n");
				omp_set_num_threads(1);
			} else
				if(cfg_get_bool(SECTION_OPTIONS, NULL, "MPIOMPverbose", 1) &&
				   mpi_id == 0)
					fprintf(stderr, "Note: Running both MPI and OMP (see doc/README.mpi)\n");
		}
#endif
		if (options.flags & FLG_SINGLE_CHK)
			do_single_crack(&database);
		else
		if (options.flags & FLG_WORDLIST_CHK)
			do_wordlist_crack(&database, options.wordlist,
				(options.flags & FLG_RULES) != 0);
		else
		if (options.flags & FLG_INC_CHK)
			do_incremental_crack(&database, options.charset);
		else
		if (options.flags & FLG_MKV_CHK)
			do_markov_crack(&database, options.mkv_level, options.mkv_start, options.mkv_end, options.mkv_maxlen, options.mkv_minlevel, options.mkv_minlen);
		else
		if (options.flags & FLG_EXTERNAL_CHK)
			do_external_crack(&database);
		else
		if (options.flags & FLG_BATCH_CHK)
			do_batch_crack(&database);

		status_print();
		tty_done();

		if (database.password_count < remaining) {
			char *might = "Warning: passwords printed above might";
			char *partial = " be partial";
			char *not_all = " not be all those cracked";
			switch (database.options->flags &
			    (DB_SPLIT | DB_NODUP)) {
			case DB_SPLIT:
#ifdef HAVE_MPI
				if (mpi_id == 0)
#endif
				fprintf(stderr, "%s%s\n", might, partial);
				break;
			case DB_NODUP:
#ifdef HAVE_MPI
				if (mpi_id == 0)
#endif
				fprintf(stderr, "%s%s\n", might, not_all);
				break;
			case (DB_SPLIT | DB_NODUP):
#ifdef HAVE_MPI
				if (mpi_id == 0)
#endif
				fprintf(stderr, "%s%s and%s\n",
				    might, partial, not_all);
			}
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fputs("Use the \"--show\" option to display all of "
			    "the cracked passwords reliably\n", stderr);
		}
	}
}

static void john_done(void)
{
	path_done();

	if ((options.flags & FLG_CRACKING_CHK) &&
	    !(options.flags & FLG_STDOUT)) {
		if (event_abort)
			log_event("Session aborted");
		else
			log_event("Session completed");
	}
	log_done();
	check_abort(0);
	cleanup_tiny_memory();
}

int main(int argc, char **argv)
{
	char *name;

#ifdef _MSC_VER
   // Send all reports to STDOUT
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );
#endif

#ifdef __DJGPP__
	if (--argc <= 0) return 1;
	if ((name = strrchr(argv[0], '/')))
		strcpy(name + 1, argv[1]);
	name = argv[1];
	argv[1] = argv[0];
	argv++;
#else
	if (!argv[0])
		name = "john";
	else
	if ((name = strrchr(argv[0], '/')))
		name++;
#if defined(__CYGWIN32__) || defined (__MINGW32__) || defined (_MSC_VER)
	else
	if ((name = strrchr(argv[0], '\\')))
		name++;
#endif
	else
		name = argv[0];
#endif

#if defined(__CYGWIN32__) || defined (__MINGW32__) || defined (_MSC_VER)
	strlwr(name);
	if (strlen(name) > 4 && !strcmp(name + strlen(name) - 4, ".exe"))
		name[strlen(name) - 4] = 0;
#endif

	if (!strcmp(name, "unique"))
		return unique(argc, argv);

	if (!strcmp(name, "unshadow"))
		return unshadow(argc, argv);

	if (!strcmp(name, "unafs"))
		return unafs(argc, argv);

	if (!strcmp(name, "undrop"))
               return undrop(argc, argv);

#ifndef _MSC_VER
	if (!strcmp(name, "ssh2john"))
		return ssh2john(argc, argv);

 	if (!strcmp(name, "pdf2john"))
		return pdf2john(argc, argv);

	if (!strcmp(name, "rar2john"))
		return rar2john(argc, argv);

	if (!strcmp(name, "zip2john"))
		return zip2john(argc, argv);
#endif

#ifdef HAVE_MPI
	mpi_setup(argc, argv);
#endif
	john_init(name, argc, argv);
	john_run();
	john_done();

#ifdef _MSC_VER
	_CrtDumpMemoryLeaks();
#endif

	return exit_status;
}
