/*
 * 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_filters.c 6998 2007-06-18 04:22:11Z vladimir $
 */
#include "filters.h"
#include "parser_lib.h"
#include "utilities.h"

#include "dagpf_tcpdump.h"

typedef struct LeafNode
{
	PtNodePtr mNode;
	struct LeafNode* mNext;

} LeafNode, * LeafNodePtr;


typedef struct FilterNode
{
	cam_entry_t mFilter;
	struct FilterNode* mNext;

} FilterNode, * FilterNodePtr;

/* Linked list for temporary storage of input lines */
typedef struct llist_s {
    struct llist_s *next;
    char line[0];
} llist_t;

typedef struct default_action_t {
    filter_action_t icmp;
    filter_action_t igrp;
    filter_action_t ip;
    filter_action_t tcp;
    filter_action_t udp;
} default_action_t;

#if 0
filter_action_t uDefaultIcmpAction = ACCEPT;
filter_action_t uDefaultIgrpAction = ACCEPT;
filter_action_t uDefaultIpAction = ACCEPT;
filter_action_t uDefaultTcpAction = ACCEPT;
filter_action_t uDefaultUdpAction = ACCEPT;
#endif

/* Internal routines. */
static FILE* get_obfuscated_outfile(void);
static LeafNodePtr new_leaf_node(PtNodePtr node, LeafNodePtr next);
static FilterNodePtr new_filter_node(FilterNodePtr next);
static FilterNodePtr duplicate_filter_list(FilterNodePtr filters);
static FilterNodePtr add_filters_to_list(FilterNodePtr list, FilterNodePtr new_filters);
static FilterNodePtr dispose_filter_list(FilterNodePtr list);
static void dispose_tree(PtNodePtr node);
static LeafNodePtr build_leaf_node_list(PtNodePtr parse_tree, LeafNodePtr leaf_head);
static void display_leaf_node_list(LeafNodePtr leaf_head, FILE* outfile);
static LeafNodePtr dispose_leaf_node_list(LeafNodePtr list);
static FilterNodePtr add_or_constraints_down(PtNodePtr node, FilterNodePtr filters);
static FilterNodePtr add_and_constraints_down(PtNodePtr node, PtNodePtr previous, FilterNodePtr filters);
static FilterNodePtr add_port_constraints(PtNodePtr node, FilterNodePtr filters);
static FilterNodePtr add_host_constraints(PtNodePtr node, FilterNodePtr filters);
static FilterNodePtr add_tcp_flags_constraints(PtNodePtr node, FilterNodePtr filters);
static void flip_filters(PtNodePtr node, FilterNodePtr filters);
static FilterNodePtr add_leaf_node_filters(LeafNodePtr leaf);
static unsigned int filter_is_not_wildcard(FilterNodePtr node);
static unsigned int write_filters(FilterNodePtr list, uint8_t protocol, FILE* outfile, unsigned int colour, default_action_t* default_action);
static uint8_t get_default_action(uint8_t protocol, default_action_t* default_action);
static void create_filters_from_leaf_nodes(LeafNodePtr leaf_head, FILE* outfile, default_action_t* default_action);
#ifndef NDEBUG
static void verify_parse_tree(PtNodePtr parse_tree);
static void verify_protocol_tree(PtNodePtr node);
#endif /* NDEBUG */


/* Implementation of internal routines. */
static FILE*
get_obfuscated_outfile(void)
{
	unsigned int index = 1;
	char filename[64];

	snprintf(filename, 64, "obfuscated_rule_%u.txt", index);

	while (access(filename, F_OK) == 0)
	{
		/* File exists, try again. */
		index++;
		snprintf(filename, 64, "obfuscated_rule_%u.txt", index);
	}

	return fopen(filename, "w");
}

/**
 * Read tcpdump file
 *
 * Read filter rules from 'infile' assuming tcpdump-like syntax (for
 * actual grammar see 'Coprocessor IP Filter manual'). Remove
 * comments, newlines and extra whitespace characters and return the
 * resulting text in a dynamically allocated buffer. Obfuscate
 * addresses/port numbers if required (see gObfuscateRules).
 *
 * \param infile     pointer to file descriptor of rule-file 
 * \param obfuscate  obfucate rules   
 *
 * \return pointer to the character buffer containing the rules
 *
 */
