/*
 * Copyright (c) 2004-2007 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: tcpdump_compiler.c 6998 2007-06-18 04:22:11Z vladimir $
 */

/* C Standard Library headers. */
#include <stdbool.h>
#include <stdio.h>

/* Endace headers. */
#include "dagclarg.h"
#include "dagutil.h"
#include "dagpf_tcpdump.h"

/* CVS header. */
static const char * const kMainCvsHeader __attribute__((unused)) = "$Id: tcpdump_compiler.c 6998 2007-06-18 04:22:11Z vladimir $";
static const char* const kRevisionString = "$Revision: 6998 $";

/* Configuration options. Values here are the defaults. */

/**
 * Whether to reject unmatched packets 
 */
bool gRejectUnmatchedPackets = true;

/**
 * Non-zero means obfuscate rules
 */
bool gObfuscateRules = false;

const char* gOutfileName = NULL;
const char* gInfileName = NULL;
FILE* gInfile = NULL;
FILE* gOutfile = NULL;


#define FILENAME_BYTES 256
static char uInfileBuf[FILENAME_BYTES] = "";
static char uOutfileBuf[FILENAME_BYTES] = "";


/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr clarg);
static void configure(int argc, const char* argv[]);

int
main(int argc, const char* argv[])
{
	const char* chunk = NULL;
    unsigned int flags = 0;

	/* Read command-line arguments. */
	configure(argc, argv);

	assert(gInfile != NULL);
	assert(gOutfile != NULL);

	/* Configure default actions. */
	if (gRejectUnmatchedPackets)
	{
        flags |= DAGPF_TCPDUMP_REJECT_ICMP | DAGPF_TCPDUMP_REJECT_IGRP | DAGPF_TCPDUMP_REJECT_IP | DAGPF_TCPDUMP_REJECT_TCP | DAGPF_TCPDUMP_REJECT_UDP;
	}

    if (gObfuscateRules) { flags |= DAGPF_TCPDUMP_OBFUSCATE; }

	dagpf_tcpdump_to_endace(gInfile, gOutfile, flags);
	dagutil_verbose("finished.\n");

    free((void*)chunk);
	fclose(gInfile);
	fclose(gOutfile);

	return EXIT_SUCCESS;
}

/* Implementation of internal routines. */
static void
print_version(void)
{
	printf("tcpdump_compiler (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("tcpdump_compiler - turn tcpdump-style expressions into filters suitable for loading onto a Coprocessor-equipped Endace DAG card.\n");
	printf("Usage: tcpdump_compiler [options]\n");
	dagclarg_display_usage(clarg, stdout);
}


/* Commandline argument codes. */
enum
{
	CLA_DEFAULT_ACCEPT,
	CLA_DEFAULT_REJECT,
	CLA_HELP,
	CLA_INPUT_FILE,
	CLA_OBFUSCATE,
	CLA_OUTPUT_FILE,
	CLA_VERBOSE,
	CLA_VERSION
};


void
configure(int argc, const char* argv[])
{
	ClArgPtr clarg = NULL;
	FILE* errorfile = NULL;
	unsigned int index;
	int code;
	int result;
	int argindex;

	gInfile = stdin;
	gOutfile = stdout;
	dagutil_set_progname("tcpdump_compiler");

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, argv);
	dagclarg_add_string(clarg, "Name of a text file containing one tcpdump-style expression.", "--infile", 'i', "filename", uInfileBuf, FILENAME_BYTES, CLA_INPUT_FILE);
	dagclarg_add_description(clarg, CLA_INPUT_FILE, "Rules will be read from standard input if this option is not present.");
	
	dagclarg_add_string(clarg, "Name of a file to write the generated filters.", "--outfile", 'o', "filename", uOutfileBuf, FILENAME_BYTES, CLA_OUTPUT_FILE);
	dagclarg_add_description(clarg, CLA_OUTPUT_FILE, "Filters will be written to standard output if this option is not present.");
	
	dagclarg_add(clarg, "Obfuscate IP addresses and port numbers before processing.", "--obfuscate", 'n', CLA_OBFUSCATE);
	dagclarg_add_description(clarg, CLA_OBFUSCATE, "An obfuscated version of the input file will also be written to the current directory.");
	
	dagclarg_add(clarg, "Packets which do not match any rule are accepted.", "--accept", 'a', CLA_DEFAULT_ACCEPT);
	dagclarg_add(clarg, "Packets which do not match any rule are rejected (this is the default behaviour).", "--reject", 'r', CLA_DEFAULT_REJECT);
	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "Display version information.", "--version", 'V', CLA_VERSION);
	
	dagclarg_add(clarg, "This page.", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
		case CLA_DEFAULT_ACCEPT:
			gRejectUnmatchedPackets = false;
			break;

		case CLA_DEFAULT_REJECT:
			gRejectUnmatchedPackets = true;
			break;
			
		case CLA_HELP:
			print_usage(clarg);
			exit(EXIT_SUCCESS);
			break;

		case CLA_INPUT_FILE:
			gInfileName = (const char*) uInfileBuf;
			break;

		case CLA_OBFUSCATE:
			gObfuscateRules = true;
			break;

		case CLA_OUTPUT_FILE:
			gOutfileName = (const char*) uOutfileBuf;
			break;

		case CLA_VERBOSE:
			dagutil_inc_verbosity();
			errorfile = stderr;
			break;

		case CLA_VERSION:
			print_version();
			exit(EXIT_SUCCESS);
			break;

		default:
			if (argv[argindex][0] == '-')
			{
				/* Unknown option. */
				dagutil_error("unknown option %s\n", argv[argindex]);
				print_usage(clarg);
			    exit(EXIT_FAILURE);
			}
			break;
		}

		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == result)
	{
		/* Error occurred. */
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]); 
		}
		dagclarg_display_usage(clarg, stderr);
		exit(EXIT_FAILURE);
	}
	
    /* Display unprocessed arguments if verbose flag was given. */
	argv = (const char**) dagclarg_get_unprocessed_args(clarg, &argc);
	if ((NULL != argv) && (0 < argc) && (0 < dagutil_get_verbosity()))
	{
		for (index = 0; index < argc; index++)
		{
			dagutil_verbose("unprocessed argument: '%s'\n", argv[index]);
		}
	}

	if (gInfileName)
	{
		dagutil_verbose("reading rules from '%s'.\n", gInfileName);
		
		gInfile = fopen(gInfileName, "r");
		if (gInfile == NULL)
		{
			dagutil_error("could not open file '%s' for reading rules: %s\n", gInfileName, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
	else
	{
		dagutil_verbose("reading rules from standard input.\n");
	}

	if (gOutfileName)
	{
		dagutil_verbose("writing filters to '%s'.\n", gOutfileName);
		
		gOutfile = fopen(gOutfileName, "w");
		if (gOutfile == NULL)
		{
			dagutil_error("could not open file '%s' for writing filters: %s\n", gOutfileName, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
	else
	{
		dagutil_verbose("writing filters to standard output.\n");
	}

	if (gRejectUnmatchedPackets)
	{
		dagutil_verbose("will reject unmatched packets.\n");
	}
	else
	{
		dagutil_verbose("will accept unmatched packets.\n");
	}

	if (gObfuscateRules)
	{
		dagutil_verbose("will obfuscate IP addresses and port numbers.\n");
	}

    dagclarg_dispose(clarg);
}
