/*
 * Soubor:  main.c
 * Datum:   06.08.2012
 * Autor:   Stanislav Bárta, xbarta29@stud.fit.vutbr.cz
 * Projekt: IM modul
 * Popis:   soubor obsahujici funkci main aplikace IM modulu
 *          inicializace funkci knihovny pcap a smycka zachytavani paketu
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <pcap.h>
#include <getopt.h>

#include "detekce_protokolu.h"
#include "xmpp.h"
#include "irc.h"
#include "oscar.h"
#include "ymsg.h"
#include "iri.h"
#include "sprava.h"
#include "my_string.h"

// hodnota jak velky paket maximalne zpracovat -> urceno podle manualu pcap
#define BUFFER 65535

// struktura uchovavajici zadane parametry
typedef struct
{
	char *rozhrani; // rozhrani na kterem budeme odposlouchavat
	char *pcap_file; // soubor se zachycenou komunikaci pro zpracovani
	int pouze_zname_porty;
	int help;
	int xmpp;
	int irc;
	int oscar;
	int ymsg;
} Parametry;


// handler rozhrani pro odposlouchavani
// globalne kvuli moznosti uzavrit handler po ukonceni programu pres sigint
pcap_t *handler_rozhrani;

// pokud jsou pouzivany pouze zname porty
struct bpf_program fp;
char filter_exp[] = "port 5222 or portrange 6665-6669 or portrange 5190-5193 or port 5050";

struct seznam_sezeni Seznam_sezeni[TABLE_SIZE];

Parametry ziskane_parametry;

/*
 * funkce zavolana po zachyceni signalu sigint (ctrl+c)
 * uzavreni otevrenych spojeni a uvolneni alokovane pameti
 */
void sigint_handler(int sig)
{
	uzavri_spojeni_pro_iri();
	smazani_seznamu_sezeni(Seznam_sezeni);
	pcap_close(handler_rozhrani);

	exit(EXIT_SUCCESS);
}

/*
 * vycet typu chyb pro identifikovani chyby a vypsani upozorneni
 */
enum typy_chyb
{
	OK,
	CHYBNY_POCET_PARAMETRU,
	CHYBA_V_PARAMETRECH,
	CHYBEJICI_ROZHRANI,
	CHYBEJICI_SOUBOR,
	CHYBA_KOMPILACE_FILTRU,
	CHYBA_APLIKACE_FILTRU
};

/*
 * pole obsahujici chybove zpravy pro predtim definovane kody
 */
const char *CHYBOVE_ZPRAVY[] = {
"Vse OK\n",
"Byl zadan chybny pocet parametru.\n",
"Chybne zadane parametry. Spustenim aplikace s parametrem --help nebo -h ziskate\nseznam parametru nutnych pro spusteni aplikace\n",
"Chyba pri otevirani rozhrani.",
"Chyba pri otevirani .pcap souboru.",
"Chyba pri kompilaci filtru pro pcap.\n",
"Chyba pri aplikaci filtru pro pcap.\n"
};

const char *HELP = {
"Aplikaci je treba spustit s parametrem:\n"
"\t--interface <nazev_rozhrani_kde_provadet_odposlech>\n"
"nebo s parametrem:\n"
"\t--pcap_file <nazev_souboru_se_zachycenou_komunikaci>\n"
"Parametr pro filtrovani provozu podle specifiskych portu pro dane protokoly:\n"
"\t--specific_ports\n"
"Parametry pro urceni pozadovanych im protokolu:\n"
"\t--xmpp\n"
"\t--irc\n"
"\t--oscar\n"
"\t--ymsg\n"
};

/*
 * funkce zkontroluje zadani parametru a nastavi promennou rozhrani na 
 * hodnotu ziskanou z prikazove radky.
 */