static const char*
read_tcpdump_file(FILE* infile, bool obfuscate)
{
	FILE* obfile = NULL;
	char* chunk = NULL;
	unsigned int byte_count = 0;
	unsigned int offset = 0;
	char linebuf[LINE_MAX];
	int len;		/* input line length */

	llist_t *lhead = NULL;	/* head of input line list */
	llist_t *ltail = NULL;	/* tail of input line list */
	llist_t *lent = NULL;	/* entry in Input line list */

	if (obfuscate)
	{
		srand(time(NULL));
		obfile = get_obfuscated_outfile();
	}

	/* Read tcpdump file and create a single character array. */

	/* Determine how much memory we need.  We won't be storing the terminating NULLs. */
	/* Build a linked list of the input lines */
	while (fgets(linebuf, LINE_MAX, infile))
	{
		/* Skip comment lines. */
		if ((linebuf[0] == '#') || (linebuf[0] == '%'))
		{
			continue;
		}


		len = strlen(linebuf);
		byte_count += len;

		/* Build up a list of the input lines */
		lent = (llist_t *)malloc(sizeof(llist_t) + len + 1);
		memcpy(lent->line, linebuf, len+1);

		if (lhead == NULL) {
		    /* first entry in list */
		    lhead = lent;
		    ltail = lent;
		} else {
		    /* Add the new entry to the end of the list */
		    ltail->next = lent;
		    ltail = lent;
		}
		lent->next = NULL;
	}

	/* Allocate memory (extra bytes are for a final newline and NULL terminator). */
	chunk = malloc(2 + byte_count);
	if (!chunk)
	{
		fprintf(stderr, "error allocating memory for input file: %s", strerror(errno));
		fflush(stderr);
		exit(EXIT_FAILURE);
	}

	/* Terminate the buffer. */
	chunk[byte_count - 1] = '\n';
	chunk[byte_count] = '\0';

	/* Merge all of the input lines into one buffer */
	for (lent=lhead; lent != NULL; lent=lent->next) {
	        char *linebuf = lent->line;
		unsigned int length = strlen(linebuf);
		int index;

		/* Skip comment lines. */
		if ((linebuf[0] == '#') || (linebuf[0] == '%'))
		{
			continue;
		}

		if (obfuscate)
		{
			char tokenbuf[LINE_MAX];
		    char* token;

			strncpy(tokenbuf, linebuf, LINE_MAX);
			token = strtok(tokenbuf, " .&=()\t\n");

			/* Randomize all IP addresses and port numbers. */
			while (token)
			{
				unsigned int offset = (unsigned int) (token - tokenbuf);
				int index;

				/* Randomize numbers such that xyz goes to a number between 100 and x99,
				 * i.e. we preserve the number of digits and restrict the new first digit 
				 * to be less than or equal to the original first digit. 
				 *
				 * Then we ensure that three-digit numbers beginning with 2 are less than 250.
				 *
				 * Together these stop IP address octets from being assigned invalid values (eg 957, 263).
				 */
				if (isdigit(token[0]))
				{
					for (index = strlen(token) - 1; index > 0; index--)
					{
						token[index] = (char) (((int) '0') + rand() % 10);
					}

					if (token[0] != '0')
					{
						token[0] = (char) (((int) '1') + rand() % ((int) token[0] - (int) '0'));
					}

					/* Deal with IP addresses. */
					if ((strlen(token) == 3) && (token[0] == '2'))
					{
						/* Keep the value below 250. */
						token[1] = (char) (((int) '0') + rand() % 5);
					}

					/* Write changed number back to main buffer. */
					for (index = 0; index < strlen(token); index++)
					{
						linebuf[index + offset] = token[index];
					}
				}

				token = strtok(NULL, " .&=()\t\n");
			}
		
			/* Write out randomized rule file. */
			if (obfile != NULL)
			{
				fputs(linebuf, obfile);
			}
		}

		/* Replace newline and linefeed characters with spaces. */
		for (index = 0; index < length; index++)
		{
			if ((linebuf[index] == '\r') || (linebuf[index] == '\n'))
			{
				linebuf[index] = ' ';
			}
		}

		/* Copy up to and including the space. */
		memcpy(&chunk[offset], linebuf, length);
		offset += length;
	}

	/* Free the small line list */
	while (lhead != NULL) {
	    lent = lhead->next;
	    free(lhead);
	    lhead = lent;
	}

	/* Close the obfuscated file. */
	if (obfile != NULL)
	{
		if (-1 == fclose(obfile))
		{
			dagutil_warning("error closing obfuscated rule file: %s\n", strerror(errno));
		}
	}

	/* Display the buffer. */
	dagutil_verbose("%s\n", chunk);

	return (const char*) chunk;
}


static LeafNodePtr
new_leaf_node(PtNodePtr node, LeafNodePtr leaf_head)
{
	LeafNodePtr result = (LeafNodePtr) malloc(sizeof(LeafNode));

	assert(node != NULL);

	if (result)
	{
		result->mNode = node;
		result->mNext = leaf_head;
	}

	return result;
}


static FilterNodePtr
new_filter_node(FilterNodePtr next)
{
	FilterNodePtr result = (FilterNodePtr) malloc(sizeof(FilterNode));

	if (result)
	{
		memset(&result->mFilter, 0, sizeof(cam_entry_t));
		result->mFilter.action = ACCEPT;
		result->mNext = next;
	}

	return result;
}


