/*
** Sella NMS
** $Id: snmptrap.cxx,v 1.32 2006/01/24 07:08:10 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: snmptrap.cxx,v 1.32 2006/01/24 07:08:10 sella Exp $";

#include "main.h"
#include "daemon.h"
#include "snmptrap.h"
#include "common/ip_util.h"
#include "common/snmp_util.h"
#include "state/state.h"
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/if.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUF_SIZE 512
#define WAIT_AND_KILL

#if defined HAVE_NET_SNMP && ! defined RECEIVED_MESSAGE
	#define RECEIVED_MESSAGE NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
#endif

extern ConfigNode* config;
extern bool interrupt;

SNMPTrap::SNMPTrap():exitAfter(0) {
	pthread_mutex_init(&trap_mutex, NULL);
	pthread_mutex_init(&proxy_mutex, NULL);

	flag_init(trap_flag);
	flag_init(proxy_flag);
}

SNMPTrap::~SNMPTrap() {
	pthread_mutex_destroy(&trap_mutex);
	pthread_mutex_destroy(&proxy_mutex);
}

RESULT SNMPTrap::execute() {
	RESULT result = SUCCESS;

	int error = 0;

	pthread_t thread_id = 0;

	slist<pthread_t> t_list;
	slist<pthread_t>::iterator it;

	TRACE(TRACE_THREAD, "SNMPTrap::execute() starting");

	/* Create the proxy thread (if needed) */
	if ((error = pthread_create(&thread_id, NULL, (void* (*)(void*))SNMPTrap::proxy_thread, this)) == 0 ) {
		t_list.push_front(thread_id);
	}

	/* Create the policy thread */
	if (error == 0) {
		if ((error = pthread_create(&thread_id, NULL, (void* (*)(void*))SNMPTrap::policy_thread, this)) == 0 ) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the receiver thread */
	if (error == 0) {
		if ((error = pthread_create(&thread_id, NULL, (void* (*)(void*))SNMPTrap::receive_thread, this)) == 0 ) {
			t_list.push_front(thread_id);
		}
	}

	/* Wait on all threads. */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		if ((error = pthread_join(*it, NULL)) != 0) {
			TRACE(TRACE_THREAD, "pthread_join returned %d", error);
		} else {
			TRACE(TRACE_THREAD, "joined thread %d.", *it);
		}
	}
	TRACE(TRACE_THREAD, "SNMPTrap::execute() exiting");

	return result;
}

RESULT SNMPTrap::readConfig() {
	RESULT result = SUCCESS;
	string str;
	vector<string> v;
	vector<string>::iterator v_it;
	struct in_addr ip;

	ConfigNode node;

	if (!config->assignNode(getConfigPath(), node)) {
		LMSG("No '%s' section found within the config file.", getConfigPath());

		return FAILURE;
	}

	/* FIXME: keyword 'convert' needs to be removed from the list 'proxy'. Its not a hostname. */
	node.assignValues("proxy", proxy);

	if (node.assignValue("proxy convert", str)) {
		if (!str.compare("v1")) {
			LMSG("WARNING: conversion from SNMPv2 to SNMPv1 is not supported yet.");
			convert = 0; /* Set to 1 once its supported */
		} else if (!str.compare("v2")) {
			convert = 2;
		} else {
			convert = 0;
		}
	} else {
		convert = 0; /* Do no conversion */
	}

	if (node.assignValue("source", str)) {
		if (inet_pton(AF_INET, str.c_str(), &(this->source)) == 0) {
			this->source = htonl(INADDR_ANY);
		}
	} else if (node.assignValue("interface", str)) {
		source_ip(str.c_str(), &ip);
		this->source = ip.s_addr;
	} else {
		this->source = htonl(INADDR_ANY);
	}

	node.assignValue("port", port, (unsigned long) SNMP_TRAP_PORT);

	node.assignValue("queue-depth", queueDepth, MONITOR_SNMPTRAP_QUEUEDEPTH_DEF, MONITOR_SNMPTRAP_QUEUEDEPTH_MIN, MONITOR_SNMPTRAP_QUEUEDEPTH_MAX);
	node.assignValue("exit-after", exitAfter, MONITOR_SNMPTRAP_EXITAFTER_DEF);

	node.assignValue("rate-interval", rateInterval, MONITOR_SNMPTRAP_RATEINTERVAL_DEF, MONITOR_SNMPTRAP_RATEINTERVAL_MIN, MONITOR_SNMPTRAP_RATEINTERVAL_MAX);

	node.assignValue("rate-clean", rateClean, MONITOR_SNMPTRAP_RATECLEAN_DEF, MONITOR_SNMPTRAP_RATECLEAN_MIN, MONITOR_SNMPTRAP_RATECLEAN_MAX);

	/* Clear any existing policies. */
	inputPolicy.clear();
	outputPolicy.clear();

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		/* This is actually just added to the begining of the output policies. */
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");

			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (node.assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile output policies.");

			return FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("NOTICE: An output policy is required for this module.");

		return FAILURE;
	}

	/* Configure Storage */
	if (storage.setConfig(*config) != SUCCESS) {
		result = FAILURE;
	}

	printConfig();

	return result;
}

