#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <search.h>
#include <stdbool.h>
#include <sepol/sepol.h>
#include <sepol/policydb/policydb.h>

#define TABLE_SIZE 1024
#define KVP_NUM_OF_RULES (sizeof(rules) / sizeof(key_map))
#define log_set_verbose() do { logging_verbose = 1; log_info("Enabling verbose\n"); } while(0)
#define log_error(fmt, ...) log_msg(stderr, "Error: ", fmt, ##__VA_ARGS__)
#define log_warn(fmt, ...) log_msg(stderr, "Warning: ", fmt, ##__VA_ARGS__)
#define log_info(fmt, ...) if (logging_verbose ) { log_msg(stdout, "Info: ", fmt, ##__VA_ARGS__); }

typedef struct line_order_list line_order_list;
typedef struct hash_entry hash_entry;
typedef enum key_dir key_dir;
typedef enum data_type data_type;
typedef enum rule_map_switch rule_map_switch;
typedef enum map_match map_match;
typedef struct key_map key_map;
typedef struct kvp kvp;
typedef struct rule_map rule_map;
typedef struct policy_info policy_info;

enum map_match {
	map_no_matches,
	map_input_matched,
	map_matched
};

/**
 * Whether or not the "key" from a key vaue pair is considered an
 * input or an output.
 */
enum key_dir {
	dir_in, dir_out
};

/**
 * Used as options to rule_map_free()
 *
 * This is needed to get around the fact that GNU C's hash_map doesn't copy the key, so
 * we cannot free a key when overrding rule_map's in the table.
 */
enum rule_map_switch {
	rule_map_preserve_key, /** Used to preserve the key in the rule_map, ie don't free it*/
	rule_map_destroy_key   /** Used when you need a full free of the rule_map structure*/
};

/**
 * The expected "type" of data the value in the key
 * value pair should be.
 */
enum data_type {
	dt_bool, dt_string
};

/**
 * This list is used to store a double pointer to each
 * hash table / line rule combination. This way a replacement
 * in the hash table automatically updates the list. The list
 * is also used to keep "first encountered" ordering amongst
 * the encountered key value pairs in the rules file.
 */
struct line_order_list {
	hash_entry *e;
	line_order_list *next;
};

/**
 * The workhorse of the logic. This struct maps key value pairs to
 * an associated set of meta data maintained in rule_map_new()
 */
struct key_map {
	char *name;
	key_dir dir;
	data_type type;
	char *data;
};

/**
 * Key value pair struct, this represents the raw kvp values coming
 * from the rules files.
 */
struct kvp {
	char *key;
	char *value;
};

/**
 * Rules are made up of meta data and an associated set of kvp stored in a
 * key_map array.
 */
struct rule_map {
	char *key; /** key value before hashing */
	size_t length; /** length of the key map */
	int lineno; /** Line number rule was encounter on */
	key_map m[]; /** key value mapping */
};

struct hash_entry {
	rule_map *r; /** The rule map to store at that location */
};

/**
 * Data associated for a policy file
 */
struct policy_info {

	char *policy_file_name; /** policy file path name */
	FILE *policy_file;      /** file handle to the policy file */
	sepol_policydb_t *db;
	sepol_policy_file_t *pf;
	sepol_handle_t *handle;
	sepol_context_t *con;
};

/** Set to !0 to enable verbose logging */
static int logging_verbose = 0;

/** set to !0 to enable strict checking of duplicate entries */
static int is_strict = 0;

/** file handle to the output file */
static FILE *output_file = NULL;

/** file handle to the input file */
static FILE *input_file = NULL;

/** output file path name */
static char *out_file_name = NULL;

/** input file path name */
static char *in_file_name = NULL;

static policy_info pol = {
	.policy_file_name = NULL,
	.policy_file = NULL,
	.db = NULL,
	.pf = NULL,
	.handle = NULL,
	.con = NULL
};

/**
 * The heart of the mapping process, this must be updated if a new key value pair is added
 * to a rule.
 */