static FilterNodePtr
duplicate_filter_list(FilterNodePtr filters)
{
	FilterNodePtr current_old = filters;
	FilterNodePtr result = NULL;
	FilterNodePtr current_new = NULL;

	assert(filters != NULL);

	while (current_old)
	{
		FilterNodePtr new_filter = new_filter_node(NULL);

		/* Copy old filter values to new filter. */
		memcpy(&new_filter->mFilter, &current_old->mFilter, sizeof(cam_entry_t));
		
		if (result == NULL)
		{
			result = new_filter;
		}
		else
		{
			current_new->mNext = new_filter;
		}

		current_new = new_filter;
		current_old = current_old->mNext;
	}

	return result;
}


static FilterNodePtr
add_filters_to_list(FilterNodePtr list, FilterNodePtr new_filters)
{
	FilterNodePtr current = list;

	if (current == NULL)
	{
		return new_filters;
	}

	while (current->mNext != NULL)
	{
		current = current->mNext;
	}

	assert(current != NULL);
	assert(current->mNext == NULL);

	/* Add the new filters to the tail of the list. */
	current->mNext = new_filters;
	return list;
}


static FilterNodePtr
dispose_filter_list(FilterNodePtr list)
{
	FilterNodePtr current = list;

	while (current != NULL)
	{
		FilterNodePtr next = current->mNext;

		free(current);

		current = next;
	}

	return NULL;
}


static LeafNodePtr
build_leaf_node_list(PtNodePtr node, LeafNodePtr leaf_head)
{
	PtNodePtr child = ptn_get_next_child(node, NULL);
	node_t node_type = ptn_get_type(node);

	assert(node != NULL);

	if (NULL == child)
	{
		/* At a leaf node - add new entry to list. */
		return new_leaf_node(node, leaf_head);
	}

	if ((PTN_IP == node_type) || (PTN_ANDNOT == node_type) || (PTN_OR == node_type))
	{
		/* Add every child to the list. */
		while (child)
		{
			leaf_head = build_leaf_node_list(child, leaf_head);

			child = ptn_get_sibling(child);
		}
	}
	else
	{
		/* Add only the first child to the list. */
		leaf_head = build_leaf_node_list(child, leaf_head);
	}

	return leaf_head;
}


static void
display_leaf_node_list(LeafNodePtr leaf_head, FILE* outfile)
{
	LeafNodePtr current = leaf_head;

	fprintf(outfile, "\nDisplaying leaf nodes:\n");

	while (current)
	{
		ptn_display(current->mNode, outfile);
		current = current->mNext;
	}
}


static LeafNodePtr
dispose_leaf_node_list(LeafNodePtr list)
{
	LeafNodePtr current = list;

	while (current != NULL)
	{
		LeafNodePtr next = current->mNext;

		free(current);

		current = next;
	}

	return NULL;
}

static FilterNodePtr
add_or_constraints_down(PtNodePtr node, FilterNodePtr filters)
{
	PtNodePtr current = ptn_get_next_child(node, NULL);
	FilterNodePtr result = NULL;

	assert(filters != NULL);
	assert(PTN_OR == ptn_get_type(node));

	/* For each child duplicate the filters and add constraints. */
	while (current)
	{
		/* Duplicate the filters. */
		FilterNodePtr subtree_filters = duplicate_filter_list(filters);
		node_t node_type = ptn_get_type(current);

		/* Descend into the OR branch. */
		if (PTN_OR == node_type)
		{
			subtree_filters = add_or_constraints_down(current, subtree_filters);
		}
		else if (PTN_AND == node_type)
		{
			subtree_filters = add_and_constraints_down(current, NULL, subtree_filters);
		}
		else if (PTN_PORT == node_type)
		{
			subtree_filters = add_port_constraints(current, subtree_filters);
		}
		else if (PTN_HOST == node_type)
		{
			subtree_filters = add_host_constraints(current, subtree_filters);
		}
		else if (PTN_TCP_FLAGS == node_type)
		{
			subtree_filters = add_tcp_flags_constraints(current, subtree_filters);
		}
		else if (PTN_NOT == node_type)
		{
			/* Flip filters from ACCEPT to REJECT. */
			flip_filters(current, result);
		}
		else
		{
			assert(0);
		}
		
		/* Add the resulting filters to the result list. */
		result = add_filters_to_list(subtree_filters, result);

		current = ptn_get_sibling(current);
	}

	/* Dispose of original filters. */
	filters = dispose_filter_list(filters);

	return result;
}


