/*
 * Copyright (c) 2004-2005 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: snort_filters.c 6998 2007-06-18 04:22:11Z vladimir $
 */

/* Project headers. */
#include "filters.h"
#include "negation_tree.h"
#include "rule_lib.h"
#include "utilities.h"
#include "parser_lib.h" /* parse_single_line (generated by bison) */

/* Endace headers. */
#include "dag_platform.h"
#include "dagutil.h"

/* C Standard Library headers. */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

/* File header. */
#include "dagpf_snort.h"

#define RULE_ARRAY_LENGTH 65535

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


#ifndef INLINE
#if defined(__linux__) || defined(__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
#define INLINE inline
#elif defined(_WIN32)
#define INLINE __inline
#else
#define INLINE
#endif /* Platform-specific code. */
#endif /* INLINE */

/* Internal routines. */

static unsigned int count_inverse_rules(filter_rule_t** rule_array, uint16_t rule_count);
static unsigned int count_portrange_rules(filter_rule_t** rule_array, uint16_t rule_count);

static unsigned int get_filter_port_generality(cam_port_t* cam_port) __attribute__((unused));
static unsigned int get_filter_entry_generality(cam_entry_t* cam_entry) __attribute__((unused));
static unsigned int cam_entries_equal(const cam_entry_t* lhs, const cam_entry_t* rhs) __attribute__((unused));


/* Implementation of internal routines. */



static unsigned int
count_inverse_rules(filter_rule_t** rule_array, uint16_t rule_count)
{
	unsigned int count = 0;
	unsigned int index;

	for (index = 1; index <= rule_count; index++)
	{
		filter_rule_t* rule = rule_array[index];

		if (rule->src_inverse || rule->dest_inverse || rule->src_port.inverse || rule->dest_port.inverse)
		{
			count++;
		}
	}

	return count;
}


static unsigned int
count_portrange_rules(filter_rule_t** rule_array, uint16_t rule_count)
{
	unsigned int count = 0;
	unsigned int index;

	for (index = 1; index <= rule_count; index++)
	{
		filter_rule_t* rule = rule_array[index];

		if (rule->src_port.any || rule->dest_port.any || rule->src_port.inverse || rule->dest_port.inverse ||
			(!rule->src_port.any && (rule->src_port.first != rule->src_port.last)) || 
			(!rule->dest_port.any && (rule->dest_port.first != rule->dest_port.last)))
		{
			count++;
		}
	}

	return count;
}

static unsigned int
get_filter_port_generality(cam_port_t* port)
{
	/* "Generality" = number of don't care bits. */
	uint32_t value = (port->mask & 0xffff);

	return 16 - count_one_bits(value);
}


static unsigned int
get_filter_entry_generality(cam_entry_t* entry)
{
	unsigned int result = 0;

    /* "Generality" = number of don't care bits = number of zero bits. */
	result += 32 - count_one_bits(entry->source.mask);
	result += 32 - count_one_bits(entry->dest.mask);

	/* Don't care bits in ports. */
	result += get_filter_port_generality(&entry->src_port);
	result += get_filter_port_generality(&entry->dest_port);

	return result;
}


static unsigned int
cam_entries_equal(const cam_entry_t* lhs, const cam_entry_t* rhs)
{
	if (lhs->protocol.protocol != rhs->protocol.protocol)
	{
		return 0;
	}

	if ((lhs->src_port.port & lhs->src_port.mask) != (rhs->src_port.port & rhs->src_port.mask))
	{
		return 0;
	}

	if ((lhs->dest_port.port & lhs->dest_port.mask) != (rhs->dest_port.port & rhs->dest_port.mask))
	{
		return 0;
	}

	if ((lhs->source.ip4_addr & lhs->source.mask) != (rhs->source.ip4_addr & rhs->source.mask))
	{
		return 0;
	}

	if ((lhs->dest.ip4_addr & lhs->dest.mask) != (rhs->dest.ip4_addr & rhs->dest.mask))
	{
		return 0;
	}

	return 1;
}

