/*
** SellaNMS
** $Id: snmp_util.cxx,v 1.7 2006/01/24 07:08:09 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: snmp_util.cxx,v 1.7 2006/01/24 07:08:09 sella Exp $";

#include "snmp_util.h"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <stl/string>

#define BUF_SIZE 1024

void convOidToString(string &ret_val, oid *name, size_t name_len) {
	char buf[11];
	ret_val.erase();

	assert(name != NULL);
	assert(name_len < 100);

	for (size_t x = 0; x < name_len; x++) {
		snprintf(buf, 11, ".%lu", name[x]);
		ret_val.append(buf);
	}
}

char* convOidToChar(char* ret_val, size_t ret_len, oid *name, size_t name_len) {
	char *buf = new char[ret_len];

	assert(ret_val != NULL);

	ret_val[0] = '\0';
	for (size_t x = 0; x < name_len; x++) {
		strncpy(buf, ret_val, ret_len);
		snprintf(ret_val, ret_len, "%s.%lu", buf, name[x]);
	}

	delete[] buf;

	return ret_val;
}

bool convOidValueToString(string &ret_val, struct variable_list *vp) {
	char buf[BUF_SIZE];

	/* Instruct the libary to leave the OID and Hints off sprint_value() output. */
#ifdef HAVE_NET_SNMP
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, 1);
	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_PRINT_NUMERIC_OIDS, 1);
	ds_set_boolean(DS_LIBRARY_ID, DS_LIB_QUICK_PRINT, 1);
	ds_set_boolean(DS_LIBRARY_ID, DS_LIB_PRINT_BARE_VALUE, 1);
#endif

	ret_val.erase();

	switch (vp->type) {
		case SNMP_ENDOFMIBVIEW:
		case SNMP_NOSUCHOBJECT:
		case SNMP_NOSUCHINSTANCE:
			return false;

		case ASN_INTEGER:
			snprintf(buf, BUF_SIZE, "%ld", *(long*)vp->val.integer);
			break;

		case ASN_OBJECT_ID:
			/* Both options work. */
#if 0
			convOidToChar(buf, BUF_SIZE, (oid*) vp->val.string, (int) (vp->val_len / sizeof(oid)));
#else
			snprint_value(buf, BUF_SIZE, vp->name, vp->name_length, vp);
#endif
			break;

		case ASN_GAUGE:
		case ASN_COUNTER:
		case ASN_TIMETICKS:
			snprintf(buf, BUF_SIZE, "%lu", *(long*)vp->val.integer);
			break;

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

		default:
			LMSG("DEBUG: SNMP::sendRequest() hit default for vp->type == %d", vp->type);
			case ASN_NULL:
			case ASN_BOOLEAN:
			case ASN_COUNTER64:
			case ASN_OCTET_STR:
			/* If val_len == 0, sprint_value() fills in the next oid. No idea why. */
			if (vp->val_len == 0) {
				buf[0] = '\0';
			} else {
				snprint_value(buf, BUF_SIZE, vp->name, vp->name_length, vp);
#if 1
				/* FIXME: Hack to remove quotes around output. There is some way to disable this in NET-SNMP. */
				int buf_len = strlen(buf) - 1;
				if (buf[0] == '"' && buf[buf_len] == '"') {
					for (int x = 0; x < buf_len; x++) {
						buf[x] = buf[x + 1];
					}
					buf[buf_len - 1] = '\0';
				}
#endif
			}
			break;
	}

	ret_val = buf;

	return true;
}

char* convVariableToIP(char* ret_val, size_t ret_len, variable_list *var) {
#if 1
	char *buf = new char[ret_len];

	assert(ret_val != NULL);

	for (size_t x = 0; x < var->val_len; x++) {
		if (x == 0) {
			snprintf(ret_val, ret_len, "%u", var->val.string[x]);
		} else {
			strncpy(buf, ret_val, ret_len);
			snprintf(ret_val, ret_len, "%s.%u", buf, var->val.string[x]);
		}
	}

	delete[] buf;
#else
	char buf[4]; // 2^8 converted to a str + 1 */

	ret_val[0] = '\0';
	for (size_t x = 0; x < var->val_len; x++) {
		snprintf(buf, 4, "%d", var->val.string[x]);
		strcat(ret_val, buf);
		if (x + 1 < var->val_len) {
			strcat(ret_val, ".");
		}
	}
#endif

	return ret_val;
}