static FilterNodePtr
add_and_constraints_down(PtNodePtr node, PtNodePtr previous, FilterNodePtr filters)
{
	PtNodePtr current = ptn_get_next_child(node, NULL);
	FilterNodePtr result = filters;

	assert(filters != NULL);

	/* For each child except 'previous', add constraints. */
	while (current)
	{
		if (current != previous)
		{
			node_t node_type = ptn_get_type(current);

			if (PTN_PORT == node_type)
			{
				result = add_port_constraints(current, result);
			}
			else if (PTN_HOST == node_type)
			{
				result = add_host_constraints(current, result);
			}
			else if (PTN_TCP_FLAGS == node_type)
			{
				result = add_tcp_flags_constraints(current, result);
			}
			else if (PTN_AND == node_type)
			{
				/* Recursive call to deal with AND nodes. */
				result = add_and_constraints_down(current, NULL, result);
			}
			else if (PTN_OR == node_type)
			{
				/* Copy filters so there's one per branch from the OR node. */
				result = add_or_constraints_down(current, result);
			}
			else if (PTN_NOT == node_type)
			{
				/* Flip filters from ACCEPT to REJECT. */
				flip_filters(current, result);
			}
			else
			{
				assert(0);
			}
		}

		current = ptn_get_sibling(current);
	}

	return result;
}


static FilterNodePtr
add_port_constraints(PtNodePtr node, FilterNodePtr filters)
{
	qualifiers_t port_quals = ptn_get_qualifiers(node);
	uint16_t network_port = htons(ptn_get_port(node));
	FilterNodePtr filter_node = filters;
	FilterNodePtr result = filters;

	assert(filters != NULL);

	while (filter_node)
	{
		if (port_quals == QUAL_SRC)
		{
			/* Add source port to each filter. */
			if (filter_node->mFilter.src_port.port == 0)
			{
				filter_node->mFilter.src_port.port = network_port;
				filter_node->mFilter.src_port.mask = 0xffff;
			}
			else
			{
				dagutil_warning("attempted to filter on source port twice:\n");
				ptn_display(node, stderr);
			}
		}
		else if (port_quals == QUAL_DST)
		{
			/* Add destination port to each filter. */
			if (filter_node->mFilter.dest_port.port == 0)
			{
				filter_node->mFilter.dest_port.port = network_port;
				filter_node->mFilter.dest_port.mask = 0xffff;
			}
			else
			{
				dagutil_warning("attempted to filter on destination port twice:\n");
				ptn_display(node, stderr);
			}
		}
		else
		{
			/* If filter has both ports free, duplicate it and put the port as source in one, dest in the other. 
			 * Otherwise put the port in whichever of src/dest is free.
			 */
			if ((filter_node->mFilter.src_port.port == 0) && (filter_node->mFilter.dest_port.port == 0))
			{
				FilterNodePtr new_node = new_filter_node(result);

				memcpy(&new_node->mFilter, &filter_node->mFilter, sizeof(cam_entry_t));
					
				new_node->mFilter.src_port.port = network_port;
				new_node->mFilter.src_port.mask = 0xffff;
				filter_node->mFilter.dest_port.port = network_port;
				filter_node->mFilter.dest_port.mask = 0xffff;

				result = new_node;
			}
			else if (filter_node->mFilter.src_port.port == 0)
			{
				filter_node->mFilter.src_port.port = network_port;
				filter_node->mFilter.src_port.mask = 0xffff;
			}
			else if (filter_node->mFilter.dest_port.port == 0)
			{
				filter_node->mFilter.dest_port.port = network_port;
				filter_node->mFilter.dest_port.mask = 0xffff;
			}
			else
			{
				dagutil_warning("attempted to filter on a source or destination port twice:\n");
				ptn_display(node, stderr);
			}
		}

		filter_node = filter_node->mNext;
	}

	return result;
}


static FilterNodePtr
add_host_constraints(PtNodePtr node, FilterNodePtr filters)
{
	qualifiers_t host_quals = ptn_get_qualifiers(node);
	in_addr_t network_host = ptn_get_host(node);
	uint32_t network_mask = ptn_get_netmask(node);
	FilterNodePtr filter_node = filters;
	FilterNodePtr result = filters;

	assert(filters != NULL);

	while (filter_node)
	{
		if (host_quals == QUAL_SRC)
		{
			/* Add source host and mask to each filter. */
			if (filter_node->mFilter.source.ip4_addr == 0)
			{
				filter_node->mFilter.source.ip4_addr = network_host;
				filter_node->mFilter.source.mask = network_mask;
			}
			else
			{
				dagutil_warning("attempted to filter on source IP address twice:\n");
				ptn_display(node, stderr);
			}
		}
		else if (host_quals == QUAL_DST)
		{
			/* Add destination host and mask to each filter. */
			if (filter_node->mFilter.dest.ip4_addr == 0)
			{
				filter_node->mFilter.dest.ip4_addr = network_host;
				filter_node->mFilter.dest.mask = network_mask;
			}
			else
			{
				dagutil_warning("attempted to filter on destination IP address twice:\n");
				ptn_display(node, stderr);
			}
		}
		else
		{
			/* If filter has both addresses free, duplicate it and put the address as source in one, dest in the other.
			 * Otherwise put the address in whichever of source/dest is free.
			 */
			if ((filter_node->mFilter.source.ip4_addr == 0) && (filter_node->mFilter.dest.ip4_addr == 0))
			{
				FilterNodePtr new_node = new_filter_node(result);

				memcpy(&new_node->mFilter, &filter_node->mFilter, sizeof(cam_entry_t));
					
				new_node->mFilter.source.ip4_addr = network_host;
				new_node->mFilter.source.mask = network_mask;
				filter_node->mFilter.dest.ip4_addr = network_host;
				filter_node->mFilter.dest.mask = network_mask;

				result = new_node;
			}
			else if (filter_node->mFilter.source.ip4_addr == 0)
			{
				filter_node->mFilter.source.ip4_addr = network_host;
				filter_node->mFilter.source.mask = network_mask;
			}
			else if (filter_node->mFilter.dest.ip4_addr == 0)
			{
				filter_node->mFilter.dest.ip4_addr = network_host;
				filter_node->mFilter.dest.mask = network_mask;
			}
			else
			{
				dagutil_warning("attempted to filter on a source or destination IP address twice:\n");
				ptn_display(node, stderr);
			}
		}

		filter_node = filter_node->mNext;
	}

	return result;
}


