/*

$Id: bgpcrack.c,v 1.11 2003/04/28 23:09:38 peloy Exp $

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netdb.h>

#include "config.h"
#include "bgpcrack.h"
#include "rules.h"
#include "db.h"
#include "online.h"

void do_wordlist_crack(char *dictname);
void do_stdin_crack(void);

char *program_name;

/* For command-line options */
struct options options = {
	OFFLINE_CRACK, /* crack_mode */
	NULL, /* dumpfile */
	NULL, /* dictfile */
	RULES_NAME, /* rules_name */
	0, /* segments_to_test */
	0, /* delay */
	{0}, /* source_ip */
	{0} /* destination_ip */
};

FILE *word_file;
int line_number, rule_number, rule_count;

#define USAGE \
"\nBGP Crack Version " PACKAGE_VERSION "\n\n" \
"Usage: %s [-h] [-d delay] [-n number] [-R rules_file] -w words_file\n" \
"     <-r trace_file | -S source_ip -D destination_ip>\n" \
"     [pcap_expression]\n\n" \
"-h: this help\n" \
"-n: process only number packets from the trace file.\n" \
"-r: capture_file contains frames in pcap (tcpdump) format.\n" \
"-w: words_file contains possible passwords, one per line.\n" \
"-d: delay to use for \"live\" dictionary attack.\n" \
"-S: IP address to spoof in \"live\" dictionary attack.\n" \
"-D: Victim's IP address in \"live\" dictionary attack.\n" \
"-R: Name of rules file.\n" \
"\nIf there is no pcap expression all frames will be processed.\n"

void usage(void)
{
	fprintf(stderr, USAGE, program_name);
	exit(1);
}

int main(int argc, char **argv)
{
	int c;
	struct hostent *host;
	char *pcap_expression;

	if ((program_name = strrchr(argv[0], '/')) )
		program_name++;
	else
		program_name = argv[0];

	while ( (c = getopt(argc, argv, "n:r:w:R:hS:D:d:") ) != -1)
		switch (c) {
			case 'n':
				/* Should make sure number is valid */
				options.segments_to_test = atoi(optarg);
				break;
			case 'r':
				options.dumpfile = optarg;
				break;
			case 'w':
				options.dictfile = optarg;
				break;
			case 'R':
				options.rules_name = optarg;
				break;
			case 'h':
				usage();
				exit(1);
				break; /* Not reached */
			case 'S':
				if ( (host = gethostbyname(optarg) ) == NULL)
					error(1, "Error resolving %s.\n",
					      optarg);

				options.source_ip.s_addr = *(unsigned *)
							host->h_addr_list[0];
				break;
			case 'D':
				if ( (host = gethostbyname(optarg) ) == NULL)
					error(1, "Error resolving %s.\n",
					      optarg);

				options.destination_ip.s_addr = *(unsigned *)
							host->h_addr_list[0];
				break;
			case 'd':
				/* Should make sure number is valid */
				options.delay = atoi(optarg);
				break;
			default:
				usage();
		}

	/* Make sure the user specified correct options */
	if (options.dictfile == NULL)
		usage();

	if (options.dumpfile != NULL)
		options.crack_mode = OFFLINE_CRACK;
	else if (options.source_ip.s_addr != 0 &&
		 options.destination_ip.s_addr != 0) {
		if (geteuid() != 0)
			error(1, "On-line cracking mode requires root "
			      "privileges.");
		options.crack_mode = ONLINE_CRACK;
	} else
		usage();

	/* Make STDOUT unbuffered */
	setvbuf(stdout, NULL, _IOLBF, 0);

	cfg_init(options.rules_name);

	if (options.crack_mode == OFFLINE_CRACK) {
		pcap_expression = copy_argv(&argv[optind]);
		if (db_init(options.dumpfile, pcap_expression) != 0)
			error(1, "There are no packets to process!");
	} else
		online_init(options.source_ip, options.destination_ip);

	if (!strcmp(options.dictfile, "-") )
		do_stdin_crack();
	else
		do_wordlist_crack(options.dictfile);

	if (options.crack_mode == OFFLINE_CRACK)
		db_flush();
	else
		online_flush();

	return 0;
}

