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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "arch.h"
#include "misc.h"
#include "params.h"
#include "memory.h"
#include "list.h"
#include "loader.h"
#include "logger.h"
#include "status.h"
#include "recovery.h"
#include "options.h"
#include "bench.h"
#include "md5_gen.h"
#ifdef HAVE_MPI
#include "john-mpi.h"
#define _MPI_VERSION " (mpi)"
#define _PER_NODE "per node "
#else
#define _MPI_VERSION ""
#define _PER_NODE ""
#endif

struct options_main options;
static char *field_sep_char_string;

#if defined (__MINGW32__) || defined (_MSC_VER)
// Later versions of MSVC can handle %lld but some older
// ones can only handle %I64d.  Easiest to simply use
// %I64d then all versions of MSVC will handle it just fine
#define LLd "%I64d"
#else
#define LLd "%lld"
#endif

static struct opt_entry opt_list[] = {
	{"", FLG_PASSWD, 0, 0, 0, OPT_FMT_ADD_LIST, &options.passwd},
	{"single", FLG_SINGLE_SET, FLG_CRACKING_CHK, 0, 0,
		OPT_FMT_STR_ALLOC, &options.loader.activesinglerules},
	{"wordlist", FLG_WORDLIST_SET, FLG_CRACKING_CHK,
		0, OPT_REQ_PARAM, OPT_FMT_STR_ALLOC, &options.wordlist},
	{"encoding", FLG_INP_ENCODING, FLG_INP_ENCODING,
		0, 0, OPT_FMT_STR_ALLOC, &options.encoding},
	{"stdin", FLG_STDIN_SET, FLG_CRACKING_CHK},
	{"pipe", FLG_PIPE_SET, FLG_CRACKING_CHK},
	{"rules", FLG_RULES, FLG_RULES, FLG_WORDLIST_CHK, FLG_STDIN_CHK,
		OPT_FMT_STR_ALLOC, &options.loader.activewordlistrules},
	{"incremental", FLG_INC_SET, FLG_CRACKING_CHK,
		0, 0, OPT_FMT_STR_ALLOC, &options.charset},
	{"markov", FLG_MKV_SET, FLG_CRACKING_CHK,
		0, 0, OPT_FMT_STR_ALLOC, &options.mkv_param},
	{"external", FLG_EXTERNAL_SET, FLG_EXTERNAL_CHK,
		0, OPT_REQ_PARAM, OPT_FMT_STR_ALLOC, &options.external},
	{"stdout", FLG_STDOUT, FLG_STDOUT,
		FLG_CRACKING_SUP, FLG_SINGLE_CHK | FLG_BATCH_CHK,
		"%u", &options.length},
	{"restore", FLG_RESTORE_SET, FLG_RESTORE_CHK,
		0, ~FLG_RESTORE_SET & ~OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.session},
	{"session", FLG_SESSION, FLG_SESSION,
		FLG_CRACKING_SUP, OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.session},
	{"status", FLG_STATUS_SET, FLG_STATUS_CHK,
		0, ~FLG_STATUS_SET & ~OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.session},
	{"make-charset", FLG_MAKECHR_SET, FLG_MAKECHR_CHK,
		0, FLG_CRACKING_CHK | FLG_SESSION | OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.charset},
	{"show", FLG_SHOW_SET, FLG_SHOW_CHK,
		0, FLG_CRACKING_SUP | FLG_MAKECHR_CHK,
		OPT_FMT_STR_ALLOC, &options.showuncracked_str},
	{"test", FLG_TEST_SET, FLG_TEST_CHK,
		0, ~FLG_TEST_SET & ~FLG_FORMAT & ~FLG_SAVEMEM & ~FLG_CONFIG_CLI &
		~OPT_REQ_PARAM & ~FLG_NOLOG & ~FLG_INP_ENCODING, "%u", &benchmark_time},
	{"users", FLG_NONE, 0, FLG_PASSWD, OPT_REQ_PARAM,
		OPT_FMT_ADD_LIST_MULTI, &options.loader.users},
	{"groups", FLG_NONE, 0, FLG_PASSWD, OPT_REQ_PARAM,
		OPT_FMT_ADD_LIST_MULTI, &options.loader.groups},
	{"shells", FLG_NONE, 0, FLG_PASSWD, OPT_REQ_PARAM,
		OPT_FMT_ADD_LIST_MULTI, &options.loader.shells},
	{"salts", FLG_SALTS, FLG_SALTS, FLG_PASSWD, OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.salt_param},
	{"pot", FLG_NONE, FLG_NONE, 0, OPT_REQ_PARAM,
	    OPT_FMT_STR_ALLOC, &options.loader.activepot},
	{"format", FLG_FORMAT, FLG_FORMAT,
		0, FLG_STDOUT | OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.format},
	{"subformat", FLG_NONE, FLG_NONE,
		0, FLG_STDOUT | OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.subformat},
	{"save-memory", FLG_SAVEMEM, FLG_SAVEMEM, 0, OPT_REQ_PARAM,
		"%u", &mem_saving_level},
	{"mem-file-size", FLG_NONE, FLG_NONE, 0, OPT_REQ_PARAM,
		"%u", &options.loader.max_wordfile_memory},
	{"fix-state-delay", FLG_NONE, FLG_NONE, 0, OPT_REQ_PARAM,
		"%u", &options.loader.max_fix_state_delay},
	{"field-separator-char", FLG_NONE, FLG_NONE, 0, OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &field_sep_char_string},
	{"config", FLG_CONFIG_CLI, FLG_NONE, 0, OPT_REQ_PARAM,
		OPT_FMT_STR_ALLOC, &options.config},
	{"nolog", FLG_NOLOG, FLG_NOLOG},
	{NULL}
};