key_map rules[] = {
                /*Inputs*/
                { .name = "isSystemServer", .type = dt_bool,   .dir = dir_in,  .data = NULL },
                { .name = "user",           .type = dt_string, .dir = dir_in,  .data = NULL },
                { .name = "seinfo",         .type = dt_string, .dir = dir_in,  .data = NULL },
                { .name = "name",           .type = dt_string, .dir = dir_in,  .data = NULL },
                { .name = "path",           .type = dt_string, .dir = dir_in,  .data = NULL },
                { .name = "sebool",         .type = dt_string, .dir = dir_in,  .data = NULL },
                /*Outputs*/
                { .name = "domain",         .type = dt_string, .dir = dir_out, .data = NULL },
                { .name = "type",           .type = dt_string, .dir = dir_out, .data = NULL },
                { .name = "levelFromUid",   .type = dt_bool,   .dir = dir_out, .data = NULL },
                { .name = "levelFrom",      .type = dt_string,   .dir = dir_out, .data = NULL },
                { .name = "level",          .type = dt_string, .dir = dir_out, .data = NULL },
};

/**
 * Head pointer to a linked list of
 * rule map table entries, used for
 * preserving the order of entries
 * based on "first encounter"
 */
static line_order_list *list_head = NULL;

/**
 * Pointer to the tail of the list for
 * quick appends to the end of the list
 */
static line_order_list *list_tail = NULL;

/**
 * Send a logging message to a file
 * @param out
 * 	Output file to send message too
 * @param prefix
 * 	A special prefix to write to the file, such as "Error:"
 * @param fmt
 * 	The printf style formatter to use, such as "%d"
 */
static void __attribute__ ((format(printf, 3, 4)))
log_msg(FILE *out, const char *prefix, const char *fmt, ...) {

	fprintf(out, "%s", prefix);
	va_list args;
	va_start(args, fmt);
	vfprintf(out, fmt, args);
	va_end(args);
}

/**
 * Checks for a type in the policy.
 * @param db
 * 	The policy db to search
 * @param type
 * 	The type to search for
 * @return
 * 	1 if the type is found, 0 otherwise.
 * @warning
 * 	This function always returns 1 if libsepol is not linked
 * 	statically to this executable and LINK_SEPOL_STATIC is not
 * 	defined.
 */
int check_type(sepol_policydb_t *db, char *type) {

	int rc = 1;
#if defined(LINK_SEPOL_STATIC)
	policydb_t *d = (policydb_t *)db;
	hashtab_datum_t dat;
	dat = hashtab_search(d->p_types.table, type);
	rc = (dat == NULL) ? 0 : 1;
#endif
	return rc;
}

/**
 * Validates a key_map against a set of enforcement rules, this
 * function exits the application on a type that cannot be properly
 * checked
 *
 * @param m
 * 	The key map to check
 * @param lineno
 * 	The line number in the source file for the corresponding key map
 * @return
 * 	1 if valid, 0 if invalid
 */