static FilterNodePtr
add_tcp_flags_constraints(PtNodePtr node, FilterNodePtr filters)
{
	uint8_t value = ptn_get_tcp_flags_value(node);
	uint8_t mask = ptn_get_tcp_flags_mask(node);
	FilterNodePtr filter_node = filters;
	FilterNodePtr result = filters;

	assert(filters != NULL);
	assert(mask != 0);

	while (filter_node)
	{
		/* Add TCP flags to each filter. */
		if (filter_node->mFilter.tcp_flags.mask == 0)
		{
			filter_node->mFilter.tcp_flags.flags = value;
			filter_node->mFilter.tcp_flags.mask = mask;
		}
		else
		{
			dagutil_warning("attempted to filter on TCP flags twice:\n");
			ptn_display(node, stderr);
		}

		filter_node = filter_node->mNext;
	}

	return result;
}


static void
flip_filters(PtNodePtr node, FilterNodePtr filters)
{
	FilterNodePtr filter_node = filters;
	
	while (filter_node)
	{
		if (ACCEPT == filter_node->mFilter.action)
		{
			filter_node->mFilter.action = REJECT;
		}
		else
		{
			dagutil_warning("attempted to reverse sense of filters twice:\n");
			ptn_display(node, stderr);
		}

		filter_node = filter_node->mNext;
	}
}


static FilterNodePtr
add_leaf_node_filters(LeafNodePtr leaf_head)
{
	FilterNodePtr filters = new_filter_node(NULL); /* Initial filter for this leaf. */
	PtNodePtr current = leaf_head->mNode;
	PtNodePtr previous = NULL;

	/* Move from the leaf node to the root, adding filters as we go. */
	while (current)
	{
		node_t node_type = ptn_get_type(current);

		if (node_type == PTN_PORT)
		{
			filters = add_port_constraints(current, filters);
		}
		else if (node_type == PTN_HOST)
		{
			filters = add_host_constraints(current, filters);
		}
		else if (node_type == PTN_TCP_FLAGS)
		{
			filters = add_tcp_flags_constraints(current, filters);
		}
		else if (node_type == PTN_PROTOCOL)
		{
			uint8_t protocol = ptn_get_protocol(current);
			FilterNodePtr filter_node = filters;
			
			while (filter_node)
			{
				if (0 == filter_node->mFilter.protocol.protocol)
				{
					filter_node->mFilter.protocol.protocol = protocol;
					filter_node->mFilter.protocol.mask = 0xff;
				}
				else
				{
					dagutil_warning("attempted to set protocol twice:\n");
					ptn_display(current, stderr);
				}

				filter_node = filter_node->mNext;
			}

			/* Once we've seen the protocol node, there are no more nodes of interest. */
			return filters;
		}
		else if ((node_type == PTN_OR) || (node_type == PTN_IP))
		{
			/* Keep moving up the tree. */
		}
		else if (node_type == PTN_ANDNOT)
		{
			/* Flip filters to REJECT instead of ACCEPT. */
			flip_filters(current, filters);
		}
		else if (node_type == PTN_AND)
		{
			/* Add constraints from all children except the one just visited. */
			filters = add_and_constraints_down(current, previous, filters);
		}
		else if (node_type == PTN_NOT)
		{
			/* Flip filters to REJECT instead of ACCEPT. */
			flip_filters(current, filters);
		}
		else
		{
			assert(0);
		}

		previous = current;
		current = ptn_get_parent(current);
	}

	/* Reached the parse tree's root. 
	 * Malformed tree - should have hit a protocol node by now (and exited).
	 */
	assert(0);
	return filters;
}