#define JOHN_COPYRIGHT \
	"Solar Designer and others"

// the 2 DJ_DOS builds currently set this (and do not build the header). If other environs
// can not build the header, then they will also have this value set.
#ifdef NO_JOHN_BLD
#define JOHN_BLD "unk-build-type"
#else
#include "john_build_rule.h"
#endif

#define JOHN_USAGE \
"John the Ripper password cracker, ver: " JOHN_VERSION _MPI_VERSION " [" JOHN_BLD "]\n" \
"Copyright (c) 1996-2011 by " JOHN_COPYRIGHT "\n" \
"Homepage: http://www.openwall.com/john/\n" \
"\n" \
"Usage: %s [OPTIONS] [PASSWORD-FILES]\n" \
"--config=FILE             use FILE instead of john.conf or john.ini\n" \
"--single[=SECTION]        \"single crack\" mode\n" \
"--wordlist=FILE --stdin   wordlist mode, read words from FILE or stdin\n" \
"                --pipe    like --stdin, but bulk reads, and allows rules\n" \
"--encoding=NAME           the input data is in a 'non-standard' character\n" \
"                          encoding. NAME = utf-8, iso-8859-1, koi8-r, cp1251\n" \
"--rules[=SECTION]         enable word mangling rules for wordlist mode\n" \
"--incremental[=MODE]      \"incremental\" mode [using section MODE]\n" \
"--markov[=LEVEL[:opts]]   \"Markov\" mode (see documentation)\n" \
"--external=MODE           external mode or word filter\n" \
"--stdout[=LENGTH]         just output candidate passwords [cut at LENGTH]\n" \
"--restore[=NAME]          restore an interrupted session [called NAME]\n" \
"--session=NAME            give a new session the NAME\n" \
"--status[=NAME]           print status of a session [called NAME]\n" \
"--make-charset=FILE       make a charset file. It will be overwritten\n" \
"--show[=LEFT]             show cracked passwords [if =LEFT, then uncracked]\n" \
"--test[=TIME]             run tests and benchmarks for TIME seconds each\n" \
"--users=[-]LOGIN|UID[,..] [do not] load this (these) user(s) only\n" \
"--groups=[-]GID[,..]      load users [not] of this (these) group(s) only\n" \
"--shells=[-]SHELL[,..]    load users with[out] this (these) shell(s) only\n" \
"--salts=[-]COUNT[:MAX]    load salts with[out] COUNT [to MAX] hashes\n" \
"--pot=NAME                pot file to use\n" \
"--format=NAME             force hash type NAME: "