static int key_map_validate(key_map *m, int lineno) {

	int rc = 1;
	int ret = 1;
	int resp;
	char *key = m->name;
	char *value = m->data;
	data_type type = m->type;
	sepol_bool_key_t *se_key;

	log_info("Validating %s=%s\n", key, value);

	 /* Booleans can always be checked for sanity */
	if (type == dt_bool && (!strcmp("true", value) || !strcmp("false", value))) {
		goto out;
	}
	else if (type == dt_bool) {
		log_error("Expected boolean value got: %s=%s on line: %d in file: %s\n",
				key, value, lineno, out_file_name);
		rc = 0;
		goto out;
	}

	if (!strcasecmp(key, "levelFrom") &&
	    (strcasecmp(value, "none") && strcasecmp(value, "all") &&
	     strcasecmp(value, "app") && strcasecmp(value, "user"))) {
		log_error("Unknown levelFrom=%s on line: %d in file: %s\n",
			  value, lineno, out_file_name);
		rc = 0;
		goto out;
	}

	/*
	 * If there is no policy file present,
	 * then it is not going to enforce the types against the policy so just return.
	 * User and name cannot really be checked.
	 */
	if (!pol.policy_file) {
		goto out;
	}
	else if (!strcasecmp(key, "sebool")) {

		ret = sepol_bool_key_create(pol.handle, value, &se_key);
		if (ret < 0) {
			log_error("Could not create selinux boolean key, error: %s\n",
					strerror(errno));
			rc = 0;
			goto out;
		}

		ret = sepol_bool_exists(pol.handle, pol.db, se_key, &resp);
		if (ret < 0) {
			log_error("Could not check selinux boolean, error: %s\n",
					strerror(errno));
			rc = 0;
			sepol_bool_key_free(se_key);
			goto out;
		}

		if(!resp) {
			log_error("Could not find selinux boolean \"%s\" on line: %d in file: %s\n",
					value, lineno, out_file_name);
			rc = 0;
			sepol_bool_key_free(se_key);
			goto out;
		}
		sepol_bool_key_free(se_key);
	}
	else if (!strcasecmp(key, "type") || !strcasecmp(key, "domain")) {

		if(!check_type(pol.db, value)) {
			log_error("Could not find selinux type \"%s\" on line: %d in file: %s\n", value,
					lineno, out_file_name);
			rc = 0;
		}
		goto out;
	}
	else if (!strcasecmp(key, "level")) {

		ret = sepol_mls_check(pol.handle, pol.db, value);
		if (ret < 0) {
			log_error("Could not find selinux level \"%s\", on line: %d in file: %s\n", value,
					lineno, out_file_name);
			rc = 0;
			goto out;
		}
	}

out:
	log_info("Key map validate returning: %d\n", rc);
	return rc;
}

/**
 * Prints a rule map back to a file
 * @param fp
 * 	The file handle to print too
 * @param r
 * 	The rule map to print
 */
static void rule_map_print(FILE *fp, rule_map *r) {

	size_t i;
	key_map *m;

	for (i = 0; i < r->length; i++) {
		m = &(r->m[i]);
		if (i < r->length - 1)
			fprintf(fp, "%s=%s ", m->name, m->data);
		else
			fprintf(fp, "%s=%s", m->name, m->data);
	}
}

/**
 * Compare two rule maps for equality
 * @param rmA
 * 	a rule map to check
 * @param rmB
 * 	a rule map to check
 * @return
 *  a map_match enum indicating the result
 */
static map_match rule_map_cmp(rule_map *rmA, rule_map *rmB) {

	size_t i;
	size_t j;
	int inputs_found = 0;
	int num_of_matched_inputs = 0;
	int input_mode = 0;
	size_t matches = 0;
	key_map *mA;
	key_map *mB;

	if (rmA->length != rmB->length)
		return map_no_matches;

	for (i = 0; i < rmA->length; i++) {
		mA = &(rmA->m[i]);

		for (j = 0; j < rmB->length; j++) {
			mB = &(rmB->m[j]);
			input_mode = 0;

			if (mA->type != mB->type)
				continue;

			if (strcmp(mA->name, mB->name))
				continue;

			if (strcmp(mA->data, mB->data))
				continue;

			if (mB->dir != mA->dir)
				continue;
			else if (mB->dir == dir_in) {
				input_mode = 1;
				inputs_found++;
			}

			if (input_mode) {
				log_info("Matched input lines: name=%s data=%s\n", mA->name, mA->data);
				num_of_matched_inputs++;
			}

			/* Match found, move on */
			log_info("Matched lines: name=%s data=%s", mA->name, mA->data);
			matches++;
			break;
		}
	}

	/* If they all matched*/
	if (matches == rmA->length) {
		log_info("Rule map cmp MATCH\n");
		return map_matched;
	}

	/* They didn't all match but the input's did */
	else if (num_of_matched_inputs == inputs_found) {
		log_info("Rule map cmp INPUT MATCH\n");
		return map_input_matched;
	}

	/* They didn't all match, and the inputs didn't match, ie it didn't
	 * match */
	else {
		log_info("Rule map cmp NO MATCH\n");
		return map_no_matches;
	}
}

/**
 * Frees a rule map
 * @param rm
 * 	rule map to be freed.
 */