int zpracuj_parametry(int argc, char **argv, Parametry *ziskane_parametry)
{
	static struct option long_options[] =
	{
		{"interface",              required_argument, 0, 'i'},
		{"pcap_file",              required_argument, 0, 'p'},
		{"specific_ports",         no_argument, 0, 's'},
		{"help",                   no_argument, 0, 'h'},
		{"xmpp",                   no_argument, 0, 'x'},
		{"irc",                    no_argument, 0, 'r'},
		{"oscar",                  no_argument, 0, 'o'},
		{"ymsg",                   no_argument, 0, 'y'},
		{0, 0, 0, 0}
	};

	int option_index = 0;
	int c;

	while((c = getopt_long (argc, argv, "i:p:shxroy", long_options, &option_index)) != -1)
	{
		switch (c)
		{
			case 0:
				break;

			case 'i':
					ziskane_parametry->rozhrani = optarg;
				break;

			case 'p':
					ziskane_parametry->pcap_file = optarg;
				break;

			case 's':
					ziskane_parametry->pouze_zname_porty = 1;
				break;

			case 'h':
					ziskane_parametry->help = 1;
				break;

			case 'x':
					ziskane_parametry->xmpp = 1;
				break;

			case 'r':
					ziskane_parametry->irc = 1;
				break;

			case 'o':
					ziskane_parametry->oscar = 1;
				break;

			case 'y':
					ziskane_parametry->ymsg = 1;
				break;

			case '?':
				return CHYBA_V_PARAMETRECH;
				break;

			default:
				return CHYBA_V_PARAMETRECH;
		}
	}

	if(optind < argc)
	{
		return CHYBA_V_PARAMETRECH;
	}

	if(((ziskane_parametry->rozhrani == NULL) && (ziskane_parametry->pcap_file == NULL)) && !ziskane_parametry->help)
	{
		return CHYBA_V_PARAMETRECH;
	}

	return OK;
}

/*
 * callback funkce volana po zachyceni packetu
 */
void ziskat_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
	struct sezeni *aktualni_sezeni = NULL;
	struct polozka_tabulky polozka;
	// ziskani dat z packetu
	int link_type = pcap_datalink(handler_rozhrani);
	struct data_packetu obsah = ziskej_obsah(header, packet, link_type);

	// kontrola jestli byla zachycena zprava nektereho ze sledovanych protokolu
	if(obsah.typ != NEZNAME)
	{
		polozka = ziskat_sezeni(Seznam_sezeni, obsah);
		aktualni_sezeni = polozka.sezeni;
	}

	// podle typu zjisteneho protokolu zpracujeme obsah
	if((obsah.typ == XMPP) && ziskane_parametry.xmpp)
	{
		if(obsah.stav_tcp == ZPRACOVAT)
		{
			zpracuj_xmpp(&obsah, aktualni_sezeni);
			free(obsah.data);
			if(aktualni_sezeni->ukonceno)
			{
				odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
			}
		}
		else if(obsah.stav_tcp == UKONCENO)
		{
			ukonci_xmpp(&obsah, aktualni_sezeni);
			odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
		}
	}
	else if((obsah.typ == IRC) && ziskane_parametry.irc)
	{
		if(obsah.stav_tcp == ZPRACOVAT)
		{
			zpracuj_irc(&obsah, aktualni_sezeni);
			free(obsah.data);
			if(aktualni_sezeni->ukonceno)
			{
				odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
			}
		}
		else if(obsah.stav_tcp == UKONCENO)
		{
			ukonci_irc(&obsah, aktualni_sezeni);
			odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
		}
	}
	else if((obsah.typ == OSCAR) && ziskane_parametry.oscar)
	{
		if(obsah.stav_tcp == ZPRACOVAT)
		{
			zpracuj_oscar(&obsah, aktualni_sezeni);
			if(aktualni_sezeni->ukonceno)
			{
				odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
			}
		}
		else if(obsah.stav_tcp == UKONCENO)
		{
			ukonci_oscar(&obsah, aktualni_sezeni);
			odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
		}
	}
	else if((obsah.typ == YMSG) && ziskane_parametry.ymsg)
	{
		if(obsah.stav_tcp == ZPRACOVAT)
		{
			zpracuj_ymsg(&obsah, aktualni_sezeni);
			if(aktualni_sezeni->ukonceno)
			{
				odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
			}
		}
		else if(obsah.stav_tcp == UKONCENO)
		{
			ukonci_ymsg(&obsah, aktualni_sezeni);
			odeber_sezeni(&Seznam_sezeni[polozka.index], aktualni_sezeni);
		}
	}
}

