/*
 * Copyright (C) 2006 Voop as
 * Thorsten Lockert <tholo@voop.as>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*! \file
 *
 * \brief SNMP Agent / SubAgent support for Asterisk
 *
 * \author Thorsten Lockert <tholo@voop.as>
 */

/*** MODULEINFO
	<support_level>extended</support_level>
 ***/

/* Needed for net-snmp headers */
#define ASTMM_LIBC ASTMM_IGNORE
#include "asterisk.h"

/*
 * There is some collision collision between netsmp and asterisk names,
 * causing build under AST_DEVMODE to fail.
 *
 * The following PACKAGE_* macros are one place.
 * Also netsnmp has an improper check for HAVE_DMALLOC_H, using
 *    #if HAVE_DMALLOC_H   instead of #ifdef HAVE_DMALLOC_H
 * As a countermeasure we define it to 0, however this will fail
 * when the proper check is implemented.
 */
#ifdef PACKAGE_NAME
#undef PACKAGE_NAME
#endif
#ifdef PACKAGE_BUGREPORT
#undef PACKAGE_BUGREPORT
#endif
#ifdef PACKAGE_STRING
#undef PACKAGE_STRING
#endif
#ifdef PACKAGE_TARNAME
#undef PACKAGE_TARNAME
#endif
#ifdef PACKAGE_VERSION
#undef PACKAGE_VERSION
#endif
#ifndef HAVE_DMALLOC_H
#define HAVE_DMALLOC_H 0	/* XXX we shouldn't do this */
#endif

#if defined(__OpenBSD__)
/*
 * OpenBSD uses old "legacy" cc which has a rather pedantic builtin preprocessor.
 * Using a macro which is not #defined throws an error.
 */
#define __NetBSD_Version__ 0
#endif

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#if !defined(RONLY) && defined(NETSNMP_OLDAPI_RONLY)
#define RONLY NETSNMP_OLDAPI_RONLY
#endif

#include "asterisk/paths.h"	/* need ast_config_AST_SOCKET */
#include "asterisk/channel.h"
#include "asterisk/logger.h"
#include "asterisk/options.h"
#include "asterisk/indications.h"
#include "asterisk/ast_version.h"
#include "asterisk/pbx.h"

/* Collision between Net-SNMP and Asterisk */
#define unload_module ast_unload_module
#include "asterisk/module.h"
#undef unload_module

#include "agent.h"

/* Helper functions in Net-SNMP, header file not installed by default */
int header_generic(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **);
int header_simple_table(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **, int);
int register_sysORTable(oid *, size_t, const char *);
int unregister_sysORTable(oid *, size_t);

/* Forward declaration */
static void init_asterisk_mib(void);

/*
 * Anchor for all the Asterisk MIB values
 */
static oid asterisk_oid[] = { 1, 3, 6, 1, 4, 1, 22736, 1 };

/*
 * MIB values -- these correspond to values in the Asterisk MIB,
 * and MUST be kept in sync with the MIB for things to work as
 * expected.
 */
#define ASTVERSION				1
#define		ASTVERSTRING			1
#define		ASTVERTAG				2

#define	ASTCONFIGURATION		2
#define		ASTCONFUPTIME			1
#define		ASTCONFRELOADTIME		2
#define		ASTCONFPID				3
#define		ASTCONFSOCKET			4
#define		ASTCONFACTIVECALLS	5
#define		ASTCONFPROCESSEDCALLS   6

#define	ASTMODULES				3
#define		ASTMODCOUNT				1

#define	ASTINDICATIONS			4
#define		ASTINDCOUNT				1
#define		ASTINDCURRENT			2

#define		ASTINDTABLE				3
#define			ASTINDINDEX				1
#define			ASTINDCOUNTRY			2
#define			ASTINDALIAS				3
#define			ASTINDDESCRIPTION		4

#define	ASTCHANNELS				5
#define		ASTCHANCOUNT			1