static void rule_map_free(rule_map *rm,
		rule_map_switch s __attribute__((unused)) /* only glibc builds, ignored otherwise */) {

	size_t i;
	size_t len = rm->length;
	for (i = 0; i < len; i++) {
		key_map *m = &(rm->m[i]);
		free(m->data);
	}

/* hdestroy() frees comparsion keys for non glibc */
#ifdef __GLIBC__
	if(s == rule_map_destroy_key && rm->key)
		free(rm->key);
#endif

	free(rm);
}

static void free_kvp(kvp *k) {
	free(k->key);
	free(k->value);
}

/**
 * Checks a rule_map for any variation of KVP's that shouldn't be allowed.
 * Note that this function logs all errors.
 *
 * Current Checks:
 * 1. That a specified name entry should have a specified seinfo entry as well.
 * @param rm
 *  The rule map to check for validity.
 * @return
 *  true if the rule is valid, false otherwise.
 */
static bool rule_map_validate(const rule_map *rm) {

	size_t i;
	bool found_name = false;
	bool found_seinfo = false;
	char *name = NULL;
	const key_map *tmp;

	for(i=0; i < rm->length; i++) {
		tmp = &(rm->m[i]);

		if(!strcmp(tmp->name, "name") && tmp->data) {
			name = tmp->data;
			found_name = true;
		}
		if(!strcmp(tmp->name, "seinfo") && tmp->data && strcmp(tmp->data, "default")) {
			found_seinfo = true;
		}
	}

	if(found_name && !found_seinfo) {
		log_error("No specific seinfo value specified with name=\"%s\", on line: %d:  insecure configuration!\n",
				name, rm->lineno);
		return false;
	}

	return true;
}

/**
 * Given a set of key value pairs, this will construct a new rule map.
 * On error this function calls exit.
 * @param keys
 * 	Keys from a rule line to map
 * @param num_of_keys
 * 	The length of the keys array
 * @param lineno
 * 	The line number the keys were extracted from
 * @return
 * 	A rule map pointer.
 */
static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno) {

	size_t i = 0, j = 0;
	bool valid_rule;
	rule_map *new_map = NULL;
	kvp *k = NULL;
	key_map *r = NULL, *x = NULL;

	new_map = calloc(1, (num_of_keys * sizeof(key_map)) + sizeof(rule_map));
	if (!new_map)
		goto oom;

	new_map->length = num_of_keys;
	new_map->lineno = lineno;

	/* For all the keys in a rule line*/
	for (i = 0; i < num_of_keys; i++) {
		k = &(keys[i]);
		r = &(new_map->m[i]);

		for (j = 0; j < KVP_NUM_OF_RULES; j++) {
			x = &(rules[j]);

			/* Only assign key name to map name */
			if (strcasecmp(k->key, x->name)) {
				if (i == KVP_NUM_OF_RULES) {
					log_error("No match for key: %s\n", k->key);
					goto err;
				}
				continue;
			}

			memcpy(r, x, sizeof(key_map));

			/* Assign rule map value to one from file */
			r->data = strdup(k->value);
			if (!r->data)
				goto oom;

			/* Enforce type check*/
			log_info("Validating keys!\n");
			if (!key_map_validate(r, lineno)) {
				log_error("Could not validate\n");
				goto err;
			}

			/* Only build key off of inputs*/
			if (r->dir == dir_in) {
				char *tmp;
				int key_len = strlen(k->key);
				int val_len = strlen(k->value);
				int l = (new_map->key) ? strlen(new_map->key) : 0;
				l = l + key_len + val_len;
				l += 1;

				tmp = realloc(new_map->key, l);
				if (!tmp)
					goto oom;

				if (!new_map->key)
					memset(tmp, 0, l);

				new_map->key = tmp;

				strncat(new_map->key, k->key, key_len);
				strncat(new_map->key, k->value, val_len);
			}
			break;
		}
		free_kvp(k);
	}

	if (new_map->key == NULL) {
		log_error("Strange, no keys found, input file corrupt perhaps?\n");
		goto err;
	}

	valid_rule = rule_map_validate(new_map);
	if(!valid_rule) {
		/* Error message logged from rule_map_validate() */
		goto err;
	}

	return new_map;

oom:
	log_error("Out of memory!\n");
err:
	if(new_map) {
		rule_map_free(new_map, rule_map_destroy_key);
		for (; i < num_of_keys; i++) {
			k = &(keys[i]);
			free_kvp(k);
		}
	}
	exit(EXIT_FAILURE);
}