/* The format-list is automatically generated now */

#define JOHN_USAGE_TAIL \
"--subformat=LIST          get a listing of all md5-gen(n) formats\n" \
"--save-memory=LEVEL       enable memory saving, at LEVEL 1..3\n" \
"--mem-file-size=SIZE      size threshold for wordlist preload (default 5 MB)\n" \
"--field-separator-char=C  use 'C' instead of the ':' in input and pot files\n" \
"--fix-state-delay=N       performance tweak, see documentation\n" \
"--nolog                   disables creation and writing to john.log file\n" \


#define USAGE_INDENT \
"                          "

static void formatted_print_item(char *label)
{
	static int col = 48;
	static int doslash = 0;

	if (col + strlen(label) > 80) {
		printf("\n" USAGE_INDENT);
		col = strlen(USAGE_INDENT);
		doslash = 0;
	}
	col += strlen(label) + 1;
	if (doslash++)
		printf("/");
	printf("%s", label);
}

static void list_formats(void)
{
	int md5gens = 0;
	struct fmt_main *format;
	if ((format = fmt_list)) {
		do {
			// Skip all md5-gens, print it last
			if (!strncmp(format->params.label, "md5-gen", 7))
				md5gens++;
			else
				formatted_print_item(format->params.label);
		} while ((format = format->next));
		if (md5gens)
			formatted_print_item("md5-gen(n)");
		printf("\n");
	} else
		printf(USAGE_INDENT "** Error listing formats **\n");
}