#define		ASTCHANTABLE			2
#define			ASTCHANINDEX			1
#define			ASTCHANNAME				2
#define			ASTCHANLANGUAGE			3
#define			ASTCHANTYPE				4
#define			ASTCHANMUSICCLASS		5
#define			ASTCHANBRIDGE			6
#define			ASTCHANMASQ				7
#define			ASTCHANMASQR			8
#define			ASTCHANWHENHANGUP		9
#define			ASTCHANAPP				10
#define			ASTCHANDATA				11
#define			ASTCHANCONTEXT			12
#define			ASTCHANEXTEN			16
#define			ASTCHANPRI				17
#define			ASTCHANACCOUNTCODE		18
#define			ASTCHANFORWARDTO		19
#define			ASTCHANUNIQUEID			20
#define			ASTCHANCALLGROUP		21
#define			ASTCHANPICKUPGROUP		22
#define			ASTCHANSTATE			23
#define			ASTCHANMUTED			24
#define			ASTCHANRINGS			25
#define			ASTCHANCIDDNID			26
#define			ASTCHANCIDNUM			27
#define			ASTCHANCIDNAME			28
#define			ASTCHANCIDANI			29
#define			ASTCHANCIDRDNIS			30
#define			ASTCHANCIDPRES			31
#define			ASTCHANCIDANI2			32
#define			ASTCHANCIDTON			33
#define			ASTCHANCIDTNS			34
#define			ASTCHANAMAFLAGS			35
#define			ASTCHANADSI				36
#define			ASTCHANTONEZONE			37
#define			ASTCHANHANGUPCAUSE		38
#define			ASTCHANVARIABLES		39
#define			ASTCHANFLAGS			40
#define			ASTCHANTRANSFERCAP		41

#define		ASTCHANTYPECOUNT		3

#define		ASTCHANTYPETABLE		4
#define			ASTCHANTYPEINDEX		1
#define			ASTCHANTYPENAME			2
#define			ASTCHANTYPEDESC			3
#define			ASTCHANTYPEDEVSTATE		4
#define			ASTCHANTYPEINDICATIONS	5
#define			ASTCHANTYPETRANSFER		6
#define			ASTCHANTYPECHANNELS		7

#define		ASTCHANSCALARS			5
#define			ASTCHANBRIDGECOUNT		1

void *agent_thread(void *arg)
{
	ast_verb(2, "Starting %sAgent\n", res_snmp_agentx_subagent ? "Sub" : "");

	snmp_enable_stderrlog();

	if (res_snmp_agentx_subagent)
		netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID,
							   NETSNMP_DS_AGENT_ROLE,
							   1);

	init_agent("asterisk");

	init_asterisk_mib();

	init_snmp("asterisk");

	if (!res_snmp_agentx_subagent)
		init_master_agent();

	while (res_snmp_dont_stop)
		agent_check_and_process(1);

	snmp_shutdown("asterisk");

	ast_verb(2, "Terminating %sAgent\n", res_snmp_agentx_subagent ? "Sub" : "");

	return NULL;
}

static u_char *
ast_var_channels(struct variable *vp, oid *name, size_t *length,
				 int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	if (vp->magic != ASTCHANCOUNT)
		return NULL;

	long_ret = ast_active_channels();

	return (u_char *)&long_ret;
}