/* Check to see if the filter is a wildcard (all don't care entries). */
static unsigned int
filter_is_not_wildcard(FilterNodePtr node)
{
	cam_entry_t* cam_entry = &node->mFilter;

	if (cam_entry->src_port.mask != 0)
	{
		return 1;
	}

	if (cam_entry->dest_port.mask != 0)
	{
		return 1;
	}

	if (cam_entry->source.mask != 0)
	{
		return 1;
	}

	if (cam_entry->dest.mask != 0)
	{
		return 1;
	}

	if (cam_entry->tcp_flags.mask != 0)
	{
		return 1;
	}

	return 0;
}


static unsigned int
write_filters(FilterNodePtr list, 
              uint8_t protocol,
              FILE* outfile,
              unsigned int colour,
              default_action_t* default_action)
{
	FilterNodePtr current = list;
	filter_action_t action = get_default_action(protocol, default_action);
	cam_entry_t default_entry;
	
	while (current)
	{
		if (filter_is_not_wildcard(current))
		{
			/* Assign a colour to the filter. */
			colour++;
			current->mFilter.colour = colour;
			
			display_filter_entry(outfile, &current->mFilter);
		}
		
		current = current->mNext;
	}

	/* Add the final accept/reject filter (if it differs from the catch-all default). */
	if ((protocol == IPPROTO_IP) || (action != get_default_action(IPPROTO_IP, default_action)))
	{
		memset(&default_entry, 0, sizeof(cam_entry_t));
		
		colour++;
		default_entry.colour = colour;
		default_entry.protocol.protocol = protocol;
		default_entry.protocol.mask = 0xff;
		default_entry.action = action;
		
		display_filter_entry(outfile, &default_entry);
	}

	return colour;
}


static uint8_t
get_default_action(uint8_t protocol, default_action_t* default_action)
{
	if (IPPROTO_IP == protocol)
	{
		return default_action->ip;
	}
	else if (IPPROTO_TCP == protocol)
	{
		return default_action->tcp;
	}
	else if (IPPROTO_UDP == protocol)
	{
		return default_action->udp;
	}
	else if (IPPROTO_ICMP == protocol)
	{
		return default_action->icmp;
	}
	else if (IPPROTO_IGRP == protocol)
	{
		return default_action->igrp;
	}
	
	assert(0);
	return ACCEPT;
}


static void
create_filters_from_leaf_nodes(LeafNodePtr leaf_head, FILE* outfile, default_action_t* default_action)
{
	LeafNodePtr current = leaf_head;
	FilterNodePtr icmp_filter_list = NULL;
	FilterNodePtr igrp_filter_list = NULL;
	FilterNodePtr ip_filter_list = NULL;
	FilterNodePtr tcp_filter_list = NULL;
	FilterNodePtr udp_filter_list = NULL;
	time_t current_time = time(NULL);
	unsigned int filter_count;
	cam_entry_t default_entry;
	char timebuf[256];

	/* For each leaf node, create one or more filter entries. 
	 * This is done by tracing up from the leaf node to the root, adding more filtering
	 * information as we go.
	 */

	while (current)
	{
		FilterNodePtr new_filters = add_leaf_node_filters(current);

		/* Add new filters to end of current list. */
		if (new_filters->mFilter.protocol.protocol == IPPROTO_TCP)
		{
			tcp_filter_list = add_filters_to_list(tcp_filter_list, new_filters);
		}
		else if (new_filters->mFilter.protocol.protocol == IPPROTO_UDP)
		{
			udp_filter_list = add_filters_to_list(udp_filter_list, new_filters);
		}
		else if (new_filters->mFilter.protocol.protocol == IPPROTO_ICMP)
		{
			icmp_filter_list = add_filters_to_list(icmp_filter_list, new_filters);
		}
		else if (new_filters->mFilter.protocol.protocol == IPPROTO_IGRP)
		{
			igrp_filter_list = add_filters_to_list(igrp_filter_list, new_filters);
		}
		else if (new_filters->mFilter.protocol.protocol == IPPROTO_IP)
		{
			ip_filter_list = add_filters_to_list(ip_filter_list, new_filters);
		}
		else
		{
			assert(0);
		}

		current = current->mNext;
	}

	/* Write filters to file. */
	strncpy(timebuf, asctime(localtime(&current_time)), 256);
	timebuf[strlen(timebuf) - 1] = '\0';
	fprintf(outfile, "# Filter file created at %s.\n", timebuf);
	
	/* Protocol specific filters. */
	filter_count = write_filters(tcp_filter_list, IPPROTO_TCP, outfile, 0, default_action);
	filter_count = write_filters(udp_filter_list, IPPROTO_UDP, outfile, filter_count, default_action);
	filter_count = write_filters(icmp_filter_list, IPPROTO_ICMP, outfile, filter_count, default_action);
	filter_count = write_filters(igrp_filter_list, IPPROTO_IGRP, outfile, filter_count, default_action);
	filter_count = write_filters(ip_filter_list, IPPROTO_IP, outfile, filter_count, default_action);

	/* Global accept/reject default filter. */
	memset(&default_entry, 0, sizeof(cam_entry_t));
	default_entry.colour = 0;
	default_entry.protocol.protocol = 0;
	default_entry.protocol.mask = 0;
	default_entry.action = REJECT;
	
	display_filter_entry(outfile, &default_entry);

	dagutil_verbose("%u filter entries written.\n", filter_count);

    dispose_filter_list(tcp_filter_list);
    dispose_filter_list(udp_filter_list);
    dispose_filter_list(icmp_filter_list);
    dispose_filter_list(igrp_filter_list);
    dispose_filter_list(ip_filter_list);
}