void opt_init(char *name, int argc, char **argv)
{
	if (argc < 2) {
		printf(JOHN_USAGE, name);
		list_formats();
		printf(JOHN_USAGE_TAIL);
		exit(0);
	}

	memset(&options, 0, sizeof(options));

	options.loader.field_sep_char = options.field_sep_char = ':';
	options.loader.max_fix_state_delay = 0;
	options.loader.max_wordfile_memory = 5000000;

	list_init(&options.passwd);

	options.loader.flags = DB_LOGIN;
	list_init(&options.loader.users);
	list_init(&options.loader.groups);
	list_init(&options.loader.shells);

	options.length = -1;

	opt_process(opt_list, &options.flags, argv);

	if ((options.flags &
	    (FLG_EXTERNAL_CHK | FLG_CRACKING_CHK | FLG_MAKECHR_CHK)) ==
	    FLG_EXTERNAL_CHK)
		options.flags |= FLG_CRACKING_SET;

	if (!(options.flags & FLG_ACTION))
		options.flags |= FLG_BATCH_SET;

	opt_check(opt_list, options.flags, argv);

	if (options.session) {
		rec_name = options.session;
		rec_name_completed = 0;
	}

	if (options.flags & FLG_RESTORE_CHK) {
		rec_restore_args(1);
		return;
	}

	if (options.subformat && !strcasecmp(options.subformat, "list"))
	{
		md5_gen_DISPLAY_ALL_FORMATS();
		// NOTE if we have other 'generics', like sha1, sha2, rc4, ....  then EACH of
		// them should have a DISPLAY_ALL_FORMATS() function and we can call them here.
		exit(0);
	}

	if (options.flags & FLG_STATUS_CHK) {
		rec_restore_args(0);
		options.flags |= FLG_STATUS_SET;
		status_init(NULL, 1);
		status_print();
		exit(0);
	}

	if (options.flags & FLG_SALTS)
	{
		int two_salts = 0;
		if (sscanf(options.salt_param, "%d:%d", &options.loader.min_pps, &options.loader.max_pps) == 2)
			two_salts = 1;
		if (!two_salts && sscanf(options.salt_param, "%d,%d", &options.loader.min_pps, &options.loader.max_pps) == 2)
			two_salts = 1;
		if (!two_salts){
			sscanf(options.salt_param, "%d", &options.loader.min_pps);
			if (options.loader.min_pps < 0) {
				options.loader.max_pps = -1 - options.loader.min_pps;
				options.loader.min_pps = 0;
			}
			else
				options.loader.max_pps = 0x7fffffff;
		} else if (options.loader.min_pps < 0) {
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fprintf(stderr, "Usage of negative -salt min is not 'valid' if using Min and Max salt range of values\n");
			error();
		}
		if (options.loader.min_pps > options.loader.max_pps) {
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fprintf(stderr, "Min number salts wanted is less than Max salts wanted\n");
			error();
		}
	}

	if (options.length < 0)
		options.length = PLAINTEXT_BUFFER_SIZE - 3;
	else
	if (options.length < 1 || options.length > PLAINTEXT_BUFFER_SIZE - 3) {
#ifdef HAVE_MPI
		if (mpi_id == 0)
#endif
		fprintf(stderr, "Invalid plaintext length requested\n");
		error();
	}

	if (options.flags & FLG_STDOUT) options.flags &= ~FLG_PWD_REQ;

	if ((options.flags & (FLG_PASSWD | FLG_PWD_REQ)) == FLG_PWD_REQ) {
#ifdef HAVE_MPI
		if (mpi_id == 0)
#endif
		fprintf(stderr, "Password files required, "
			"but none specified\n");
		error();
	}

	if ((options.flags & (FLG_PASSWD | FLG_PWD_SUP)) == FLG_PASSWD) {
#ifdef HAVE_MPI
		if (mpi_id == 0)
#endif
		fprintf(stderr, "Password files specified, "
			"but no option would use them\n");
		error();
	}

	if (options.flags & FLG_MKV_CHK) {
		char * token;

		options.mkv_start = 0;
		options.mkv_end = 0;
		options.mkv_maxlen = 0;
		options.mkv_minlevel = 0;
		options.mkv_minlen = 0;
		if (options.mkv_param)
		{
			token = strtok(options.mkv_param, ":");
			if(sscanf(token, "%d-%d", &options.mkv_minlevel, &options.mkv_level) != 2)
			{
				options.mkv_minlevel = 0;
				if (sscanf(token, "%d", &options.mkv_level) != 1)
				{
#ifdef HAVE_MPI
					if (mpi_id == 0)
#endif
					fprintf(stderr, "Could not parse markov parameters\n");
					error();
				}
			}
			token = strtok(NULL, ":");
			if( (token != NULL) && (sscanf(token, LLd, &options.mkv_start)==1) )
			{
				token = strtok(NULL, ":");
				if( (token != NULL) && (sscanf(token, LLd, &options.mkv_end)==1) )
				{
					token = strtok(NULL, ":");
					if( (token != NULL) && (sscanf(token, "%d-%d", &options.mkv_minlen, &options.mkv_maxlen)!=2) )
					{
						options.mkv_minlen = 0;
						sscanf(token, "%d", &options.mkv_maxlen);
					}
				}
			}
		}
		if(options.mkv_level<options.mkv_minlevel)
		{
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fprintf(stderr, "Warning: max level(%d) < min level(%d), min level set to %d\n", options.mkv_level, options.mkv_minlevel, options.mkv_level);
			options.mkv_minlevel = options.mkv_level;
		}
		if(options.mkv_minlen > options.mkv_maxlen)
		{
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fprintf(stderr, "Warning: minimum length(%d) < maximum length(%d), minimum length set to %d\n", options.mkv_minlen, options.mkv_maxlen, options.mkv_maxlen);
			options.mkv_minlen = options.mkv_maxlen;
		}
	}

#ifdef HAVE_MPI
	if (options.flags & (FLG_STDIN_CHK | FLG_SHOW_CHK | FLG_MAKECHR_CHK ) && (mpi_p > 1)) {
		if (mpi_id == 0) fprintf(stderr, "Chosen mode not suitable for running on multiple nodes\n");
		error();
	}
#endif

	if ( (options.flags & FLG_SHOW_SET) && options.showuncracked_str) {
		if (!strcasecmp( options.showuncracked_str, "left"))  {
			options.loader.showuncracked = 1;
			// Note we 'do' want the pot file to load normally, but during that load,
			// we print out hashes left. At the end of the load, john exits.  However
			// we do NOT want the 'special' -SHOW_CHK logic to happen (which happens
			// instead of normal loading if we are in 'normal' show mode)
			options.flags &= ~FLG_SHOW_CHK;
		}
		else {
			fprintf(stderr, "Invalid option in --show switch.\nOnly --show or --show=left are valid\n");
			error();
		}
	}

	if (options.loader.activepot == NULL)
		options.loader.activepot = str_alloc_copy(POT_NAME);

	if (options.loader.activewordlistrules == NULL)
		options.loader.activewordlistrules = str_alloc_copy(SUBSECTION_WORDLIST);

	if (options.loader.activesinglerules == NULL)
		options.loader.activesinglerules = str_alloc_copy(SUBSECTION_SINGLE);

	if (field_sep_char_string != NULL)
	{
		if (strlen(field_sep_char_string) == 1)
			options.field_sep_char = *field_sep_char_string;
		else if (field_sep_char_string[0] == '\\' && (field_sep_char_string[1]=='x'||field_sep_char_string[1]=='X'))
		{
			unsigned xTmp=0;
			sscanf(&field_sep_char_string[2], "%x", &xTmp);
			if (!xTmp || xTmp > 255)
			{
#ifdef HAVE_MPI
				if (mpi_id == 0)
#endif
				fprintf (stderr, "trying to use an invalid field separator char:  %s\n", field_sep_char_string);
				error();
			}
			options.field_sep_char = (char)xTmp;
		}

		options.loader.field_sep_char = options.field_sep_char;
		if (options.loader.field_sep_char != ':')
#ifdef HAVE_MPI
			if (mpi_id == 0)
#endif
			fprintf (stderr, "using field sep char '%c' (0x%02x)\n", options.field_sep_char, options.field_sep_char);
	}

	options.utf8 = options.iso8859_1 = options.koi8_r = options.cp1251 = 0;
	// by 'default' we are setup in 7 bit ascii mode (for rules).
	options.encoding_7_bit = 1;
	if ( (options.flags & FLG_INP_ENCODING) && options.encoding) {
		// Ok, check a 'few' valid things for utf8
		options.encoding_7_bit = 0;
		if (!strcasecmp(options.encoding, "utf8")||!strcasecmp(options.encoding, "utf-8"))
			options.utf8 = 1;
		else
		if (!strcasecmp(options.encoding, "ansi")||!strcasecmp(options.encoding, "iso-8859-1")||!strcasecmp(options.encoding, "8859-1"))
			options.iso8859_1 = 1;
		else
		if (!strcasecmp(options.encoding, "koi8-r")||!strcasecmp(options.encoding, "koi8r"))
			options.koi8_r = 1;
		else
		if (!strcasecmp(options.encoding, "cp1251")||!strcasecmp(options.encoding, "cp-1251"))
			options.cp1251 = 1;
		else {
			fprintf(stderr, "Unrecognised encoding given.\nSupported encodings: iso-8859-1, koi8-r, cp1251, utf-8\n");
			error();
		}
	}

	rec_argc = argc; rec_argv = argv;
	rec_check = 0;
}