void SNMPTrap::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	struct in_addr in;
	char inet_buffer[INET_ADDRSTRLEN];

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "port: %lu", this->port);
	TRACE(TRACE_CONFIG, "queue-depth: %lu", this->queueDepth);
	in.s_addr = this->source;
	TRACE(TRACE_CONFIG, "source: %s", inet_ntop(AF_INET, &in, inet_buffer, INET_ADDRSTRLEN));
	TRACE(TRACE_CONFIG, "exit-after: %lu", this->exitAfter);
	TRACE(TRACE_CONFIG, "rate-interval: %lu", this->rateInterval);
	TRACE(TRACE_CONFIG, "rate-clean: %lu", this->rateClean);

	if (config->assignValues("monitor snmptrap policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("monitor snmptrap policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}

	return;
}

RESULT SNMPTrap::receive_thread(void* args) {
	unsigned long rx_count = 0;

	SNMPTrap *instance = (SNMPTrap*) args;

	received_queue_t *trap_queue = &instance->trap_queue;
	pthread_mutex_t *trap_mutex = &instance->trap_mutex;
	received_queue_t *proxy_queue = &instance->proxy_queue;
	pthread_mutex_t *proxy_mutex = &instance->proxy_mutex;

	/* Initialize the rate object */
	if (TRACE_ISSET(TRACE_DUMP)) {
		instance->rate.setDebug(true);
	}
	instance->rate.setInterval(instance->rateInterval);
	instance->rate.setClean(instance->rateClean);

	/* SNMP Variables */
	struct snmp_session session, *ss;
	int count, numfds, block;
	fd_set fdset;
	struct timeval timeout;
	int liberr, syserr;
	char *errmsg = NULL;
	void *opaque;
	snmp_pdu *pdu = NULL, *tmp_pdu = NULL;
	struct in_addr in;
	char peername[32];
	const char *buf;
	char inet_buffer[INET_ADDRSTRLEN];

	time_t next_policy_warn = 0;
	time_t next_proxy_warn = 0;

	TRACE(TRACE_THREAD, "SNMPTrap::receive_thread() starting main loop");

	snmp_sess_init(&session);

/* Set peername (source) and port. */
	memset(peername, 0, 32);
	in.s_addr = instance->source;

	/* Build a proper peername. */
#ifdef HAVE_NET_SNMP
	if (instance->source != 0 && (buf = inet_ntop(AF_INET, &in, inet_buffer, INET_ADDRSTRLEN)) != NULL) {
		snprintf(peername, 32, "%s:%lu", buf, instance->port);
		buf = NULL;
	} else {
		snprintf(peername, 32, "0.0.0.0:%lu", instance->port);
	}
#else
	if (instance->source != 0 && (buf = inet_ntop(AF_INET, &in, inet_buffer, INET_ADDRSTRLEN)) != NULL) {
		snprintf(peername, 32, "%s", buf);
	} else {
		strncpy(peername, "0.0.0.0", 32);
	}
#endif
	TRACE(TRACE_INTERNAL, "set peername to '%s' and port to '%d'.", peername, instance->port);

	session.peername = peername;
	session.version = SNMP_DEFAULT_VERSION;
	session.community_len = SNMP_DEFAULT_COMMUNITY_LEN;
	session.retries = SNMP_DEFAULT_RETRIES;
	session.timeout = SNMP_DEFAULT_TIMEOUT;
	session.callback = snmptrap_callback;
	session.callback_magic = &pdu;
	session.authenticator = NULL;
	session.isAuthoritative = SNMP_SESS_UNKNOWNAUTH;
	session.local_port = instance->port;
	/* This would be used if TCP was being used. Should add an option for this.
	session->flags |= SNMP_FLAGS_STREAM_SOCKET;
	*/

	TRACE(TRACE_THREAD, "entering receive_thread");

	grantPriv();
	opaque = snmp_sess_open(&session);

	if (opaque == NULL) {
		LMSG("failed to bind to port %d - already in use or insufficient permissions", instance->port);
		interrupt = true;

		int le;
		int se;
		char *b;
		snmp_error(&session, &le, &se, &b);

		TRACE(TRACE_INTERNAL, "SNMP ERROR: %s", b);
	} else {
		/* Grab a pointer to the session being used internally for this thread. */
		ss = snmp_sess_session(opaque);

		if (ss == NULL) {
			LMSG("failed to bind to get session");
			interrupt = true;
		}
	}
	dropPriv();

	while (!interrupt) {
		if (instance->exitAfter > 0 && rx_count >= instance->exitAfter) {
			LMSG("shutting down due to %lu traps being received (exit-after: %lu).", rx_count, instance->exitAfter);

			interrupt = true;
			continue;
		}

		numfds = 0;
		FD_ZERO(&fdset);
		block = 0;
		timeout.tv_sec = 1;
		timeout.tv_usec = 0;

		snmp_sess_select_info(opaque, &numfds, &fdset, &timeout, &block);

		count = select(numfds, &fdset, 0, 0, &timeout);
		switch (count) {
			case -1:
				/* This may be the result of a kill -HUP. Exit and let the daemon restart the module if needed. */
				LMSG("WARNING: SNMPTrap::receive_thread select() returned in error. Shutting down.");

				//DEBUG
				snmp_sess_error(opaque, &liberr, &syserr, &errmsg);
				TRACE(TRACE_DEBUG, "snmp_sess_error() %lu/%lu (%s)", liberr, syserr, errmsg);
				SAFE_FREE(errmsg);

				interrupt = true;

				continue;
			case 0:
				snmp_sess_timeout(opaque);
				continue;
			default:
				/* This triggers the callback. */
				snmp_sess_read(opaque, &fdset);

				//DEBUG: Seeing if we get an odd error when the command is crazy.
				if (pdu && pdu->command != SNMP_MSG_TRAP && pdu->command != SNMP_MSG_TRAP2) {
					LMSG("Current PDU command is %lu (SNMP_MSG_INFORM: %lu, SNMP_MSG_TRAP2: %lu", pdu->command, SNMP_MSG_INFORM, SNMP_MSG_TRAP2);
				}
				snmp_sess_error(opaque, &liberr, &syserr, &errmsg);
				if (liberr != 0 ||  syserr != 0) {
					TRACE(TRACE_DEBUG, "snmp_sess_error() %lu/%lu (%s)", liberr, syserr, errmsg);
				}
				SAFE_FREE(errmsg);
		}

		if (pdu) {
			if (pdu->community == NULL || pdu->variables == NULL || (pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC && pdu->enterprise == NULL)) {
				/* This pdu is corrupt. Discard it. */
				LMSG("WARNING: Discarding invalid SNMPTrap PDU. (pdu->community = %08x, pdu->variables = %08x)");
				SAFE_SNMP_FREE_PDU(pdu);
			} else {
				rx_count++;

				/* Send clone of trap (non-converted) */
				if (!instance->proxy.empty()) {
					if (proxy_queue->size() >= instance->queueDepth) {
						if (time(NULL) >= next_proxy_warn) {
							next_proxy_warn = time(NULL) + MONITOR_SNMPTRAP_NEXTWARN_DEF;
							LMSG("WARNING: SNMPTrap proxy queue depth exceeded %lu. Dropping snmptrap from %s. (Suppressing warnings for %lu sec)", instance->queueDepth, peername, MONITOR_SNMPTRAP_NEXTWARN_DEF);
						}
					} else {
						tmp_pdu = snmp_clone_pdu(pdu);

						pthread_mutex_lock(proxy_mutex);
							proxy_queue->push(tmp_pdu);
							tmp_pdu = NULL; /* receive_thread no longer owns the PDU. */
						pthread_mutex_unlock(proxy_mutex);

						/* Set the proxy flag. */
						flag_set(instance->proxy_flag);
					}
				}

				/* Send original trap */
				if (trap_queue->size() >= instance->queueDepth) {
					if (time(NULL) >= next_policy_warn) {
						next_policy_warn = time(NULL) + MONITOR_SNMPTRAP_NEXTWARN_DEF;
						LMSG("WARNING: SNMPTrap queue depth exceeded %lu. Dropping snmptrap from %s. (Suppressing warnings for %lu sec)", instance->queueDepth, peername, MONITOR_SNMPTRAP_NEXTWARN_DEF);
					}

					/* Don't leak the pdu. */
					SAFE_SNMP_FREE_PDU(pdu);
				} else {
					pthread_mutex_lock(trap_mutex);
						trap_queue->push(pdu);
						pdu = NULL; /* receive_thread no longer owns the PDU. */
					pthread_mutex_unlock(trap_mutex);

					/* Set the trap flag. */
					flag_set(instance->trap_flag);
				}
			}
		}
	}
	snmp_sess_close(opaque);

	TRACE(TRACE_THREAD, "existing receive_thread");

	pthread_exit(0);
}