#ifndef NDEBUG
static void
verify_parse_tree(PtNodePtr node)
{
	PtNodePtr child = ptn_get_next_child(node, NULL);
	PtNodePtr parent = ptn_get_parent(node);
	PtNodePtr sibling = ptn_get_sibling(node);
	qualifiers_t quals = ptn_get_qualifiers(node);
	node_t node_type = ptn_get_type(node);

	/* Verify the node. */
	switch (node_type)
	{
		case PTN_IP:
		{
			assert(NULL == parent);
			assert(NULL == sibling);
			assert(NULL != child);
			assert(QUAL_INVALID == quals);
			break;
		}
	
		case PTN_PROTOCOL:
		{
			assert(NULL != parent);
			assert(QUAL_INVALID == quals);
			break;
		}
	
		case PTN_PORT:
		case PTN_HOST:
		{
			assert(NULL != parent);
			assert(NULL == child);
			assert((QUAL_SRC == quals) || (QUAL_DST == quals) || (QUAL_BOTH == quals));
			break;
		}
	
		case PTN_TCP_FLAGS:
		{
			assert(NULL != parent);
			assert(NULL == child);
			assert(NULL == sibling);
			assert(QUAL_INVALID == quals);
			break;
		}
	
		case PTN_AND:
		case PTN_ANDNOT:
		case PTN_OR:
		case PTN_NOT:
		{
			assert(NULL != parent);
			assert(NULL != child);
			assert(QUAL_INVALID == quals);
			break;
		}
		
		default:
		{
			assert(0); /* Invalid node type. */
		}
	}

	/* Verify the children. */
	while (child)
	{
		verify_parse_tree(child);
		child = ptn_get_sibling(child);
	}
}


static void
verify_protocol_tree(PtNodePtr node)
{
	PtNodePtr protocol_node = node;

	/* Verify the node. */
	while (protocol_node)
	{
		PtNodePtr child = ptn_get_next_child(node, NULL);
		PtNodePtr parent = ptn_get_parent(node);
		qualifiers_t quals = ptn_get_qualifiers(node);
		node_t node_type = ptn_get_type(node);

		assert(PTN_PROTOCOL == node_type);
		assert(QUAL_INVALID == quals);
		assert(NULL == parent);
		assert(NULL == child);

		protocol_node = ptn_get_sibling(protocol_node);
	}
}
#endif /* NDEBUG */

static void
dispose_tree(PtNodePtr node) {
    PtNodePtr sibling, next, child;

    sibling = ptn_get_sibling(node);
    while (sibling) {
        next = ptn_get_sibling(sibling);
        dispose_tree(sibling);
        sibling = next;
    }
    
    child = ptn_get_next_child(node, NULL);
    if (child) {
        dispose_tree(child);
    } 

    ptn_dispose(node);
}

static void
find_default_action(PtNodePtr parse_tree, PtNodePtr protocol_tree, default_action_t* default_action)
{
	PtNodePtr protocol_node;

	/* Check protocol tree for modifications to the default actions. */
	if (protocol_tree)
	{
		/* There's a specific list of excluded Layer 4 protocols, 
		 * so set default action for these to ACCEPT. 
		 */
		default_action->tcp = ACCEPT;
		default_action->udp = ACCEPT;
		default_action->icmp = ACCEPT;
		default_action->igrp = ACCEPT;

		protocol_node = protocol_tree;
		while (protocol_node)
		{
			uint8_t protocol = ptn_get_protocol(protocol_node);

			/* Adjust default actions. */
			switch (protocol)
			{
				case IPPROTO_TCP: default_action->tcp = REJECT; break;
				case IPPROTO_UDP: default_action->udp = REJECT; break;
				case IPPROTO_ICMP: default_action->icmp = REJECT; break;
				case IPPROTO_IGRP: default_action->igrp = REJECT; break;
				default: assert(0);
			}
			
			protocol_node = ptn_get_sibling(protocol_node);
		}
	}

	protocol_node = ptn_get_next_child(parse_tree, NULL);
	while (protocol_node)
	{
		PtNodePtr first_child = ptn_get_next_child(protocol_node, NULL);
		uint8_t protocol = ptn_get_protocol(protocol_node);
		filter_action_t* action = NULL;
		
		if (IPPROTO_TCP == protocol)
		{
			action = &(default_action->tcp);
		}
		else if (IPPROTO_UDP == protocol)
		{
			action = &default_action->udp;
		}
		else if (IPPROTO_ICMP == protocol)
		{
			action = &default_action->icmp;
		}
		else
		{
			assert(0);
		}
	
		if (first_child == NULL)
		{
			/* Default rule is accept. */
			(*action) = ACCEPT;
		}
		else
		{
			node_t node_type = ptn_get_type(first_child);
			
			switch (node_type)
			{
				case PTN_ANDNOT:
				{
					/* Rules specify packets to reject, so default is to accept. */
					(*action) = ACCEPT;
					break;
				}
				
				case PTN_AND:
				case PTN_OR:
				{
					/* Rules specify packets to accept, so default is to reject. */
					(*action) = REJECT;
					break;
				}
				
				default:
				{
					assert(0);
				}
			}
		}
	
		protocol_node = ptn_get_sibling(protocol_node);
	}
}