u_int32_t count, total_count;
struct timeval last_tv;

void
status_print(char *word)
{
	struct stat stat;
	long where;
	int part_file;
	struct timeval tv, rtv;
	float rate = 0;

	if (!word_file || word_file == stdin) {
		fprintf(stderr, "Status: %s\n", word);
		return;
	}

	if (fstat(fileno(word_file), &stat))
		error(1, "fstat");
	if ((where = ftell(word_file)) == -1)
		error(1, "ftell");

	part_file = where * 100 / (stat.st_size + 1);

	gettimeofday(&tv, NULL);
	timersub(&tv, &last_tv, &rtv); 
	if (rtv.tv_sec)
		rate = (float)count/rtv.tv_sec;
	last_tv = tv;
	total_count += count;
	count = 0;

	fprintf(stderr, "Status: % 7.3f%%, % 8.1f c/s: %s\n",
		(float)(rule_number * 100 + part_file) / rule_count,
		rate,
		word);
}

int alarmed = 0;
int signaled = 0;

void sig_handle_timer(int sig)
{
	alarmed = 1;
}
			
void sig_handle_inter(int sig)
{
	signaled = 1;
	signal(SIGINT, SIG_DFL);
}

void do_wordlist_crack(char *dictname)
{
	char line[LINE_BUFFER_SIZE];
	struct rpp_context ctx;
	int length;
	char *rule, *word = NULL;
	char last[RULE_WORD_SIZE];
	int rules = 1;

	if (dictname) {
		if (!(word_file = fopen(dictname, "r")))
			error(1, "fopen: %s", dictname);
	} else
		word_file = stdin;

	length = 16;

	if (rpp_init(&ctx, SUBSECTION_WORDLIST)) {
		fprintf(stderr, "No wordlist mode rules found in %s\n",
			options.rules_name);
		exit(1);
	}

	rules_init(length);
	rule_count = rules_count(&ctx, -1);

	line_number = rule_number = 0;

	rule = rpp_next(&ctx);

	memset(last, ' ', length + 1);
	last[length + 2] = 0;

	alarmed = signaled = 0;
	signal(SIGALRM, sig_handle_timer);
	signal(SIGINT, sig_handle_inter);

	total_count += count;
	count = 0;
	gettimeofday(&last_tv, NULL);

	if (rule)
		do {
			if ((rule = rules_reject(rule, NULL)))
				while (fgetl(line, sizeof(line), word_file)) {
					line_number++;


					if (signaled) {
						alarm(1);
						signaled = 0;
						status_print(last);
					}
					if (alarmed) {
						signal(SIGALRM,
						       sig_handle_timer);
						signal(SIGINT,
						       sig_handle_inter);
						alarmed = 0;
					}

					if (line[0] == '#')
						if (!strncmp(line, "#!comment", 9))
							continue;

					word = rules_apply(line, rule, -1);
					if (word == NULL)
						continue;

					if (!strcmp(word, last))
						continue;

					strcpy(last, word);

					if (options.crack_mode == OFFLINE_CRACK)
						db_crack(word);
					else
						online_crack(word);
				}

			if (rules) {
				if (!(rule = rpp_next(&ctx))) break;
				rule_number++;

				line_number = 0;
				if (fseek(word_file, 0, SEEK_SET))
					error(1, "fseek");
			}
		} while (rules);

	if (ferror(word_file))
		error(1, "fgets");

	if (dictname) {
		if (fclose(word_file))
			error(1, "fclose");
		word_file = NULL;
	}

	alarm(0);
	signal(SIGALRM, SIG_DFL);
	signal(SIGINT, SIG_DFL);

	// return (!rules ? word : NULL);
}

void do_stdin_crack(void)
{
	char line[LINE_BUFFER_SIZE];

	while (fgetl(line, sizeof(line), stdin) )
		if (options.crack_mode == OFFLINE_CRACK)
			db_crack(line);
		else
			online_crack(line);
}