int SNMPTrap::snmptrap_callback(int op, struct snmp_session *ss, int reqid, struct snmp_pdu *pdu, void *magic) {
	struct snmp_pdu **req = (snmp_pdu**) magic;

	if (op != RECEIVED_MESSAGE) {
		LMSG("WARNING: snmptrap_callback trigged for timeout, or other non-RECEIVED_MESSAGE operation.");
		(*req) = NULL;
	} else {
		(*req) = snmp_clone_pdu(pdu);
	}
	
	return 1;
}

RESULT SNMPTrap::conv_pdu(snmp_pdu *pdu, unsigned target, bool proxy, bool skip_zero) {
	RESULT result = FAILURE;

	size_t len;
	static oid trap_oid[] = {1,3,6,1,6,3,1,1,5,0};
	static oid sysuptime[] = {1,3,6,1,2,1,1,3,0};
	static oid snmptrapoid[] = {1,3,6,1,6,3,1,1,4,1,0};
	static oid snmptrapaddress[] = {1,3,6,1,6,3,18,1,3,0};
	static oid snmptrapcommunity[] = {1,3,6,1,6,3,18,1,4,0};
	static oid snmptrapenterprise[] = {1,3,6,1,6,3,1,1,4,3,0};
	struct variable_list *var_list;
	struct sockaddr_in *agent_addr;

	if (target == 0) {
		return SUCCESS;
	}

	/* XXX: This function seems to only convert from the original into a v2c PDU. */

	switch (pdu->command) {
		/* Convert to SNMP_MSG_TRAP2 */
		case SNMP_MSG_TRAP:
			/* Allocate needed memory */
			var_list = (struct variable_list *) malloc(sizeof(struct variable_list));
			if (var_list) {
				bzero(var_list, sizeof(struct variable_list));
				var_list->next_variable = (struct variable_list *) malloc(sizeof(struct variable_list));
				if (var_list->next_variable) {
					bzero(var_list->next_variable, sizeof(struct variable_list));
					var_list->name = (oid *) malloc(sizeof(oid) * MAX_OID_LEN);
					var_list->val.integer = (long *) malloc(sizeof(long));
					var_list->next_variable->name = (oid *) malloc(sizeof(oid) * MAX_OID_LEN);
					var_list->next_variable->val.objid = (oid *) malloc(sizeof(oid) * MAX_OID_LEN);
					result = SUCCESS;
				}
			}

			if (result == SUCCESS) {
				/* Do RFC 2576 Sec 3.1.1 Conversion */
				var_list->type = ASN_TIMETICKS;
				var_list->name_length = sizeof(sysuptime) / sizeof(oid);
				memcpy(var_list->name, sysuptime, sizeof(sysuptime));
				var_list->val_len = sizeof(pdu->time);
				*(var_list->val.integer) = (long) pdu->time;

				/* Add SNMPTrapOID to get ready for 3.1.2 or 3.1.3 */
				var_list->next_variable->type = ASN_OBJECT_ID;
				var_list->next_variable->name_length = sizeof(snmptrapoid) / sizeof(oid);
				memcpy(var_list->next_variable->name, snmptrapoid, sizeof(snmptrapoid));
				if (pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
					/* Do RFC 2576 Sec 3.1.2 Enterprise Conversion (Optionally skipping the Zero). */
					assert(pdu->enterprise != NULL);
					len = pdu->enterprise_length;
					memcpy(var_list->next_variable->val.objid, pdu->enterprise, len * sizeof(oid));
					if (!skip_zero && var_list->next_variable->val.objid[len - 1] != 0) {
						var_list->next_variable->val.objid[len++] = 0;
					}
					var_list->next_variable->val.objid[len++] = pdu->specific_type;
					var_list->next_variable->val_len = len * sizeof(oid);
				} else {
					/* Do RFC 2576 Sec 3.1.3 Generic Conversion */
					memcpy(var_list->next_variable->val.objid, trap_oid, sizeof(trap_oid));
					var_list->next_variable->val.objid[9] = pdu->trap_type + 1;
					var_list->next_variable->val_len = 10 * sizeof(oid);
				}

				/* Do RFC 2576 Sec 3.1.4 proxy bindings */
				if (proxy) {
					agent_addr = (struct sockaddr_in *) &(pdu->agent_addr);
					snmp_pdu_add_variable(pdu, snmptrapaddress, sizeof(snmptrapaddress) / sizeof(oid), ASN_IPADDRESS, (u_char *) &(agent_addr->sin_addr), sizeof(in_addr));
					snmp_pdu_add_variable(pdu, snmptrapcommunity, sizeof(snmptrapcommunity) / sizeof(oid), ASN_OCTET_STR, pdu->community, pdu->community_len);
					snmp_pdu_add_variable(pdu, snmptrapenterprise, sizeof(snmptrapenterprise) / sizeof(oid), ASN_OBJECT_ID, (u_char *) pdu->enterprise, pdu->enterprise_length * sizeof(oid));
				}

				/* Fixup PDU's variable_list. We are now a SNMP_MSG_TRAP2 */
				pdu->version = SNMP_VERSION_2c;
				pdu->command = SNMP_MSG_TRAP2;
				var_list->next_variable->next_variable = pdu->variables;
				pdu->variables = var_list;
			} else {
				LMSG("WARNING: failed to allocate memory for variable_list.");
				snmp_free_varbind(var_list); /* This deallocates all parts of variable_list */
			}
			break;
 		case SNMP_MSG_TRAP2:
		case SNMP_MSG_INFORM:
			/*
			if (pdu->variables && pdu->variables->next_variable && pdu->variables->next_variable->val.objid) {
				pdu->trap_type = pdu->variables->next_variable->val.objid[9];
				pdu->specific_type = pdu->variables->next_variable->val.objid[10];
			}
			*/

			/* PDU is already a v2 PDU. */
			result = SUCCESS;
			break;

		default:
			LMSG("WARNING: SNMPTrap PDU contains unknown command %lu. PDU corrupt?", pdu->command);
			LMSG("pdu*: %08x", pdu);
			LMSG("pdu->version: %lu, pdu->variables: %08x, pdu->community: %08x", pdu->version, pdu->variables, pdu->community);
			LMSG("pdu->communit: %s", pdu->community);
	}
	
	return result;
}