RESULT resolveEnumeration(string &out_val, const string &in_oid, const string &in_value) {
	size_t					objid_size = MAX_OID_LEN;
	long						value;

	oid						objid[MAX_OID_LEN];

	tree						*loaded_mibs = get_tree_head();
	tree						*t = NULL;

	if (loaded_mibs == NULL) {
		LMSG("MIB tree hasn't been loaded. No enumerations can be resolved.");

		return FAILURE;
	}

	/* Lookup oid based on input. */
	if (read_objid(in_oid.c_str(), objid, &objid_size) == 0) {
		objid_size = MAX_OID_LEN;
		if (get_node(in_oid.c_str(), objid, &objid_size) == 0) {
			/* If we couldn't convert, it wasn't an SNMP oid. */
			out_val = in_value;

			return SUCCESS;
		}
	}

	/* get_tree() sets t to the node right before the first index. */
	if ((t = get_tree(objid, objid_size, loaded_mibs)) != NULL) {
		switch (t->type) {
			case TYPE_INTEGER:
			case TYPE_INTEGER32:
				value = strtol(in_value.c_str(), NULL, 10);
				for (enum_list *e = t->enums; e != NULL; e = e->next) {
					if (e->value == value) {
						out_val = e->label;

						return SUCCESS;
					}
				}
				break;
				
			case TYPE_UINTEGER:
			case TYPE_UNSIGNED32:
				value = strtoul(in_value.c_str(), NULL, 10);
				for (enum_list *e = t->enums; e != NULL; e = e->next) {
					if (e->value == value) {
						out_val = e->label;

						return SUCCESS;
					}
				}
				break;
				
			case TYPE_TIMETICKS:
				/* NOTE: We don't report an error here since v2c traps on some Cisco routers incorrectly
							set the variable binding type to ASN_INTEGER, rather than ASN_TIMETICKS.
				*/
				break;

			case TYPE_OBJGROUP:
				/* Not 100% this is the correct handling of this type. Never seen one with an enum. */
				value = strtol(in_value.c_str(), NULL, 10);
				for (enum_list *e = t->enums; e != NULL; e = e->next) {
					if (e->value == value) {
						out_val = e->label;

						return SUCCESS;
					}
				}

				break;

			case TYPE_NETADDR:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_NETADDR(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_COUNTER:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_COUNTER(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_GAUGE:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_GAUGE(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_OTHER:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_OTHER(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				TRACE(TRACE_DEBUG, "You may not have a full set of MIBs. Check the oid here: http://jaguar.ir.miami.edu/cgi-bin/snmptransc.cgi");
				break;

			case TYPE_TRAPTYPE:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_TRAPTYPE(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_NOTIFTYPE:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_NOTIFTYPE(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_COUNTER64:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_COUNTER64(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_MODID:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_MODID(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			case TYPE_IPADDR:
				TRACE(TRACE_DEBUG, "Unimplemented t->type TYPE_IPADDR(%d) in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				break;

			default:
				TRACE(TRACE_DEBUG, "Unimplemented t->type %d in snmp_util.cxx. OID: %s, Value: %s", t->type, in_oid.c_str(), in_value.c_str());
				TRACE(TRACE_DEBUG, "Check the MIB here: http://jaguar.ir.miami.edu/cgi-bin/snmptransc.cgi");
				break;
		}
	}

/* 
#define TYPE_OTHER          0
#define TYPE_OBJID          1
#define TYPE_OCTETSTR       2
#define TYPE_INTEGER        3
#define TYPE_NETADDR        4
#define TYPE_IPADDR         5
#define TYPE_COUNTER        6
#define TYPE_GAUGE          7
#define TYPE_TIMETICKS      8
#define TYPE_OPAQUE         9
#define TYPE_NULL           10
#define TYPE_COUNTER64      11
#define TYPE_BITSTRING      12
#define TYPE_NSAPADDRESS    13
#define TYPE_UINTEGER       14
#define TYPE_UNSIGNED32     15
#define TYPE_INTEGER32      16
#define TYPE_SIMPLE_LAST    16
#define TYPE_TRAPTYPE       20
#define TYPE_NOTIFTYPE      21
#define TYPE_OBJGROUP       22
#define TYPE_NOTIFGROUP     23
#define TYPE_MODID          24
#define TYPE_AGENTCAP       25
#define TYPE_MODCOMP        26
*/

	/* Set output equal to input. */
	out_val = in_value;

	return SUCCESS;
}

bool convTxtToOid(oid *objid, size_t &objid_size, const string &input_oid) {
	objid_size = MAX_OID_LEN;
	if (read_objid(input_oid.c_str(), objid, &objid_size) == 0) {
		objid_size = MAX_OID_LEN;
		if (get_node(input_oid.c_str(), objid, &objid_size) == 0) {
			return false;
		}
	}

	return true;
}

RESULT resolveMIBObject(string &in) {
	RESULT result = SUCCESS;

	oid					objid[MAX_OID_LEN];
	size_t				objid_size = MAX_OID_LEN;

	string::size_type	b, e;
	string				var_name;

	string				input = in;

	/* Look for any 'snmp:<x>' substrings. */
	while ((b = input.find("snmp:")) != input.npos) {
		/* Find the end of the SNMP variable name. */
		e = input.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.", b + 5);

		/* Don't consume a trailing '.' */
		if (e > 1 && input[e - 1] == '.') {
			e--;
		}
	
		/* Strip out the SNMP variable name. */	
		var_name = input.substr(b + 5, e - b - 5);

		/* Look up the SNMP variable in the MIB and convert it to an OID. */
		if (convTxtToOid(objid, objid_size, var_name)) {
			convOidToString(var_name, objid, objid_size);
			
		} else {
			LMSG("WARNING: SNMP variable '%s' could not be resolved by any loaded MIB.", var_name.c_str());

			result = FAILURE;
		}
		input.replace(b, e - b, var_name);
	}
	/* Store the resolved string as the result. */
	in = input;

	return result;
}

RESULT parseOid(parsed_oid_t &parsed_oid, const string &input_oid) {
	string str;
	string index_str;
	size_t oid_size, org_oid_size, main_oid_size, index_oid_loc, index_oid_size, index_value_size;
	oid objid[MAX_OID_LEN], *oid_ptr;
	index_list *index, *index_root = NULL;
	tree *t, *tree_head = get_tree_head();
	pair<string, string> index_pair;

	/* Convert input_oid to an oid. */
	if (!convTxtToOid(objid, org_oid_size, input_oid)) {
		/* Passed input wasn't an oid. */
		LMSG("DEBUG: Passed wasn't an oid: %s (len: %ul)", input_oid.c_str(), org_oid_size);
		return FAILURE;
	}

	/* Strip out oid, minus index. get_tree() places us right before the index. */
	if ((t = get_tree(objid, org_oid_size, tree_head)) != NULL) {
		/* Climb down tree down to root, to get a node code. */
		for (main_oid_size = 0; t != NULL && main_oid_size < org_oid_size; main_oid_size++) {
			if (t->indexes != NULL) {
				/* This is a sanity check that can be removed. */
				if (index_root != NULL) {
					LMSG("DEBUG: Hey! There were two indexes within the tree. WTF?!");
				}
				index_root = t->indexes;
				index_oid_loc = main_oid_size;
			}
			t = t->parent;
		}

		/* NOTE: This may not be correct. If there is an index defined in the tree, we
		 *       assume that get_tree() dropped us on the node directly before the index.
		 *       Otherwise, if there is no index defined, then we cut the last node,
		 *       assuming its the index.
		*/
		if (index_root) {
			index_oid_size = main_oid_size - index_oid_loc;
		} else {
			/* Assume its a .0 index. */
			main_oid_size = org_oid_size - 1;
		}

		convOidToString(parsed_oid.main_oid, objid, main_oid_size);
		//LMSG("MINUS INDEX: %s  ->  %s", input_oid.c_str(), parsed_oid.main_oid.c_str());
	} else {
		LMSG("DEBUG: No tree. Input oid was: %s", input_oid.c_str());
		return FAILURE;
	}

	/* Clear our indexes. */
	parsed_oid.indexes.clear();

	/* Find all indexes. */
	if (index_root) {
		convOidToString(index_str, objid, index_oid_size);
		oid_ptr = objid + main_oid_size;
		/* Walk thru all available indexes. */
		for (index = index_root; index != NULL; index = index->next) {
			str = index_str + "." + index->ilabel;
			//LMSG("INDEX: %s -> %s", index->ilabel, str.c_str());
#if 0
			/* Original attempt. */
			if (!convTxtToOid(objid, oid_size, str)) {
				LMSG("NOTICE: Failed to convert index text (%s) into an oid. Your MIBs incomplete or damaged.", str.c_str());
				LMSG("NOTICE: Original input is: %s.", input_oid.c_str());
			} else {
#else
			/* New attempt. Not sure if this is exactly correct or not. */
			if (!convTxtToOid(objid, oid_size, str) && !convTxtToOid(objid, oid_size, index_str)) {
				LMSG("NOTICE[2]: Failed to convert index text (%s) into an oid. Your MIBs incomplete or damaged.", index_str.c_str());
				LMSG("NOTICE[2]: Original input is: %s.", input_oid.c_str());
			} else {
#if 0
				if (!convTxtToOid(objid, oid_size, str)) {
					LMSG("Using the second. Original input was: %s", input_oid.c_str());
				}
#endif
#endif
				/* Get tree entry for index. */
				if ((t = get_tree(objid, oid_size, tree_head)) != NULL) {
					if (t->type == TYPE_IPADDR || t->type == TYPE_NETADDR) {
						if (t->type == TYPE_NETADDR) {
							LMSG("DEBUG: It was a TYPE_NETADDR. Was the index really 4 in length: %s", input_oid.c_str());
						}
						index_value_size = 4;
					} else if (t->type == TYPE_NSAPADDRESS) {
						LMSG("DEBUG: It was a TYPE_NSAPADDRESS. Was the index really 20 in length: %s", input_oid.c_str());
						/* Untested. 20 might not be right. */
						index_value_size = 20;
					} else {
						index_value_size = 1;
					}
				} else {
					LMSG("DEBUG: No tree.. setting to 1. This shouldn't ever happen and this condition can be removed.");
					index_value_size = 1;
				}
				
				/* Insert index variable (plus index [+1]) into pair. */
				convOidToString(index_pair.first, objid, index_oid_size + 1);

				/* Insert index value into pair. */

				convOidToString(index_pair.second, oid_ptr, index_value_size);
				if (!index_pair.second.empty()) {
					/* Remove leading "." */
					index_pair.second.erase(0, 1);
				} else {
					LMSG("DEBUG: Failed to get index junk out of oid.");
					index_pair.second = "unknown";
				}
				/* Slide to end of this index, incase there is another. */
				oid_ptr += index_value_size;

				/* Insert pair into index slist. */
				parsed_oid.indexes.push_front(index_pair);
			}
		}
	}

	return SUCCESS;
}

struct snmp_session *snmp_async_session(string &host, string &community, void* callback, void* magic) {
	struct snmp_session sess, *ss;

	/* Initialize session. */
	snmp_sess_init(&sess);

	/* Configure session. */
	sess.version = SNMP_VERSION_2c;
	sess.peername = strdup(host.c_str());
	sess.community = (u_char*) strdup(community.c_str());
	sess.community_len = community.size();
	sess.callback = (snmp_callback) callback;
	sess.callback_magic = magic;

	if (!(ss = snmp_open(&sess))) {
		LMSG("Failed to open snmp session");

		return NULL;
	}

	return ss;
}

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