/*
 * hlavni funkce main
 */
int main(int argc, char **argv)
{
	char errbuf[PCAP_ERRBUF_SIZE];
	int chybovy_kod;
	
	// inicializace parametru
	ziskane_parametry.rozhrani = NULL;
	ziskane_parametry.pcap_file = NULL;
	ziskane_parametry.pouze_zname_porty = 0;
	ziskane_parametry.help = 0;
	ziskane_parametry.xmpp = 0;
	ziskane_parametry.irc = 0;
	ziskane_parametry.oscar = 0;
	ziskane_parametry.ymsg = 0;

	// zpracujeme parametry prikazove radky
	if((chybovy_kod = zpracuj_parametry(argc, argv, &ziskane_parametry)) != OK)
	{
		fprintf(stderr, "%s" ,CHYBOVE_ZPRAVY[chybovy_kod]);
		return EXIT_FAILURE;
	}

	if(ziskane_parametry.help)
	{
		printf("%s", HELP);
		return EXIT_SUCCESS;
	}

	// otevreni spojeni pro zasilani vytvorenych iri zprav
	if(otevri_spojeni_pro_iri())
	{
		return EXIT_FAILURE;
	}

	// nastaveni handler funkce pro signal ukonceni pomoci ctrl+c
	signal(SIGINT, sigint_handler);


	// inicializace seznamu a pomocnych globalnich promennych
	inicializace_seznamu_sezeni(Seznam_sezeni);



	if(ziskane_parametry.pcap_file != NULL)
	{
		handler_rozhrani = pcap_open_offline(ziskane_parametry.pcap_file, errbuf);

		if(handler_rozhrani == NULL)
		{
			perror(CHYBOVE_ZPRAVY[CHYBEJICI_SOUBOR]);
			return EXIT_FAILURE;
		}
	}
	else
	{
		// otevreni rozhrani v promiskuitnim modu pro zachytavani paketu
		handler_rozhrani = pcap_open_live(ziskane_parametry.rozhrani,BUFFER,1,1000,errbuf);

		if(handler_rozhrani == NULL)
		{
			perror(CHYBOVE_ZPRAVY[CHYBEJICI_ROZHRANI]);
			return EXIT_FAILURE;
		}
	}

	if(ziskane_parametry.pouze_zname_porty)
	{
		if(pcap_compile(handler_rozhrani, &fp, filter_exp, 0, 0) == -1)
		{
			fprintf(stderr, "%s" ,CHYBOVE_ZPRAVY[CHYBA_KOMPILACE_FILTRU]);
			return EXIT_FAILURE;
		}

		if(pcap_setfilter(handler_rozhrani, &fp) == -1)
		{
			fprintf(stderr, "%s" ,CHYBOVE_ZPRAVY[CHYBA_APLIKACE_FILTRU]);
			return EXIT_FAILURE;
		}
	}
	pcap_freecode(&fp);
	// smycka zachytavani packetu
	pcap_loop(handler_rozhrani, -1, ziskat_packet, NULL);

	// ukonceni po precteni pcap souboru
	if(ziskane_parametry.pcap_file != NULL)
	{
		uzavri_spojeni_pro_iri();
		smazani_seznamu_sezeni(Seznam_sezeni);
		pcap_close(handler_rozhrani);
	}

	return EXIT_SUCCESS;
}