/**
 * Convert Tcpdump filters
 *
 * Create a parse tree from the rules described in 'chunk' and display
 * it on stdout. Verify the tree (if compiled with debugging enabled).
 * Build and display a list of leaf nodes needed to create a complete
 * filter set. 
 *
 * Create filters from leaf nodes and print them to 'outfile'.
 *
 * \param chunk           string of preprocessed tcpdump rules
 * \param outfile         pointer to file descriptor of the output file
 * \param default_action  default actions for protocols (NULL results in accept-all)
 */
static void
convert_tcpdump_filters(const char* chunk, FILE* outfile, default_action_t* default_action)
{
	PtNodePtr parse_tree = NULL;
	PtNodePtr protocol_tree = NULL;
	LeafNodePtr leaf_head = NULL;
    default_action_t accept_all;

	assert(chunk != NULL);

    if (!default_action) {
        accept_all.icmp = ACCEPT;
        accept_all.igrp = ACCEPT;
        accept_all.ip = ACCEPT;
        accept_all.tcp = ACCEPT;
        accept_all.udp = ACCEPT;

        default_action = &accept_all;
    }

	/* Create and display the parse tree. */
	tcpdump_rule_parse(chunk, &parse_tree, &protocol_tree);
	ptn_display_recursive(parse_tree, stdout);
	if (protocol_tree)
	{
		PtNodePtr sibling = ptn_get_sibling(protocol_tree);

		ptn_display_recursive(protocol_tree, stdout);
		while (sibling)
		{
			ptn_display_recursive(sibling, stdout);
			sibling = ptn_get_sibling(sibling);
		}
	}

#ifndef NDEBUG
	/* Verify the parse tree. */
	verify_parse_tree(parse_tree);
	if (protocol_tree)
	{
		verify_protocol_tree(protocol_tree);
	}
#endif /* NDEBUG */
	
	/* Find the default actions for each protocol. */
	find_default_action(parse_tree, protocol_tree, default_action);

	/* Build and display a list of leaf nodes needed to create a complete filter set. */
	leaf_head = build_leaf_node_list(parse_tree, NULL);
	display_leaf_node_list(leaf_head, stdout);
	
	/* Create filters from leaf nodes. */
	create_filters_from_leaf_nodes(leaf_head, outfile, default_action);

    /* cleanup, free allocated buffers */
    dispose_leaf_node_list(leaf_head);
    dispose_tree(parse_tree);
    dispose_tree(protocol_tree);
}


/**
 * Convert Tcpdump rules
 *
 * Read rules from 'infile' given in Tcpdump-like format and convert
 * them into Endace rules. Only a subset of the formats is supported,
 * for actual grammar see 'Coprocessor IP Filter manual'. Obfuscate
 * addresses/port numbers and create accept/reject rules for the
 * supported protocols if required by the flags.
 *
 * \param infile   file containing Tcpdump 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_tcpdump_to_endace(FILE* infile, FILE* outfile, uint32_t flags) {
    default_action_t action;
    bool obfuscate = false;
    const char* chunk;

    /* parameter conversion */
    if (flags & DAGPF_TCPDUMP_OBFUSCATE) {
        obfuscate = true;
    }

    action.icmp = ACCEPT;
    action.igrp = ACCEPT;
    action.ip   = ACCEPT;
    action.tcp  = ACCEPT;
    action.udp  = ACCEPT;

    if (flags & DAGPF_TCPDUMP_REJECT_ICMP) { action.icmp = REJECT; }
    if (flags & DAGPF_TCPDUMP_REJECT_IGRP) { action.igrp = REJECT; }
    if (flags & DAGPF_TCPDUMP_REJECT_IP)   { action.ip   = REJECT; }
    if (flags & DAGPF_TCPDUMP_REJECT_TCP)  { action.tcp  = REJECT; }
    if (flags & DAGPF_TCPDUMP_REJECT_UDP)  { action.udp  = REJECT; }

    chunk = read_tcpdump_file(infile, obfuscate);
    convert_tcpdump_filters(chunk, outfile, &action);
    free((void*)chunk);

    return 0;
}
