/*
** SellaNMS
** $Id: snmptrap.cxx,v 1.92 2005/08/22 02:25:42 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "snmptrap.h"
#include "debug.h"
#include "ip_util.h"
#include "snmp_util.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

SNMPTrap::SNMPTrap():exitAfter(0), nextWarning(0) {
	this->name.append(" snmptrap");

	has_scheduler = false;
	has_execute = true;

	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;
	RESULT t_result = SUCCESS;

	pthread_t thread_id = 0;

	bool running = true;
	struct snmp_session session;

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

	if (trace.thread) {
		LMSG("SNMPTrap::execute() starting");
	}

	/* Create the proxy thread (if needed) */
	if (!proxy.empty()) {
		if ((result = createThread(thread_id, (void* (*)(void*))SNMPTrap::proxy_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the storage thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))SNMPTrap::storage_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the receiver thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))SNMPTrap::receive_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

#ifdef WAIT_AND_KILL
	/* Wait and kill all threads. */
	for (it = t_list.begin(); it != t_list.end();) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
		/* Kill the next thread. */
		if (++it != t_list.end()) {
			this->killThread(*it);
		}
	}
#else
	/* Wait on all threads. */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
	}
#endif
	if (trace.thread) {
		LMSG("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;

	config->assignValues("proxy", proxy);

	if (config->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 (config->assignValue("source", str)) {
		if (inet_pton(AF_INET, str.c_str(), &(this->source)) == 0) {
			this->source = htonl(INADDR_ANY);
		}
	} else if (config->assignValue("interface", str)) {
		source_ip(str.c_str(), &ip);
		this->source = ip.s_addr;
	} else {
		this->source = htonl(INADDR_ANY);
	}

	config->assignValue("port", port, (unsigned long) SNMP_TRAP_PORT);

	config->assignValue("queue-depth", queueDepth, MODULE_MONITOR_SNMPTRAP_QUEUEDEPTH_DEFAULT, 1);
	config->assignValue("exit-after", exitAfter, MODULE_MONITOR_SNMPTRAP_EXITAFTER_DEFAULT);

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

			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (config->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 */
	storage.setConfig(*config);

	return result;
}

RESULT SNMPTrap::printConfig() {
	struct in_addr in;
	char inet_buffer[INET_ADDRSTRLEN];

	LMSG("port: %lu", this->port);
	LMSG("queue-depth: %lu", this->queueDepth);
	in.s_addr = this->source;
	LMSG("source: %s", inet_ntop(AF_INET, &in, inet_buffer, INET_ADDRSTRLEN));
	LMSG("exit-after: %lu", this->exitAfter);

	return Monitor::printConfig();
}

RESULT SNMPTrap::receive_thread(void* args) {
	RESULT result = SUCCESS;	
	STATUS status;

	unsigned long rx_count = 0;

	pthread_t thread_id = pthread_self();

	SNMPTrap *instance = (SNMPTrap*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	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;

	bool running = true;

	/* SNMP Variables */
	struct snmp_session session, *ss;
	int count, numfds, block;
	fd_set fdset;
	struct timeval timeout, *tvp;
	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];

	if (instance->trace.thread) {
		LMSG("SNMPTrap::recieve_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:%d", buf, instance->port);
		buf = NULL;
	} else {
		snprintf(peername, 32, "0.0.0.0:%d", 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
	if (instance->trace.internal) {
		LMSG("Peername: %s, Port: %lu", 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;
	*/

	instance->grantPriv();

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

		LMSG("WARNING: Failed to bind to port %d. Port already in use or permissions insufficient.", instance->port);

		result = FAILURE;
	} else {
		/* Grab a pointer to the session being used internally for this thread. */
		ss = snmp_sess_session(opaque);
	}

	instance->dropPriv();

	while (running && !instance->interrupt) {
		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		} else if (instance->exitAfter > 0 && rx_count >= instance->exitAfter) {
			LMSG("NOTICE: SNMPTrap::recieve_thread exiting. We have now recieved %lu snmptraps (exit-after: %lu).", rx_count, instance->exitAfter);
			running = false;
		} else {
			numfds = 0;
			FD_ZERO(&fdset);
			block = 0;
			timeout.tv_sec = 3;
			timeout.tv_usec = 0;
			snmp_sess_select_info(opaque, &numfds, &fdset, &timeout, &block);

			count = select(numfds, &fdset, 0, 0, (block) ? NULL : &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::recieve_thread select() returned in error. Ending thread.");
					running = false;

					//DEBUG
					snmp_sess_error(opaque, &liberr, &syserr, &errmsg);
					LMSG("Error? Was %lu/%lu (%s)", liberr, syserr, errmsg);
					SAFE_FREE(errmsg);
					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) {
						LMSG("Error? Was %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) {
							LMSG("WARNING: SNMPTrap proxy queue depth exceeded %lu. Dropping snmptrap.", instance->queueDepth);
						} else {
							tmp_pdu = snmp_clone_pdu(pdu);

							pthread_mutex_lock(proxy_mutex);
								proxy_queue->push(tmp_pdu);
								tmp_pdu = NULL; /* recieve_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) >= instance->nextWarning) {
							instance->nextWarning = time(NULL) + MODULE_MONITOR_SNMPTRAP_NEXTWARN_DEFAULT;
							LMSG("WARNING: SNMPTrap queue depth exceeded %lu. Dropping snmptrap from . (Suppressing warnings for %lus)", instance->queueDepth, MODULE_MONITOR_SNMPTRAP_NEXTWARN_DEFAULT);
						}

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

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

	snmp_sess_close(opaque);

	if (instance->trace.thread) {
		LMSG("SNMPTrap::recieve_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

int SNMPTrap::snmptrap_callback(int op, struct snmp_session *ss, int reqid, struct snmp_pdu *pdu, void *magic) {
	RESULT result = FAILURE;
	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 variable trap_oid[] = {1,3,6,1,6,3,1,1,5,0};
	static variable sysuptime[] = {1,3,6,1,2,1,1,3,0};
	static variable snmptrapoid[] = {1,3,6,1,6,3,1,1,4,1,0};
	static variable snmptrapaddress[] = {1,3,6,1,6,3,18,1,3,0};
	static variable snmptrapcommunity[] = {1,3,6,1,6,3,18,1,4,0};
	static variable 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, snmptrapvariable, 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_variable, 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::storage_thread(void* args) {
	RESULT result = SUCCESS;
	ACTION action;
	STATUS status;

	pthread_t thread_id = pthread_self();

	SNMPTrap *instance = (SNMPTrap*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	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;

	struct in_addr adr;
	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";

	bool write = false;
	bool running = true;
	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];

	if (instance->trace.thread) {
		LMSG("SNMPTrap::storage_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 (running && !instance->interrupt) {
		/* Wait for the trap flag to get set. */
		flag_wait(instance->trap_flag, &(instance->interrupt));

		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		}
		while (!trap_queue->empty() && !instance->interrupt) {
			/* 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. */
			}

			int count = 0;

			assert(pdu->variables != NULL);

			/* Process variables */
			for (var = pdu->variables; var != NULL; var = var->next_variable) {
				if (var->name_length >= MODULE_MONITOR_SNMPTRAP_MAXNAMELENGTH_DEFAULT || 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", (long *) *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, "%lu", (long *) *(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_variable, variable) == FAILURE) {
					LMSG("DEBUG: parseOid returned in failure. Skipping this oid. var->next_variable == %08x : %s %d", var->next_variable, __FILE__, __LINE__);
					continue;	
				}

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

				if (instance->trace.internal) {
					LMSG("policy_input[%s] = %s (Main)", parsed_oid.main_oid.c_str(), buf);
					if (!parsed_oid.indexes.empty()) {
						LMSG("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();

					if (instance->trace.internal) {
						//LMSG("- policy_input[%s[%s]] = %s", parsed_oid.main_oid.c_str(), index_pair.first.c_str(), index_pair.second.c_str());
						LMSG("- policy_input[%s] = %s", index_pair.first.c_str(), policy_input[index_pair.first].c_str());
					}
				}
			}

			/* Insert protocol. */
			policy_input["protocol"] = "snmp-trap";
			policy_input["source"] = "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
#if 0
			//FIXME: This is the 'correct' way to get the address.
			netsnmp_transport *transport = NULL;
			transport = snmp_sess_transport(snmp_sess_pointer(ss));
			if (transport != NULL && transport->f_fmtaddr != NULL) {
				char *s = transport->f_fmtaddr(transport, pdu->transport_data, pdu->transport_data_length);
				policy_input["prefix"] = s;
			} else {
				policy_input["prefix"] = "0.0.0.0";
			}
#else
			//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["prefix"] = inet_ntop(AF_INET, &(source->sin_addr), inet_buffer, INET_ADDRSTRLEN);
			} else {
				policy_input["prefix"] = "0.0.0.0";
			}
#endif
#else
			source = (struct sockaddr_in*) &(pdu->address);
			policy_input["prefix"] = inet_ntop(AF_INET, &(source->sin_addr), inet_buffer, INET_ADDRSTRLEN);
#endif
			policy_input["prefix-except"] = policy_input["prefix"];

			/* 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, "%lu", atoi(str.substr(len + 1).c_str()));
						policy_input["specific-type"] = buf;
						str.erase(len);
						policy_input["enterprise"] = str;
					}
#endif
				}
			} else {
				LMSG("WARNING: Damaged SNMPTrap PDU!");
			}

			if (instance->trace.internal) {
				LMSG("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());
				LMSG("");
			}

			/* 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);

			t = time(NULL);
			if ((action = instance->outputPolicy.evaluate(policy_output, policy_input)) == ACTION_FAILURE) {
				if (instance->trace.debug) {
					LMSG("WARNING: Output policy evaluation returned in failure. Recieve thread terminating.");
				}
				instance->interrupt = true;
				result = FAILURE;
			}
			if (instance->trace.timer) {
				LMSG("outputPolicy.evaluate() ran %lu sec for trap: %s", time(NULL) - t, policy_input["trap-type"].c_str());
			}

			/* Clear policy data for next round. */
			policy_input.clear();
			policy_output.clear();
		}

//		usleep(USLEEP_NORMLOOP_TIME);
	}
		
	if (instance->trace.thread) {
		LMSG("SNMPTrap::storage_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

bool SNMPTrap::suppressDuplicate(u_int32_t *source, char* msg, int n) {
	bool ret_val = false;
	unsigned long signature = 0;
	struct in_addr ip;
	time_t stale = time(NULL) - dupHistAge;
	char inet_buffer[INET_ADDRSTRLEN];

	/* If no dup history, then no duplicates. */
	if (dupHistMax < 1) {
		return false;
	}

	/* Generate the signature. */
	for (int x = 0; x < n; x++) {
		signature += msg[x];
	}

	/* Check for the duplicate */
	for (int x = 0; x < dupHistMax; x++) {
		/* If the source is the same, and the entry isn't stale check for a duplicate. */
		if (dupHist[x].source == *source && dupHist[x].timestamp >= stale) {
			/* Is the entry close enough to be a duplicate? */
			if (dupHist[x].signature - dupFuzFactor <= signature && dupHist[x].signature + dupFuzFactor >= signature) {
				if (trace.debug) {
					ip.s_addr = *source;
					LMSG("SNMPTrap duplicate detected (age: %d, var: %d) from %s: %s", time(NULL) - dupHist[x].timestamp, dupHist[x].signature - signature, inet_ntop(AF_INET, &ip, inet_buffer, INET_ADDRSTRLEN), msg);
				}
				ret_val = true;
				break;
			}
		}
	}

	/* Insert new entry into history. */
	dupHistCur = dupHistCur >= (dupHistMax) ? 0 : dupHistCur + 1;
	dupHist[dupHistCur].source = *source;
	dupHist[dupHistCur].timestamp = time(NULL);
	dupHist[dupHistCur].signature = signature;

	return ret_val;
}


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

	pthread_t thread_id = pthread_self();

	SNMPTrap *instance = (SNMPTrap*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	//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;

	bool running = true;
	unsigned int len;
	string* last_id;
	int liberr, syserr;
	char *errmsg = NULL;
	snmp_pdu *pdu = NULL, *tmp_pdu = NULL;
	struct sockaddr_in *address, *agent_addr;

	struct snmp_session session, *ss;
	int count, numfds, block;
	fd_set fdset;
	struct timeval timeout, *tvp;
	void *opaque;

	if (instance->trace.thread) {
		LMSG("SNMPTrap::proxy_thread() starting main loop");
	}

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

		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		}
		while (!proxy_queue->empty() && !instance->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), 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;

				instance->grantPriv();

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

					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);
					}
					snmp_sess_close(opaque);
				}

				instance->dropPriv();
			}

			SAFE_SNMP_FREE_PDU(pdu);
		}

//		usleep(USLEEP_NORMLOOP_TIME);
	}

	if (instance->trace.thread) {
		LMSG("SNMPTrap::proxy_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