RESULT SNMPTrap::policy_thread(void* args) {
	RESULT result = SUCCESS;
	ACTION action;

	SNMPTrap *instance = (SNMPTrap*) args;

	received_queue_t *trap_queue = &instance->trap_queue;
	pthread_mutex_t *trap_mutex = &instance->trap_mutex;

	variable_binding_t	policy_input;
	variable_binding_t	policy_output;

	string sys_uptime = ".1.3.6.1.2.1.1.3";
	string snmptrapoid = ".1.3.6.1.6.3.1.1.4.1";
	string snmptraps = ".1.3.6.1.6.3.1.1.5.";
	string enterprise = ".1.3.6.1.6.3.1.1.5";

	char buf[BUF_SIZE];
	string str;
	unsigned int len;
	snmp_pdu* pdu;
	string variable;
	struct sockaddr_in *source, *agent_addr;
	variable_list *var;
	parsed_oid_t parsed_oid;
	time_t t;
	char inet_buffer[INET_ADDRSTRLEN];

	IPv4State *ipv4_state;
	InterfaceState *interface_state;
	ElementState *element_state;

	TRACE(TRACE_THREAD, "SNMPTrap::policy_thread() starting main loop");

	/* Instruct the libary to leave the OID and Hints off snprint_value() output. */
#ifdef HAVE_NET_SNMP
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICK_PRINT, 1);
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_BARE_VALUE, 1);
#else
	ds_set_boolean(DS_LIBRARY_ID, DS_LIB_QUICK_PRINT, 1);
	ds_set_boolean(DS_LIBRARY_ID, DS_LIB_PRINT_BARE_VALUE, 1);