static u_char *ast_var_channels_table(struct variable *vp, oid *name, size_t *length,
									int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	static u_char bits_ret[2];
	static char string_ret[256];
	struct ast_channel *chan, *bridge;
	struct timeval tval;
	u_char *ret = NULL;
	int i, bit;
	struct ast_str *out = ast_str_alloca(2048);
	struct ast_channel_iterator *iter;

	if (header_simple_table(vp, name, length, exact, var_len, write_method, ast_active_channels()))
		return NULL;

	i = name[*length - 1] - 1;

	if (!(iter = ast_channel_iterator_all_new())) {
		return NULL;
	}

	while ((chan = ast_channel_iterator_next(iter)) && i) {
		ast_channel_unref(chan);
		i--;
	}

	iter = ast_channel_iterator_destroy(iter);

	if (chan == NULL) {
		return NULL;
	}

	*var_len = sizeof(long_ret);

	ast_channel_lock(chan);

	switch (vp->magic) {
	case ASTCHANINDEX:
		long_ret = name[*length - 1];
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANNAME:
		if (!ast_strlen_zero(ast_channel_name(chan))) {
			ast_copy_string(string_ret, ast_channel_name(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANLANGUAGE:
		if (!ast_strlen_zero(ast_channel_language(chan))) {
			ast_copy_string(string_ret, ast_channel_language(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANTYPE:
		ast_copy_string(string_ret, ast_channel_tech(chan)->type, sizeof(string_ret));
		*var_len = strlen(string_ret);
		ret = (u_char *)string_ret;
		break;
	case ASTCHANMUSICCLASS:
		if (!ast_strlen_zero(ast_channel_musicclass(chan))) {
			ast_copy_string(string_ret, ast_channel_musicclass(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANBRIDGE:
		ast_channel_unlock(chan);
		bridge = ast_channel_bridge_peer(chan);
		if (bridge) {
			ast_channel_lock(bridge);
			ast_copy_string(string_ret, ast_channel_name(bridge), sizeof(string_ret));
			ast_channel_unlock(bridge);
			ast_channel_unref(bridge);

			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		ast_channel_lock(chan);
		break;
	case ASTCHANMASQ:
		if (ast_channel_masq(chan) && !ast_strlen_zero(ast_channel_name(ast_channel_masq(chan)))) {
			ast_copy_string(string_ret, ast_channel_name(ast_channel_masq(chan)), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANMASQR:
		if (ast_channel_masqr(chan) && !ast_strlen_zero(ast_channel_name(ast_channel_masqr(chan)))) {
			ast_copy_string(string_ret, ast_channel_name(ast_channel_masqr(chan)), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANWHENHANGUP:
		if (!ast_tvzero(*ast_channel_whentohangup(chan))) {
			gettimeofday(&tval, NULL);
			long_ret = difftime(ast_channel_whentohangup(chan)->tv_sec, tval.tv_sec) * 100 - tval.tv_usec / 10000;
			ret= (u_char *)&long_ret;
		}
		break;
	case ASTCHANAPP:
		if (ast_channel_appl(chan)) {
			ast_copy_string(string_ret, ast_channel_appl(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANDATA:
		if (ast_channel_data(chan)) {
			ast_copy_string(string_ret, ast_channel_data(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCONTEXT:
		ast_copy_string(string_ret, ast_channel_context(chan), sizeof(string_ret));
		*var_len = strlen(string_ret);
		ret = (u_char *)string_ret;
		break;
	case ASTCHANEXTEN:
		ast_copy_string(string_ret, ast_channel_exten(chan), sizeof(string_ret));
		*var_len = strlen(string_ret);
		ret = (u_char *)string_ret;
		break;
	case ASTCHANPRI:
		long_ret = ast_channel_priority(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANACCOUNTCODE:
		if (!ast_strlen_zero(ast_channel_accountcode(chan))) {
			ast_copy_string(string_ret, ast_channel_accountcode(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANFORWARDTO:
		if (!ast_strlen_zero(ast_channel_call_forward(chan))) {
			ast_copy_string(string_ret, ast_channel_call_forward(chan), sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANUNIQUEID:
		ast_copy_string(string_ret, ast_channel_uniqueid(chan), sizeof(string_ret));
		*var_len = strlen(string_ret);
		ret = (u_char *)string_ret;
		break;
	case ASTCHANCALLGROUP:
		long_ret = ast_channel_callgroup(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANPICKUPGROUP:
		long_ret = ast_channel_pickupgroup(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANSTATE:
		long_ret = ast_channel_state(chan) & 0xffff;
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANMUTED:
		long_ret = ast_channel_state(chan) & AST_STATE_MUTE ? 1 : 2;
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANRINGS:
		long_ret = ast_channel_rings(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANCIDDNID:
		if (ast_channel_dialed(chan)->number.str) {
			ast_copy_string(string_ret, ast_channel_dialed(chan)->number.str, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCIDNUM:
		if (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str) {
			ast_copy_string(string_ret, ast_channel_caller(chan)->id.number.str, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCIDNAME:
		if (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str) {
			ast_copy_string(string_ret, ast_channel_caller(chan)->id.name.str, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCIDANI:
		if (ast_channel_caller(chan)->ani.number.valid && ast_channel_caller(chan)->ani.number.str) {
			ast_copy_string(string_ret, ast_channel_caller(chan)->ani.number.str, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCIDRDNIS:
		if (ast_channel_redirecting(chan)->from.number.valid && ast_channel_redirecting(chan)->from.number.str) {
			ast_copy_string(string_ret, ast_channel_redirecting(chan)->from.number.str, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANCIDPRES:
		long_ret = ast_party_id_presentation(&ast_channel_caller(chan)->id);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANCIDANI2:
		long_ret = ast_channel_caller(chan)->ani2;
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANCIDTON:
		long_ret = ast_channel_caller(chan)->id.number.plan;
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANCIDTNS:
		long_ret = ast_channel_dialed(chan)->transit_network_select;
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANAMAFLAGS:
		long_ret = ast_channel_amaflags(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANADSI:
		long_ret = ast_channel_adsicpe(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANTONEZONE:
		if (ast_channel_zone(chan)) {
			ast_copy_string(string_ret, ast_channel_zone(chan)->country, sizeof(string_ret));
			*var_len = strlen(string_ret);
			ret = (u_char *)string_ret;
		}
		break;
	case ASTCHANHANGUPCAUSE:
		long_ret = ast_channel_hangupcause(chan);
		ret = (u_char *)&long_ret;
		break;
	case ASTCHANVARIABLES:
		if (pbx_builtin_serialize_variables(chan, &out)) {
			*var_len = ast_str_strlen(out);
			ret = (u_char *)ast_str_buffer(out);
		}
		break;
	case ASTCHANFLAGS:
		bits_ret[0] = 0;
		for (bit = 0; bit < 8; bit++)
			bits_ret[0] |= ((ast_channel_flags(chan)->flags & (1 << bit)) >> bit) << (7 - bit);
		bits_ret[1] = 0;
		for (bit = 0; bit < 8; bit++)
			bits_ret[1] |= (((ast_channel_flags(chan)->flags >> 8) & (1 << bit)) >> bit) << (7 - bit);
		*var_len = 2;
		ret = bits_ret;
		break;
	case ASTCHANTRANSFERCAP:
		long_ret = ast_channel_transfercapability(chan);
		ret = (u_char *)&long_ret;
	default:
		break;
	}

	ast_channel_unlock(chan);
	chan = ast_channel_unref(chan);

	return ret;
}

static u_char *ast_var_channel_types(struct variable *vp, oid *name, size_t *length,
								   int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	struct ast_variable *channel_types, *next;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	if (vp->magic != ASTCHANTYPECOUNT)
		return NULL;

	for (long_ret = 0, channel_types = next = ast_channeltype_list(); next; next = next->next)
		long_ret++;
	ast_variables_destroy(channel_types);

	return (u_char *)&long_ret;
}

static u_char *ast_var_channel_types_table(struct variable *vp, oid *name, size_t *length,
										int exact, size_t *var_len, WriteMethod **write_method)
{
	const struct ast_channel_tech *tech = NULL;
	struct ast_variable *channel_types, *next;
	static unsigned long long_ret;
	struct ast_channel *chan;
	u_long i;

	if (header_simple_table(vp, name, length, exact, var_len, write_method, -1))
		return NULL;

	channel_types = ast_channeltype_list();
	for (i = 1, next = channel_types; next && i != name[*length - 1]; next = next->next, i++)
		;
	if (next != NULL)
		tech = ast_get_channel_tech(next->name);
	ast_variables_destroy(channel_types);
	if (next == NULL || tech == NULL)
		return NULL;

	switch (vp->magic) {
	case ASTCHANTYPEINDEX:
		long_ret = name[*length - 1];
		return (u_char *)&long_ret;
	case ASTCHANTYPENAME:
		*var_len = strlen(tech->type);
		return (u_char *)tech->type;
	case ASTCHANTYPEDESC:
		*var_len = strlen(tech->description);
		return (u_char *)tech->description;
	case ASTCHANTYPEDEVSTATE:
		long_ret = tech->devicestate ? 1 : 2;
		return (u_char *)&long_ret;
	case ASTCHANTYPEINDICATIONS:
		long_ret = tech->indicate ? 1 : 2;
		return (u_char *)&long_ret;
	case ASTCHANTYPETRANSFER:
		long_ret = tech->transfer ? 1 : 2;
		return (u_char *)&long_ret;
	case ASTCHANTYPECHANNELS:
	{
		struct ast_channel_iterator *iter;

		long_ret = 0;

		if (!(iter = ast_channel_iterator_all_new())) {
			return NULL;
		}

		while ((chan = ast_channel_iterator_next(iter))) {
			if (ast_channel_tech(chan) == tech) {
				long_ret++;
			}
			chan = ast_channel_unref(chan);
		}

		ast_channel_iterator_destroy(iter);

		return (u_char *)&long_ret;
	}
	default:
		break;
	}
	return NULL;
}

static u_char *ast_var_channel_bridge(struct variable *vp, oid *name, size_t *length,
	int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	struct ast_channel *chan = NULL;
	struct ast_channel_iterator *iter;

	long_ret = 0;

	if (header_generic(vp, name, length, exact, var_len, write_method)) {
		return NULL;
	}

	if (!(iter = ast_channel_iterator_all_new())) {
		return NULL;
	}

	while ((chan = ast_channel_iterator_next(iter))) {
		ast_channel_lock(chan);
		if (ast_channel_is_bridged(chan)) {
			long_ret++;
		}
		ast_channel_unlock(chan);
		chan = ast_channel_unref(chan);
	}

	ast_channel_iterator_destroy(iter);

	*var_len = sizeof(long_ret);

	return (vp->magic == ASTCHANBRIDGECOUNT) ? (u_char *) &long_ret : NULL;
}

static u_char *ast_var_Config(struct variable *vp, oid *name, size_t *length,
							 int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	struct timeval tval;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	switch (vp->magic) {
	case ASTCONFUPTIME:
		gettimeofday(&tval, NULL);
		long_ret = difftime(tval.tv_sec, ast_startuptime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_startuptime.tv_usec / 10000;
		return (u_char *)&long_ret;
	case ASTCONFRELOADTIME:
		gettimeofday(&tval, NULL);
		if (ast_lastreloadtime.tv_sec)
			long_ret = difftime(tval.tv_sec, ast_lastreloadtime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_lastreloadtime.tv_usec / 10000;
		else
			long_ret = difftime(tval.tv_sec, ast_startuptime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_startuptime.tv_usec / 10000;
		return (u_char *)&long_ret;
	case ASTCONFPID:
		long_ret = getpid();
		return (u_char *)&long_ret;
	case ASTCONFSOCKET:
		*var_len = strlen(ast_config_AST_SOCKET);
		return (u_char *)ast_config_AST_SOCKET;
	case ASTCONFACTIVECALLS:
		long_ret = ast_active_calls();
		return (u_char *)&long_ret;
	case ASTCONFPROCESSEDCALLS:
		long_ret = ast_processed_calls();
		return (u_char *)&long_ret;
	default:
		break;
	}
	return NULL;
}

static u_char *ast_var_indications(struct variable *vp, oid *name, size_t *length,
								  int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	static char ret_buf[128];
	struct ast_tone_zone *tz = NULL;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	switch (vp->magic) {
	case ASTINDCOUNT:
	{
		struct ao2_iterator i;

		long_ret = 0;

		i = ast_tone_zone_iterator_init();
		while ((tz = ao2_iterator_next(&i))) {
			tz = ast_tone_zone_unref(tz);
			long_ret++;
		}
		ao2_iterator_destroy(&i);

		return (u_char *) &long_ret;
	}
	case ASTINDCURRENT:
		tz = ast_get_indication_zone(NULL);
		if (tz) {
			ast_copy_string(ret_buf, tz->country, sizeof(ret_buf));
			*var_len = strlen(ret_buf);
			tz = ast_tone_zone_unref(tz);
			return (u_char *) ret_buf;
		}
		*var_len = 0;
		return NULL;
	default:
		break;
	}
	return NULL;
}

static u_char *ast_var_indications_table(struct variable *vp, oid *name, size_t *length,
									   int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;
	static char ret_buf[256];
	struct ast_tone_zone *tz = NULL;
	int i;
	struct ao2_iterator iter;

	if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) {
		return NULL;
	}

	i = name[*length - 1] - 1;

	iter = ast_tone_zone_iterator_init();

	while ((tz = ao2_iterator_next(&iter)) && i) {
		tz = ast_tone_zone_unref(tz);
		i--;
	}
	ao2_iterator_destroy(&iter);

	if (tz == NULL) {
		return NULL;
	}

	switch (vp->magic) {
	case ASTINDINDEX:
		ast_tone_zone_unref(tz);
		long_ret = name[*length - 1];
		return (u_char *)&long_ret;
	case ASTINDCOUNTRY:
		ast_copy_string(ret_buf, tz->country, sizeof(ret_buf));
		ast_tone_zone_unref(tz);
		*var_len = strlen(ret_buf);
		return (u_char *) ret_buf;
	case ASTINDALIAS:
		/* No longer exists */
		ast_tone_zone_unref(tz);
		return NULL;
	case ASTINDDESCRIPTION:
		ast_tone_zone_lock(tz);
		ast_copy_string(ret_buf, tz->description, sizeof(ret_buf));
		ast_tone_zone_unlock(tz);
		ast_tone_zone_unref(tz);
		*var_len = strlen(ret_buf);
		return (u_char *) ret_buf;
	default:
		ast_tone_zone_unref(tz);
		break;
	}
	return NULL;
}

static int countmodule(const char *mod, const char *desc, int use, const char *status,
		const char *like, enum ast_module_support_level support_level)
{
	return 1;
}

static u_char *ast_var_Modules(struct variable *vp, oid *name, size_t *length,
							  int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	if (vp->magic != ASTMODCOUNT)
		return NULL;

	long_ret = ast_update_module_list(countmodule, NULL);

	return (u_char *)&long_ret;
}

static u_char *ast_var_Version(struct variable *vp, oid *name, size_t *length,
							  int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	switch (vp->magic) {
	case ASTVERSTRING:
	{
		const char *version = ast_get_version();
		*var_len = strlen(version);
		return (u_char *)version;
	}
	case ASTVERTAG:
		sscanf(ast_get_version_num(), "%30lu", &long_ret);
		return (u_char *)&long_ret;
	default:
		break;
	}
	return NULL;
}

static int term_asterisk_mib(int majorID, int minorID, void *serverarg, void *clientarg)
{
	unregister_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid));
	return 0;
}

static void init_asterisk_mib(void)
{
	static struct variable4 asterisk_vars[] = {
		{ASTVERSTRING,           ASN_OCTET_STR, RONLY, ast_var_Version,             2, {ASTVERSION, ASTVERSTRING}},
		{ASTVERTAG,              ASN_UNSIGNED,  RONLY, ast_var_Version,             2, {ASTVERSION, ASTVERTAG}},
		{ASTCONFUPTIME,          ASN_TIMETICKS, RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFUPTIME}},
		{ASTCONFRELOADTIME,      ASN_TIMETICKS, RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFRELOADTIME}},
		{ASTCONFPID,             ASN_INTEGER,   RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFPID}},
		{ASTCONFSOCKET,          ASN_OCTET_STR, RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFSOCKET}},
		{ASTCONFACTIVECALLS,     ASN_GAUGE,   	RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFACTIVECALLS}},
		{ASTCONFPROCESSEDCALLS,  ASN_COUNTER,   RONLY, ast_var_Config,              2, {ASTCONFIGURATION, ASTCONFPROCESSEDCALLS}},
		{ASTMODCOUNT,            ASN_INTEGER,   RONLY, ast_var_Modules ,            2, {ASTMODULES, ASTMODCOUNT}},
		{ASTINDCOUNT,            ASN_INTEGER,   RONLY, ast_var_indications,         2, {ASTINDICATIONS, ASTINDCOUNT}},
		{ASTINDCURRENT,          ASN_OCTET_STR, RONLY, ast_var_indications,         2, {ASTINDICATIONS, ASTINDCURRENT}},
		{ASTINDINDEX,            ASN_INTEGER,   RONLY, ast_var_indications_table,   4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDINDEX}},
		{ASTINDCOUNTRY,          ASN_OCTET_STR, RONLY, ast_var_indications_table,   4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDCOUNTRY}},
		{ASTINDALIAS,            ASN_OCTET_STR, RONLY, ast_var_indications_table,   4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDALIAS}},
		{ASTINDDESCRIPTION,      ASN_OCTET_STR, RONLY, ast_var_indications_table,   4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDDESCRIPTION}},
		{ASTCHANCOUNT,           ASN_GAUGE,     RONLY, ast_var_channels,            2, {ASTCHANNELS, ASTCHANCOUNT}},
		{ASTCHANINDEX,           ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANINDEX}},
		{ASTCHANNAME,            ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANNAME}},
		{ASTCHANLANGUAGE,        ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANLANGUAGE}},
		{ASTCHANTYPE,            ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTYPE}},
		{ASTCHANMUSICCLASS,      ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUSICCLASS}},
		{ASTCHANBRIDGE,          ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANBRIDGE}},
		{ASTCHANMASQ,            ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQ}},
		{ASTCHANMASQR,           ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQR}},
		{ASTCHANWHENHANGUP,      ASN_TIMETICKS, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANWHENHANGUP}},
		{ASTCHANAPP,             ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAPP}},
		{ASTCHANDATA,            ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANDATA}},
		{ASTCHANCONTEXT,         ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCONTEXT}},
		{ASTCHANEXTEN,           ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANEXTEN}},
		{ASTCHANPRI,             ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPRI}},
		{ASTCHANACCOUNTCODE,     ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANACCOUNTCODE}},
		{ASTCHANFORWARDTO,       ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFORWARDTO}},
		{ASTCHANUNIQUEID,        ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANUNIQUEID}},
		{ASTCHANCALLGROUP,       ASN_UNSIGNED,  RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCALLGROUP}},
		{ASTCHANPICKUPGROUP,     ASN_UNSIGNED,  RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPICKUPGROUP}},
		{ASTCHANSTATE,           ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANSTATE}},
		{ASTCHANMUTED,           ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUTED}},
		{ASTCHANRINGS,           ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANRINGS}},
		{ASTCHANCIDDNID,         ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDDNID}},
		{ASTCHANCIDNUM,          ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNUM}},
		{ASTCHANCIDNAME,         ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNAME}},
		{ASTCHANCIDANI,          ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI}},
		{ASTCHANCIDRDNIS,        ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDRDNIS}},
		{ASTCHANCIDPRES,         ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDPRES}},
		{ASTCHANCIDANI2,         ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI2}},
		{ASTCHANCIDTON,          ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTON}},
		{ASTCHANCIDTNS,          ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTNS}},
		{ASTCHANAMAFLAGS,        ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAMAFLAGS}},
		{ASTCHANADSI,            ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANADSI}},
		{ASTCHANTONEZONE,        ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTONEZONE}},
		{ASTCHANHANGUPCAUSE,     ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANHANGUPCAUSE}},
		{ASTCHANVARIABLES,       ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANVARIABLES}},
		{ASTCHANFLAGS,           ASN_OCTET_STR, RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFLAGS}},
		{ASTCHANTRANSFERCAP,     ASN_INTEGER,   RONLY, ast_var_channels_table,      4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTRANSFERCAP}},
		{ASTCHANTYPECOUNT,       ASN_INTEGER,   RONLY, ast_var_channel_types,       2, {ASTCHANNELS, ASTCHANTYPECOUNT}},
		{ASTCHANTYPEINDEX,       ASN_INTEGER,   RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDEX}},
		{ASTCHANTYPENAME,        ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPENAME}},
		{ASTCHANTYPEDESC,        ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDESC}},
		{ASTCHANTYPEDEVSTATE,    ASN_INTEGER,   RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDEVSTATE}},
		{ASTCHANTYPEINDICATIONS, ASN_INTEGER,   RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDICATIONS}},
		{ASTCHANTYPETRANSFER,    ASN_INTEGER,   RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPETRANSFER}},
		{ASTCHANTYPECHANNELS,    ASN_GAUGE,     RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPECHANNELS}},
		{ASTCHANBRIDGECOUNT,     ASN_GAUGE,     RONLY, ast_var_channel_bridge,      3, {ASTCHANNELS, ASTCHANSCALARS, ASTCHANBRIDGECOUNT}},
	};

	register_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid),
			"ASTERISK-MIB implementation for Asterisk.");

	REGISTER_MIB("res_snmp", asterisk_vars, variable4, asterisk_oid);

	snmp_register_callback(SNMP_CALLBACK_LIBRARY,
			   SNMP_CALLBACK_SHUTDOWN,
			   term_asterisk_mib, NULL);
}

/*
 * Local Variables:
 * c-basic-offset: 4
 * c-file-offsets: ((case-label . 0))
 * tab-width: 4
 * indent-tabs-mode: t
 * End:
 */