/**
 * Print the usage of the program
 */
static void usage() {
	printf(
	        "checkseapp [options] <input file>\n"
		        "Processes an seapp_contexts file specified by argument <input file> (default stdin) "
		        "and allows later declarations to override previous ones on a match.\n"
		        "Options:\n"
		        "-h - print this help message\n"
			"-s - enable strict checking of duplicates. This causes the program to exit on a duplicate entry with a non-zero exit status\n"
		        "-v - enable verbose debugging informations\n"
		        "-p policy file - specify policy file for strict checking of output selectors against the policy\n"
		        "-o output file - specify output file, default is stdout\n");
}

static void init() {

	/* If not set on stdin already */
	if(!input_file) {
		log_info("Opening input file: %s\n", in_file_name);
		input_file = fopen(in_file_name, "r");
		if (!input_file) {
			log_error("Could not open file: %s error: %s\n", in_file_name, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	/* If not set on std out already */
	if(!output_file) {
		output_file = fopen(out_file_name, "w+");
		if (!output_file) {
			log_error("Could not open file: %s error: %s\n", out_file_name, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	if (pol.policy_file_name) {

		log_info("Opening policy file: %s\n", pol.policy_file_name);
		pol.policy_file = fopen(pol.policy_file_name, "rb");
		if (!pol.policy_file) {
			log_error("Could not open file: %s error: %s\n",
					pol.policy_file_name, strerror(errno));
			exit(EXIT_FAILURE);
		}

		pol.handle = sepol_handle_create();
		if (!pol.handle) {
			log_error("Could not create sepolicy handle: %s\n",
					strerror(errno));
			exit(EXIT_FAILURE);
		}

		if (sepol_policy_file_create(&pol.pf) < 0) {
			log_error("Could not create sepolicy file: %s!\n",
					strerror(errno));
			exit(EXIT_FAILURE);
		}

		sepol_policy_file_set_fp(pol.pf, pol.policy_file);
		sepol_policy_file_set_handle(pol.pf, pol.handle);

		if (sepol_policydb_create(&pol.db) < 0) {
			log_error("Could not create sepolicy db: %s!\n",
					strerror(errno));
			exit(EXIT_FAILURE);
		}

		if (sepol_policydb_read(pol.db, pol.pf) < 0) {
			log_error("Could not lod policy file to db: %s!\n",
					strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	log_info("Policy file set to: %s\n", (pol.policy_file_name == NULL) ? "None" : pol.policy_file_name);
	log_info("Input file set to: %s\n", (in_file_name == NULL) ? "stdin" : in_file_name);
	log_info("Output file set to: %s\n", (out_file_name == NULL) ? "stdout" : out_file_name);

#if !defined(LINK_SEPOL_STATIC)
	log_warn("LINK_SEPOL_STATIC is not defined\n""Not checking types!");
#endif

}

/**
 * Handle parsing and setting the global flags for the command line
 * options. This function calls exit on failure.
 * @param argc
 * 	argument count
 * @param argv
 * 	argument list
 */
static void handle_options(int argc, char *argv[]) {

	int c;
	int num_of_args;

	while ((c = getopt(argc, argv, "ho:p:sv")) != -1) {
		switch (c) {
		case 'h':
			usage();
			exit(EXIT_SUCCESS);
		case 'o':
			out_file_name = optarg;
			break;
		case 'p':
			pol.policy_file_name = optarg;
			break;
		case 's':
			is_strict = 1;
			break;
		case 'v':
			log_set_verbose();
			break;
		case '?':
			if (optopt == 'o' || optopt == 'p')
				log_error("Option -%c requires an argument.\n", optopt);
			else if (isprint (optopt))
				log_error("Unknown option `-%c'.\n", optopt);
			else {
				log_error(
						"Unknown option character `\\x%x'.\n",
						optopt);
			}
		default:
			exit(EXIT_FAILURE);
		}
	}

	num_of_args = argc - optind;

	if (num_of_args > 1) {
		log_error("Too many arguments, expected 0 or 1, argument, got %d\n", num_of_args);
		usage();
		exit(EXIT_FAILURE);
	} else if (num_of_args == 1) {
		in_file_name = argv[argc - 1];
	} else {
		input_file = stdin;
		in_file_name = "stdin";
	}

	if (!out_file_name) {
		output_file = stdout;
		out_file_name = "stdout";
	}
}

/**
 * Adds a rule_map double pointer, ie the hash table pointer to the list.
 * By using a double pointer, the hash table can have a line be overridden
 * and the value is updated in the list. This function calls exit on failure.
 * @param rm
 * 	the rule_map to add.
 */
static void list_add(hash_entry *e) {

	line_order_list *node = malloc(sizeof(line_order_list));
	if (node == NULL)
		goto oom;

	node->next = NULL;
	node->e = e;

	if (list_head == NULL)
		list_head = list_tail = node;
	else {
		list_tail->next = node;
		list_tail = list_tail->next;
	}
	return;

oom:
	log_error("Out of memory!\n");
	exit(EXIT_FAILURE);
}

/**
 * Free's the rule map list, which ultimatley contains
 * all the malloc'd rule_maps.
 */
static void list_free() {
	line_order_list *cursor, *tmp;
	hash_entry *e;

	cursor = list_head;
	while (cursor) {
		e = cursor->e;
		rule_map_free(e->r, rule_map_destroy_key);
		tmp = cursor;
		cursor = cursor->next;
		free(e);
		free(tmp);
	}
}

/**
 * Adds a rule to the hash table and to the ordered list if needed.
 * @param rm
 * 	The rule map to add.
 */
static void rule_add(rule_map *rm) {

	map_match cmp;
	ENTRY e;
	ENTRY *f;
	hash_entry *entry;
	hash_entry *tmp;
	char *preserved_key;

	e.key = rm->key;

	log_info("Searching for key: %s\n", e.key);
	/* Check to see if it has already been added*/
	f = hsearch(e, FIND);

	/*
	 * Since your only hashing on a partial key, the inputs we need to handle
	 * when you want to override the outputs for a given input set, as well as
	 * checking for duplicate entries.
	 */
	if(f) {
		log_info("Existing entry found!\n");
		tmp = (hash_entry *)f->data;
		cmp = rule_map_cmp(rm, tmp->r);
		log_info("Comparing on rule map ret: %d\n", cmp);
		/* Override be freeing the old rule map and updating
		   the pointer */
		if(cmp != map_matched) {

			/*
			 * DO NOT free key pointers given to the hash map, instead
			 * free the new key. The ordering here is critical!
			 */
			preserved_key = tmp->r->key;
			rule_map_free(tmp->r, rule_map_preserve_key);
/*  hdestroy() frees comparsion keys for non glibc */
#ifdef __GLIBC__
			free(rm->key);
#endif
			rm->key = preserved_key;
			tmp->r = rm;
		}
		/* Duplicate */
		else {
			/* if is_strict is set, then don't allow duplicates */
			if(is_strict) {
				log_error("Duplicate line detected in file: %s\n"
					"Lines %d and %d match!\n",
					out_file_name, tmp->r->lineno, rm->lineno);
				rule_map_free(rm, rule_map_destroy_key);
				goto err;
			}

			/* Allow duplicates, just drop the entry*/
			log_info("Duplicate line detected in file: %s\n"
					"Lines %d and %d match!\n",
					out_file_name, tmp->r->lineno, rm->lineno);
			rule_map_free(rm, rule_map_destroy_key);
		}
	}
	/* It wasn't found, just add the rule map to the table */
	else {

		entry = malloc(sizeof(hash_entry));
		if (!entry)
			goto oom;

		entry->r = rm;
		e.data = entry;

		f = hsearch(e, ENTER);
		if(f == NULL) {
			goto oom;
		}

		/* new entries must be added to the ordered list */
		entry->r = rm;
		list_add(entry);
	}

	return;
oom:
	if (e.key)
		free(e.key);
	if (entry)
		free(entry);
	if (rm)
		free(rm);
	log_error("Out of memory in function: %s\n", __FUNCTION__);
err:
	exit(EXIT_FAILURE);
}

/**
 * Parses the seapp_contexts file and adds them to the
 * hash table and ordered list entries when it encounters them.
 * Calls exit on failure.
 */
static void parse() {

	char line_buf[BUFSIZ];
	char *token;
	unsigned lineno = 0;
	char *p, *name = NULL, *value = NULL, *saveptr;
	size_t len;
	kvp keys[KVP_NUM_OF_RULES];
	size_t token_cnt = 0;

	while (fgets(line_buf, sizeof line_buf - 1, input_file)) {

		lineno++;
		log_info("Got line %d\n", lineno);
		len = strlen(line_buf);
		if (line_buf[len - 1] == '\n')
			line_buf[len - 1] = '\0';
		p = line_buf;
		while (isspace(*p))
			p++;
		if (*p == '#' || *p == '\0')
			continue;

		token = strtok_r(p, " \t", &saveptr);
		if (!token)
			goto err;

		token_cnt = 0;
		memset(keys, 0, sizeof(kvp) * KVP_NUM_OF_RULES);
		while (1) {

			name = token;
			value = strchr(name, '=');
			if (!value)
				goto err;
			*value++ = 0;

			keys[token_cnt].key = strdup(name);
			if (!keys[token_cnt].key)
				goto oom;

			keys[token_cnt].value = strdup(value);
			if (!keys[token_cnt].value)
				goto oom;

			token_cnt++;

			token = strtok_r(NULL, " \t", &saveptr);
			if (!token)
				break;

		} /*End token parsing */

		rule_map *r = rule_map_new(keys, token_cnt, lineno);
		rule_add(r);

	} /* End file parsing */
	return;

err:
	log_error("reading %s, line %u, name %s, value %s\n",
			in_file_name, lineno, name, value);
	exit(EXIT_FAILURE);
oom:
	log_error("In function %s:  Out of memory\n", __FUNCTION__);
	exit(EXIT_FAILURE);
}

/**
 * Should be called after parsing to cause the printing of the rule_maps
 * stored in the ordered list, head first, which preserves the "first encountered"
 * ordering.
 */
static void output() {

	rule_map *r;
	line_order_list *cursor;
	cursor = list_head;

	while (cursor) {
		r = cursor->e->r;
		rule_map_print(output_file, r);
		cursor = cursor->next;
		fprintf(output_file, "\n");
	}
}

/**
 * This function is registered to the at exit handler and should clean up
 * the programs dynamic resources, such as memory and fd's.
 */
static void cleanup() {

	/* Only close this when it was opened by me and not the crt */
	if (out_file_name && output_file) {
		log_info("Closing file: %s\n", out_file_name);
		fclose(output_file);
	}

	/* Only close this when it was opened by me  and not the crt */
	if (in_file_name && input_file) {
		log_info("Closing file: %s\n", in_file_name);
		fclose(input_file);
	}

	if (pol.policy_file) {

		log_info("Closing file: %s\n", pol.policy_file_name);
		fclose(pol.policy_file);

		if (pol.db)
			sepol_policydb_free(pol.db);

		if (pol.pf)
			sepol_policy_file_free(pol.pf);

		if (pol.handle)
			sepol_handle_destroy(pol.handle);
	}

	log_info("Freeing list\n");
	list_free();
	hdestroy();
}

int main(int argc, char *argv[]) {
	if (!hcreate(TABLE_SIZE)) {
		log_error("Could not create hash table: %s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	atexit(cleanup);
	handle_options(argc, argv);
	init();
	log_info("Starting to parse\n");
	parse();
	log_info("Parsing completed, generating output\n");
	output();
	log_info("Success, generated output\n");
	exit(EXIT_SUCCESS);
}