#endif

	// Until we are asked to stop, store our successes.
	while (!interrupt) {
		/* Wait for the trap flag to get set. */
		flag_wait(instance->trap_flag, &(interrupt));

		while (!trap_queue->empty() && !interrupt) {
			/* Clear policy structures. */
			policy_input.clear();
			policy_output.clear();

			/* Fetch Data from queue */
			pthread_mutex_lock(trap_mutex);
			pdu = trap_queue->front();
			trap_queue->pop();
			pthread_mutex_unlock(trap_mutex);

			/* Insert original trap version. (Do before conversion.) */
			switch (pdu->version) {
				case 0:
					policy_input["version"] = "v1";
					break;
				case 1:
					policy_input["version"] = "v2c";
					break;
				case 2:
					policy_input["version"] = "v2u";
					break;
				case 3:
					policy_input["version"] = "v3";
					break;
				default:
					policy_input["version"] = "unknown";
			}

			/* Conv PDU into a non-proxy V2 trap ignoring RFC 2576 Sec 3.1.2. */
			if (instance->conv_pdu(pdu, 2, false, true) == FAILURE) {
				LMSG("DEBUG: Attempting to free memory now.. its crashed here before...");
				SAFE_SNMP_FREE_PDU(pdu);
				continue; /* PDU was not processed properly. */
			}

			assert(pdu->variables != NULL);

			/* Process variables */
			for (var = pdu->variables; var != NULL; var = var->next_variable) {
				if (var->name_length >= MONITOR_SNMPTRAP_MAXNAMELENGTH_DEF || var->name == NULL) {
					LMSG("WARNING: SNMPTrap varbind is likely corrupt. var->name_length is %lu and var->name == 0x%08x. Ending processing of this trap.", var->name_length, var->name);
					var = NULL;
					break;
				}

				convOidToString(variable, var->name, var->name_length);

				switch (var->type) {
					case ASN_OBJECT_ID:
						len = ((int) var->val_len / sizeof(oid) > BUF_SIZE) ? BUF_SIZE : (int) (var->val_len / sizeof(oid));
						convOidToChar(buf, BUF_SIZE, (oid*) var->val.string, len);
						break;

					case ASN_IPADDRESS:
						convVariableToIP(buf, BUF_SIZE, var);
						break;

					case ASN_INTEGER:
						if (var->val.integer) {
							snprintf(buf, BUF_SIZE, "%ld", *(var->val.integer));
							if (resolveEnumeration(str, variable, buf) == SUCCESS) {
								strncpy(buf, str.c_str(), BUF_SIZE);
							}
						} else {
							buf[0] = '\0';
						}

						break;

					case ASN_GAUGE:
					case ASN_COUNTER:
					case ASN_TIMETICKS:
						if (var->val.integer) {
							snprintf(buf, BUF_SIZE, "%ld", *(var->val.integer));
						} else {
							buf[0] = '\0';
						}
						break;

					case ASN_NULL:
					case ASN_BOOLEAN:
					case ASN_COUNTER64:
					case ASN_OCTET_STR:
						len = (var->name_length > BUF_SIZE) ? BUF_SIZE : var->name_length;
						snprint_value(buf, BUF_SIZE, var->name, len, var);
						instance->cleanValue(buf);
						break;

					default:
						len = (var->name_length > BUF_SIZE) ? BUF_SIZE : var->name_length;
						if (len < 1) {
							LMSG("SNMPTrap.. len was less than 1!? type is: %lu", var->type);
						}
						if (var == NULL) {
							LMSG("SNMPTrap.. var was NULL!?");
						}
						if (var->name == NULL) {
							LMSG("SNMPTrap.. var->name was NULL!?");
						}
						if (len > 1) {
							snprint_value(buf, BUF_SIZE, var->name, len, var);
						} else {
							LMSG("DEBUG: Wasn't a valid string. Its type %lu", var->type);
						}
				}
				if (policy_input.find(buf) != policy_input.end()) {
					LMSG("DEBUG: SNMPTrap is overwriting '%s' with '%s'. You will have to deal with this after all! :(", variable.c_str(), buf);
				}


				/* Parse variable oid. */
				if (parseOid(parsed_oid, variable) == FAILURE) {
					LMSG("DEBUG: parseOid returned in failure. Skipping this oid. var->next_variable == %08x : %s %d", var->next_variable, __FILE__, __LINE__);
					continue;	
				}

#if 1
				/* Hack to fix Arbor traps. Might actually be correct though. Look into this! */
				TRACE(TRACE_DUMP, "variable: %s, parsed: %s", variable.c_str(), parsed_oid.main_oid.c_str());

				/* Insert unmodified varbind variable/value. */
				policy_input[variable] = buf;
#endif

				/* Insert main variable/value. */
				policy_input[parsed_oid.main_oid] = buf;

				if (TRACE_ISSET(TRACE_INTERNAL)) {
					TRACE(TRACE_INTERNAL, "policy_input[%s] = %s (Main)", parsed_oid.main_oid.c_str(), buf);
					if (!parsed_oid.indexes.empty()) {
						TRACE(TRACE_INTERNAL, "policy_input[%s] = %s (Sub-Index)", parsed_oid.main_oid.c_str(), buf);
					}
				}

				/* Insert index variables/values. */
				while (!parsed_oid.indexes.empty()) {
					pair<string, string> index_pair = parsed_oid.indexes.front();
					/* Insert as a sub-index of main oid. This allows for uniq values. */
					policy_input[parsed_oid.main_oid + "[" + index_pair.first + "]"] = index_pair.second;
					/* Insert directly for easy access, if it doesn't already have a value. */
					if (policy_input.find(index_pair.first) == policy_input.end()) {
						policy_input[index_pair.first] = index_pair.second;
					}
					parsed_oid.indexes.pop_front();

					TRACE(TRACE_INTERNAL, "- policy_input[%s] = %s", index_pair.first.c_str(), policy_input[index_pair.first].c_str());
				}
			}

			/* Insert protocol. */
			policy_input["protocol"] = "snmptrap";
			policy_input["origin"] = "network";

			/* Insert agent */
			agent_addr = (struct sockaddr_in*) &(pdu->agent_addr);
			policy_input["agent"] = inet_ntop(AF_INET, &(agent_addr->sin_addr), inet_buffer, INET_ADDRSTRLEN);

			/* Insert packet source. */
#ifdef HAVE_NET_SNMP
			//NOTE: This is a hack way of getting the trap sender.
			source = (struct sockaddr_in *) pdu->transport_data;
			if (source != NULL && pdu->transport_data_length == sizeof(struct sockaddr_in)) {
				policy_input["source"] = inet_ntop(AF_INET, &(source->sin_addr), inet_buffer, INET_ADDRSTRLEN);
			} else {
				policy_input["source"] = "0.0.0.0";
			}
#else
			source = (struct sockaddr_in*) &(pdu->address);
			policy_input["source"] = inet_ntop(AF_INET, &(source->sin_addr), inet_buffer, INET_ADDRSTRLEN);
#endif
			policy_input["source-except"] = policy_input["source"];

			/* Insert Community. */
			assert(pdu->community != NULL);
			memcpy(buf, pdu->community, pdu->community_len);
			buf[pdu->community_len] = 0;
			policy_input["community"] = buf;

			/* Insert SysUpTime. */
			if (policy_input.find(sys_uptime) != policy_input.end()) {
				policy_input["sys-uptime"] = policy_input[sys_uptime];
			}

			/* Insert Enterprise, Trap Type and Specific Type. */
			if (policy_input.find(snmptrapoid) != policy_input.end()) {
				str = policy_input[snmptrapoid];
//				LMSG("SnmpTrapOid[%s]: %s", policy_input["version"].c_str(), str.c_str());

#if !defined HAVE_STLPORT && (defined __GNUC__ && __GNUC__ < 3)
				if (!str.substr(0, 19).compare(snmptraps) && str.size() == 20) {
#else
				if (!str.compare(0, 19, snmptraps) && str.size() == 20) {
#endif
					/* Non-enterprise trap. */
					switch (str.at(str.size() - 1)) {
						case '1':
							policy_input["trap-type"] = "cold-start";
							break;
						case '2':
							policy_input["trap-type"] = "warm-start";
							break;
						case '3':
							policy_input["trap-type"] = "link-down";
							break;
						case '4':
							policy_input["trap-type"] = "link-up";
							break;
						case '5':
							policy_input["trap-type"] = "authentication-failure";
							break;
						case '6':
							policy_input["trap-type"] = "egp-neighbor-loss";
							break;
						default:
							LMSG("DEBUG: I don't think it should ever get here!!");
							policy_input["trap-type"] = "enterprise";
					}
					policy_input["enterprise"] = enterprise;
					policy_input["specific-type"] = "0";

					policy_input["event"] = str;
				} else {
					/* Exterprise Specific Trap. */
					policy_input["trap-type"] = "enterprise";
#if 0
					/* Break down TrapOID. */
					if ((len = str.find_last_of('.')) > 0) {
						/* Grab and cut specific type. */
						snprintf(buf, BUF_SIZE, "%lu", atoi(str.substr(len + 1).c_str()));
						policy_input["specific-type"] = buf;
						str.erase(len);

						/* Cut off ".0" */
						if ((len = str.find_last_of('.')) > 0) {
							str.erase(len);
							policy_input["enterprise"] = str;

							/* Enterprise plus specific type (without the zero). */
							policy_input["event"] = str + "." + policy_input["specific-type"];
						} else {
							LMSG("WARNING: This should never happen.");
						}
					}
#else
					policy_input["event"] = str;

					/* Break down TrapOID. */
					if ((len = str.find_last_of('.')) > 0) {
						/* Grab and cut specific type. */
						snprintf(buf, BUF_SIZE, "%d", atoi(str.substr(len + 1).c_str()));
						policy_input["specific-type"] = buf;
						str.erase(len);
						policy_input["enterprise"] = str;
					}
#endif
				}
			} else {
				TRACE(TRACE_DEBUG, "damaged SNMPTrap PDU received");
			}

			TRACE(TRACE_INTERNAL, "Event: %s, Trap-Type: %s, Enterprise: %s, Specific: %s", policy_input["event"].c_str(), policy_input["trap-type"].c_str(), policy_input["enterprise"].c_str(), policy_input["specific-type"].c_str());
			TRACE(TRACE_INTERNAL, "");

			/* We could do a snmp_send() here to proxy traps to other sources, but we will need to use threadsafe calls to
			 * open another session.  The call to snmp_send() deallocates pdu for us, so it would be intead of SAFE_SNMP_FREE_PDU()
			 */

			SAFE_SNMP_FREE_PDU(pdu);

			/* Rate code */
			if (instance->rateInterval > 0) {
				instance->rate.increment(policy_input["source"]);
				snprintf(buf, BUF_SIZE, "%u", instance->rate.getRate(policy_input["source"]));
				policy_input["rate"] = buf;
				snprintf(buf, BUF_SIZE, "%u", instance->rate.getRate(policy_input["source"], instance->rateInterval / 2));
				policy_input["half-rate"] = buf;
			}

			/* Evaluate code */
			t = time(NULL);
			action = instance->inputPolicy.evaluate(policy_output, policy_input);
			TRACE(TRACE_TIMER, "inputPolicy.evaluate() ran %lu sec for trap: %s", time(NULL) - t, policy_input["trap-type"].c_str());

			if (action == ACTION_REJECT) {
				TRACE(TRACE_DEBUG, "Input policy rejected %s trap from %s. Proceeding to next trap.", policy_input["trap-type"].c_str(), policy_input["source"].c_str());

				continue;
			} else if (action == ACTION_FAILURE) {
				TRACE(TRACE_DEBUG, "Input policy evaluation failed on %s trap from %s. Shutting down.", policy_input["trap-type"].c_str(), policy_input["source"].c_str());

				interrupt = true;
				result = FAILURE;

				continue;
			}

			/* Copy output to input for output policy. */
			policy_input = policy_output;
			policy_output.clear();

			t = time(NULL);
			action = instance->outputPolicy.evaluate(policy_output, policy_input);
			TRACE(TRACE_TIMER, "outputPolicy.evaluate() ran %lu sec for trap: %s", time(NULL) - t, policy_input["trap-type"].c_str());
			
			if (action == ACTION_REJECT) {
				TRACE(TRACE_DEBUG, "Output policy rejected %s trap from %s. Proceeding to next trap.", policy_input["trap-type"].c_str(), policy_input["source"].c_str());

				continue;
			} else if (action == ACTION_FAILURE) {
				TRACE(TRACE_DEBUG, "Output policy evaluation failed on %s trap from %s. Shutting down.", policy_input["trap-type"].c_str(), policy_input["source"].c_str());

				interrupt = true;
				result = FAILURE;

				continue;
			}

			/*State code */
			if (policy_output.find("state") != policy_output.end()) {
				if (policy_output.find("ip-id") != policy_output.end()) {
					ipv4_state = new IPv4State(&(instance->storage), strtol(policy_output["ip-id"].c_str(), NULL, 10), "snmptrap");
					ipv4_state->set(policy_output["state"].c_str());
					SAFE_DELETE(ipv4_state);
				} else if (policy_output.find("interface-id") != policy_output.end()) {
					interface_state = new InterfaceState(&(instance->storage), strtol(policy_output["interface-id"].c_str(), NULL, 10), "snmptrap");
					interface_state->set(policy_output["state"].c_str());
					SAFE_DELETE(interface_state);
				} else if (policy_output.find("element-id") != policy_output.end()) {
					element_state = new ElementState(&(instance->storage), strtol(policy_output["element-id"].c_str(), NULL, 10), "snmptrap");
					element_state->set(policy_output["state"].c_str());
					SAFE_DELETE(element_state);
				}
			}
		}
	}

	TRACE(TRACE_THREAD, "SNMPTrap::policy_thread() ending main loop");

	pthread_exit(0);
}