/**
 * Convert Snort filters
 *
 * Convert Snort filters into Endace filters. Expand address/port lists and write
 * resulting rules to file.
 *
 * \param outfile     file for resulting rules 
 * \param rule_array  pointer to array of pointers to Snort filter rules
 * \param rule_count  number of rules in array
 */
static void
convert_snort_filters(FILE* outfile, filter_rule_t*** rule_array_p, uint16_t* rule_count, bool reject)
{
	time_t current_time = time(NULL);
	temp_filter_list_t list = {NULL, NULL};
	temp_filter_entry_t* temp_filter = NULL;
	uint32_t cam_index;
	uint16_t rule_index;
	cam_array_t* cam_array;
	cam_entry_t default_entry;
	char timebuf[256];
    filter_rule_t** rule_array;

    assert(rule_array_p);
    assert(rule_count);
    rule_array = *rule_array_p;

	/* Build initial list of temp_filter_entry_t*s, one per rule. */
	for (rule_index = 1; rule_index <= *rule_count; rule_index++)
	{
		filter_rule_t* rule = rule_array[rule_index];
        assert(rule);
		
		temp_filter = new_temp_filter_entry(rule);

		/* Add filter to list. */
		if (!list.head)
		{
			list.head = temp_filter;
			list.tail = temp_filter;
		}
		else
		{
			list.tail->next = temp_filter;
			list.tail = temp_filter;
		}
	}


	/* Expand source IP address lists. */
	temp_filter = list.head;
	while (temp_filter)
	{
		filter_rule_t* rule = temp_filter->rule;

		create_address_filter_entries(&temp_filter->src_addresses, temp_filter, rule->src_inverse, rule->source);
		temp_filter = temp_filter->next;
	}


	/* Expand destination IP address lists. */
	temp_filter = list.head;
	while (temp_filter)
	{
		filter_rule_t* rule = temp_filter->rule;

		create_address_filter_entries(&temp_filter->dst_addresses, temp_filter, rule->dest_inverse, rule->dest);
		temp_filter = temp_filter->next;
	}


	/* Expand source port lists. */
	temp_filter = list.head;
	while (temp_filter)
	{
		filter_rule_t* rule = temp_filter->rule;

		create_port_filter_entries(&temp_filter->src_ports, &rule->src_port);
		temp_filter = temp_filter->next;
	}


	/* Expand destination port lists. */
	temp_filter = list.head;
	while (temp_filter)
	{
		filter_rule_t* rule = temp_filter->rule;

		create_port_filter_entries(&temp_filter->dst_ports, &rule->dest_port);
		temp_filter = temp_filter->next;
	}


	/* Create CAM entries. */
	temp_filter = list.head;
	list.head = NULL;
	list.tail = NULL;
	while (temp_filter)
	{
		temp_filter_entry_t* next_entry = temp_filter->next;

		expand_temp_filter_entry(&list, temp_filter);
		if (temp_filter->rule->bidirectional)
		{
			addr_array_t temp_address;
			port_array_t temp_port;
			
			/* Swap source and destination addresses. */
			memcpy(&temp_address, &temp_filter->src_addresses, sizeof(addr_array_t));
			memcpy(&temp_filter->src_addresses, &temp_filter->dst_addresses, sizeof(addr_array_t));
			memcpy(&temp_filter->dst_addresses, &temp_address, sizeof(addr_array_t));
			
			/* Swap source and destination ports. */
			memcpy(&temp_port, &temp_filter->src_ports, sizeof(port_array_t));
			memcpy(&temp_filter->src_ports, &temp_filter->dst_ports, sizeof(port_array_t));
			memcpy(&temp_filter->dst_ports, &temp_port, sizeof(port_array_t));

			/* Add reverse direction CAM entries. */
			expand_temp_filter_entry(&list, temp_filter);
		}
		dispose_temp_filter_entry(temp_filter);
		
		temp_filter = next_entry;
	}


	/* Count final number of CAM entries. */
	cam_array = malloc(sizeof(cam_array_t));
	if (!cam_array)
	{
		dagutil_error("could not allocate memory for filters: %s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}

	cam_array->count = 0;
	cam_array->entries = NULL;
	temp_filter = list.head;
	while (temp_filter)
	{
		cam_array->count++;
		temp_filter = temp_filter->next;
	}

	dagutil_verbose("%u filter entries created.\n", cam_array->count);

	if (cam_array->count > 16384)
	{
		dagutil_error("%u filters required, 16384 available.\n", cam_array->count);
		exit(EXIT_FAILURE);
	}


	/* Create TCAM entry array. */
	errno = 0;
	cam_array->entries = (cam_entry_t**) malloc(sizeof(cam_entry_t*) * (1 + cam_array->count));
	if (!cam_array->entries)
	{
		dagutil_error("could not allocate memory for filter entry array: %s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	memset(cam_array->entries, 0, sizeof(cam_entry_t*) * (1 + cam_array->count));


	/* Transfer TCAM entries from list to array, and dispose of temporaries. */
	cam_index = 1;
	temp_filter = list.head;
	list.head = NULL;
	list.tail = NULL;
	while (temp_filter)
	{
		temp_filter_entry_t* next_entry = temp_filter->next;

		cam_array->entries[cam_index] = malloc(sizeof(cam_entry_t));
		memcpy(cam_array->entries[cam_index], &temp_filter->cam_entry, sizeof(cam_entry_t));
		cam_index++;

		dispose_temp_filter_entry(temp_filter);

		temp_filter = next_entry;
	}


	/* Write CAM entries to file. */
	strncpy(timebuf, asctime(localtime(&current_time)), 256);
	timebuf[strlen(timebuf) - 1] = '\0';
	fprintf(outfile, "# Filter file created at %s.\n", timebuf);
	for (cam_index = 1; cam_index <= cam_array->count; cam_index++)
	{
		display_filter_entry(outfile, cam_array->entries[cam_index]);
	}

	/* Add the final accept/reject filter for packets. */
	default_entry.src_port.port = 0;
	default_entry.src_port.mask = 0;
	default_entry.dest_port.port = 0;
	default_entry.dest_port.mask = 0;
	default_entry.source.ip4_addr = 0;
	default_entry.source.mask = 0;
	default_entry.dest.ip4_addr = 0;
	default_entry.dest.mask = 0;
	default_entry.colour = 0;
	default_entry.protocol.protocol = 0;
	default_entry.protocol.mask = 0;
	default_entry.tcp_flags.flags = 0;
	default_entry.tcp_flags.mask = 0;
	if (reject)
	{
		default_entry.action = REJECT;
	}
	else
	{
		default_entry.action = ACCEPT;
	}
	display_filter_entry(outfile, &default_entry);

	dagutil_verbose("%u filter entries written.\n", 1 + cam_array->count);
}


	
/**
 * Parse snort rules
 *
 * Read rules from 'rulefile' given in snort-like format. Store the
 * pointers to preprocessed rules in an array and display
 * them. Classify rules and save to per-protocol files.
 *
 * \param rulefile  pointer to file descriptor of rule file
 * \param rule_array  pointer to array of pointers to Snort filter rules
 * \param rule_count number of preprocessed rules.
 */
static void
read_snort_file(FILE* rulefile, filter_rule_t*** rule_array_p, uint16_t* rule_count)
{
	FILE* tcp_file = NULL;
	FILE* udp_file = NULL;
	FILE* icmp_file = NULL;
	FILE* ip_file = NULL;
	filter_rule_t* rule = NULL;
	unsigned int index;
	unsigned int icmp_count = 0;
	unsigned int ip_count = 0;
	unsigned int tcp_count = 0;
	unsigned int udp_count = 0;
	char linebuf[LINE_MAX];
    filter_rule_t** rule_array; 

	rule_array = (filter_rule_t**) calloc(RULE_ARRAY_LENGTH, sizeof(filter_rule_t*));
    assert(rule_array);
    assert(rule_count);
    *rule_array_p = rule_array;

	while (fgets(linebuf, LINE_MAX, rulefile))
	{
		dagutil_verbose("read line from file: %s\n", linebuf);

		rule = parse_single_line(linebuf,NULL); /* FIXME: it must be freed!!! */
		if (rule)
		{
			(*rule_count)++;
			rule->exact_colour = 2 * (*rule_count);
			rule_array[(*rule_count)] = rule;
		}
	}

	/* Classify rules and save to per-protocol files. */
	tcp_file = fopen("tcp_rules.txt", "w");
	udp_file = fopen("udp_rules.txt", "w");
	icmp_file = fopen("icmp_rules.txt", "w");
	ip_file = fopen("ip_rules.txt", "w");
	for (index = 1; index <= (*rule_count); index++)
	{
		filter_rule_t* rule = rule_array[index];
		if (rule->protocol.protocol == IPPROTO_TCP)
		{
			tcp_count++;
			display_rule(tcp_file, rule);
		}

		if (rule->protocol.protocol == IPPROTO_UDP)
		{
			udp_count++;
			display_rule(udp_file, rule);
		}

		if (rule->protocol.protocol == IPPROTO_IP)
		{
			ip_count++;
			display_rule(ip_file, rule);
		}

		if (rule->protocol.protocol == IPPROTO_ICMP)
		{
			icmp_count++;
			display_rule(icmp_file, rule);
		}
	}
	fclose(tcp_file);
	fclose(udp_file);
	fclose(icmp_file);
	fclose(ip_file);

    /* TODO: go through the pointer array and free each filter (created by parse_single_line) */

	dagutil_verbose("Rules read from file: %u\n", (*rule_count));
	dagutil_verbose("TCP rules:  %u\n", tcp_count);
	dagutil_verbose("UDP rules:  %u\n", udp_count);
	dagutil_verbose("ICMP rules: %u\n", icmp_count);
	dagutil_verbose("IP rules:   %u\n", ip_count);
	dagutil_verbose("Rules involving inverses: %u\n", count_inverse_rules(rule_array, *rule_count));
	dagutil_verbose("Rules involving port ranges: %u\n", count_portrange_rules(rule_array, *rule_count));
}


/**
 * Convert Snort rules
 *
 * Read rules from 'infile' given in Snort-like format. Classify
 * rules and save to per-protocol files. Convert Snort rules into
 * Endace rules, expand address/port lists and write resulting rules
 * to 'outfile'.
 *
 * \param infile   file containing Snort rules 
 * \param outfile  file for resulting Endace rules 
 * \param flags    rule obfuscation and protocol rejection flags
 *
 * \return non-zero in case of errors, zero otherwise
 */
int dagpf_snort_to_endace(FILE* infile, FILE* outfile, uint32_t flags) {

    uint16_t rule_index, rule_count = 0;            /* For assigning unique rule colours.  0 is unused. */
    filter_rule_t** rule_array = NULL; /* [1..rule_count] of rule_t* (65535 max).  Slot 0 is _NOT_ used. */
    bool reject;

    read_snort_file(infile, &rule_array, &rule_count);
    if (flags | DAGPF_SNORT_REJECT_ALL) {
        reject = true;
    }
    convert_snort_filters(outfile, &rule_array, &rule_count, reject);

    /* cleanup */
	for (rule_index = 1; rule_index <= rule_count; rule_index++) {
        filter_rule_t* rule = rule_array[rule_index];
        dispose_filter_rule(rule);
    }

    free(rule_array);
    
    return 0;
}