RESULT SNMPTrap::proxy_thread(void* args) {
	RESULT result = SUCCESS;

	SNMPTrap *instance = (SNMPTrap*) args;

	//FIXME: agent_addr and address are not being done correctly here. We should be overwritting agent_addr with the IP of this server, and leaving address as the original sender.

	received_queue_t *proxy_queue = &instance->proxy_queue;
	pthread_mutex_t *proxy_mutex = &instance->proxy_mutex;
	vector<string> *proxy = &instance->proxy;

	int liberr, syserr;
	char *errmsg = NULL;
	snmp_pdu *pdu = NULL, *tmp_pdu = NULL;

	struct snmp_session session;
	void *opaque;

	TRACE(TRACE_THREAD, "SNMPTrap::proxy_thread() starting main loop");

	// Until we are asked to stop, store our successes.
	while (!interrupt) {
		/* Wait for the proxy flag to get set. */
		flag_wait(instance->proxy_flag, &(interrupt));

		while (!proxy_queue->empty() && !interrupt) {
			pthread_mutex_lock(proxy_mutex);
			pdu = proxy_queue->front();
			proxy_queue->pop();
			pthread_mutex_unlock(proxy_mutex);
			
			/* Conv PDU to if needed. */
			if (instance->conv_pdu(pdu, instance->convert, true) == FAILURE) {
				LMSG("DEBUG: Leak?? Should we free the memory here??? Seems like it.");
				continue; /* PDU was not processed properly. */
			}

			for (vector<string>::iterator it = proxy->begin(); it != proxy->end(); ++it) {
				snmp_sess_init(&session);
				tmp_pdu = snmp_clone_pdu(pdu);

				/* Set a reqid (snmp_sess_send() error without this) */
				tmp_pdu->reqid = snmp_get_next_reqid();

				switch (pdu->version) {
					case SNMP_VERSION_1:
						session.version = SNMP_VERSION_1;				

						/* Clear old destination */
#ifdef HAVE_NET_SNMP
						/* Untested. Double check this line is OK if issues. */
//						bzero((tmp_pdu->transport_data), sizeof(tmp_pdu->transport_data) * tmp_pdu->transport_data_length);
//						LMSG("bzero over %d bytes (Should be 4 bytes maybe..)", sizeof(tmp_pdu->transport_data) * tmp_pdu->transport_data_length);
#else
						bzero(&(tmp_pdu->address), sizeof(tmp_pdu->address));
						LMSG("bzero of %d byes", sizeof(tmp_pdu->address));
#endif

						break;

					case SNMP_VERSION_2c:
					case SNMP_VERSION_2u:
						session.version = SNMP_VERSION_2c;

						/* Clear old destination */
#ifdef HAVE_NET_SNMP
						/* Untested. Double check this line is OK if issues. */
//						bzero((tmp_pdu->transport_data), sizeof(tmp_pdu->transport_data) * tmp_pdu->transport_data_length);
#else
						bzero(&(tmp_pdu->address), sizeof(tmp_pdu->address));
#endif
						break;

					case SNMP_VERSION_3:
						LMSG("WARNING: SNMPv3 is not yet supported.");
						continue;
						break;

					default:
						LMSG("WARNING: PDU is an unsupported SNMP version.");
						continue;
						break;
				}

				/* Destination */
				session.peername = (char *) strdup((const char *) it->c_str());
				if (!strchr(session.peername, ':')) {
					session.remote_port = SNMP_TRAP_PORT;
				}
				
				session.retries = -1;
				session.timeout = 0;

				grantPriv();

				opaque = snmp_sess_open(&session);
				if (opaque == NULL) {

					snmp_sess_error(opaque, &liberr, &syserr, &errmsg);
					LMSG("WARNING: SNMP error: %s.", errmsg);
					SAFE_FREE(errmsg);
					result = FAILURE;
				} else {
					if (!snmp_sess_send(opaque, tmp_pdu)) { /* Will free tmp_pdu */
						snmp_sess_error(opaque, &liberr, &syserr, &errmsg);
						LMSG("WARNING: SNMP send failed: %s.", errmsg);
						SAFE_FREE(errmsg);
					} else {
						TRACE(TRACE_INTERNAL, "PDU proxied successfully.");
					}
					snmp_sess_close(opaque);
				}

				dropPriv();
			}

			SAFE_SNMP_FREE_PDU(pdu);
		}
	}

	TRACE(TRACE_THREAD, "SNMPTrap::proxy_thread() ending main loop");

	pthread_exit(0);
}

void SNMPTrap::cleanValue(char* buf) {
	long x, offset; /* Should'nt be unsigned .*/
	size_t len = strlen(buf);

	/* Remove trailing quotes and space. */
	for (x = len - 1; x >= 0; x--) {
		if (buf[x] == '"' || buf[x] == ' ') {
			buf[x] = '\0';
		} else {
			break;
		}
	}

	/* Find first non quote/space character. */
	len = strlen(buf);
	for (offset = 0; offset < len; offset++) {
		if (buf[offset] != '"' && buf[offset] != ' ') {
			break;
		}
	}

	/* Remove leading quotes and space. */
	for (x = offset; x < len; x++) {
		buf[x - offset] = buf[x];
	}
	buf[x - offset] = '\0';
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3
*/
