/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 1999 - 2006, Digium, Inc.
 *
 * Mark Spencer <markster@digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * 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 Distributed Universal Number Discovery (DUNDi)
 */

/*! \li \ref pbx_dundi.c uses configuration file \ref dundi.conf
 * \addtogroup configuration_file Configuration Files
 */

/*!
 * \page dundi.conf dundi.conf
 * \verbinclude dundi.conf.sample
 */

/*** MODULEINFO
	<depend>zlib</depend>
	<use type="module">res_crypto</use>
	<use type="external">crypto</use>
	<support_level>extended</support_level>
 ***/

#include "asterisk.h"

#include "asterisk/network.h"
#include <sys/ioctl.h>
#include <zlib.h>
#include <signal.h>
#include <pthread.h>
#include <net/if.h>

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__Darwin__)
#include <net/if_dl.h>
#include <ifaddrs.h>
#include <signal.h>
#endif

#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/config.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/frame.h"
#include "asterisk/cli.h"
#include "asterisk/lock.h"
#include "asterisk/md5.h"
#include "asterisk/dundi.h"
#include "asterisk/sched.h"
#include "asterisk/io.h"
#include "asterisk/utils.h"
#include "asterisk/netsock2.h"
#include "asterisk/crypto.h"
#include "asterisk/astdb.h"
#include "asterisk/acl.h"
#include "asterisk/app.h"

#include "dundi-parser.h"

/*** DOCUMENTATION
	<function name="DUNDILOOKUP" language="en_US">
		<synopsis>
			Do a DUNDi lookup of a phone number.
		</synopsis>
		<syntax>
			<parameter name="number" required="true"/>
			<parameter name="context">
				<para>If not specified the default will be <literal>e164</literal>.</para>
			</parameter>
			<parameter name="options">
				<optionlist>
					<option name="b">
						<para>Bypass the internal DUNDi cache</para>
					</option>
				</optionlist>
			</parameter>
		</syntax>
		<description>
			<para>This will do a DUNDi lookup of the given phone number.</para>
			<para>This function will return the Technology/Resource found in the first result
			in the DUNDi lookup. If no results were found, the result will be blank.</para>
		</description>
	</function>


	<function name="DUNDIQUERY" language="en_US">
		<synopsis>
			Initiate a DUNDi query.
		</synopsis>
		<syntax>
			<parameter name="number" required="true"/>
			<parameter name="context">
				<para>If not specified the default will be <literal>e164</literal>.</para>
			</parameter>
			<parameter name="options">
				<optionlist>
					<option name="b">
						<para>Bypass the internal DUNDi cache</para>
					</option>
				</optionlist>
			</parameter>
		</syntax>
		<description>
			<para>This will do a DUNDi lookup of the given phone number.</para>
			<para>The result of this function will be a numeric ID that can be used to retrieve
			the results with the <literal>DUNDIRESULT</literal> function.</para>
		</description>
	</function>

	<function name="DUNDIRESULT" language="en_US">
		<synopsis>
			Retrieve results from a DUNDIQUERY.
		</synopsis>
		<syntax>
			<parameter name="id" required="true">
				<para>The identifier returned by the <literal>DUNDIQUERY</literal> function.</para>
			</parameter>
			<parameter name="resultnum">
				<optionlist>
					<option name="number">
						<para>The number of the result that you want to retrieve, this starts at <literal>1</literal></para>
					</option>
					<option name="getnum">
						<para>The total number of results that are available.</para>
					</option>
				</optionlist>
			</parameter>
		</syntax>
		<description>
			<para>This function will retrieve results from a previous use\n"
			of the <literal>DUNDIQUERY</literal> function.</para>
		</description>
	</function>
 ***/

#define MAX_RESULTS	64

#define MAX_PACKET_SIZE 8192

#define MAX_WEIGHT 59999

#define DUNDI_MODEL_INBOUND		(1 << 0)
#define DUNDI_MODEL_OUTBOUND	(1 << 1)
#define DUNDI_MODEL_SYMMETRIC	(DUNDI_MODEL_INBOUND | DUNDI_MODEL_OUTBOUND)

/*! Keep times of last 10 lookups */
#define DUNDI_TIMING_HISTORY	10

enum {
	FLAG_ISREG =       (1 << 0),  /*!< Transaction is register request */
	FLAG_DEAD =        (1 << 1),  /*!< Transaction is dead */
	FLAG_FINAL =       (1 << 2),  /*!< Transaction has final message sent */
	FLAG_ISQUAL =      (1 << 3),  /*!< Transaction is a qualification */
	FLAG_ENCRYPT =     (1 << 4),  /*!< Transaction is encrypted with ECX/DCX */
	FLAG_SENDFULLKEY = (1 << 5),  /*!< Send full key on transaction */
	FLAG_STOREHIST =   (1 << 6),  /*!< Record historic performance */
};

#define DUNDI_FLAG_INTERNAL_NOPARTIAL (1 << 17)

#if 0
#define DUNDI_SECRET_TIME 15	/* Testing only */
#else
#define DUNDI_SECRET_TIME DUNDI_DEFAULT_CACHE_TIME
#endif

static struct io_context *io;
static struct ast_sched_context *sched;
static int netsocket = -1; /* Socket for bindaddr if only one bindaddr. Otherwise the IPv4 socket when bindaddr2 given. */
static int netsocket2 = -1; /* IPv6 socket when bindaddr2 given. */
static pthread_t netthreadid = AST_PTHREADT_NULL;
static pthread_t precachethreadid = AST_PTHREADT_NULL;
static pthread_t clearcachethreadid = AST_PTHREADT_NULL;
static unsigned int tos = 0;
static int dundidebug = 0;
static int authdebug = 0;
static int dundi_ttl = DUNDI_DEFAULT_TTL;
static int dundi_key_ttl = DUNDI_DEFAULT_KEY_EXPIRE;
static int dundi_cache_time = DUNDI_DEFAULT_CACHE_TIME;
static int global_autokilltimeout = 0;
static dundi_eid global_eid;
static int default_expiration = 60;
static int global_storehistory = 0;
static char dept[80];
static char org[80];
static char locality[80];
static char stateprov[80];
static char country[80];
static char email[80];
static char phone[80];
static char secretpath[80];
static char cursecret[80];
static char ipaddr[80];
static time_t rotatetime;
static dundi_eid empty_eid = { { 0, 0, 0, 0, 0, 0 } };
static int dundi_shutdown = 0;

struct permission {
	AST_LIST_ENTRY(permission) list;
	int allow;
	char name[0];
};

struct dundi_packet {
	AST_LIST_ENTRY(dundi_packet) list;
	struct dundi_hdr *h;
	int datalen;
	struct dundi_transaction *parent;
	int retransid;
	int retrans;
	unsigned char data[0];
};

struct dundi_hint_metadata {
	unsigned short flags;
	char exten[AST_MAX_EXTENSION];
};

struct dundi_precache_queue {
	AST_LIST_ENTRY(dundi_precache_queue) list;
	char *context;
	time_t expiration;
	char number[0];
};

struct dundi_request;

struct dundi_transaction {
	struct ast_sockaddr addr;                      /*!< Other end of transaction */
	struct timeval start;                          /*!< When this transaction was created */
	dundi_eid eids[DUNDI_MAX_STACK + 1];
	int eidcount;                                  /*!< Number of eids in eids */
	dundi_eid us_eid;                              /*!< Our EID, to them */
	dundi_eid them_eid;                            /*!< Their EID, to us */
	ast_aes_encrypt_key ecx;                       /*!< AES 128 Encryption context */
	ast_aes_decrypt_key dcx;                       /*!< AES 128 Decryption context */
	unsigned int flags;                            /*!< Has final packet been sent */
	int ttl;                                       /*!< Remaining TTL for queries on this one */
	int thread;                                    /*!< We have a calling thread */
	int retranstimer;                              /*!< How long to wait before retransmissions */
	int autokillid;                                /*!< ID to kill connection if answer doesn't come back fast enough */
	int autokilltimeout;                           /*!< Recommended timeout for autokill */
	unsigned short strans;                         /*!< Our transaction identifier */
	unsigned short dtrans;                         /*!< Their transaction identifer */
	unsigned char iseqno;                          /*!< Next expected received seqno */
	unsigned char oiseqno;                         /*!< Last received incoming seqno */
	unsigned char oseqno;                          /*!< Next transmitted seqno */
	unsigned char aseqno;                          /*!< Last acknowledge seqno */
	AST_LIST_HEAD_NOLOCK(packetlist, dundi_packet) packets;  /*!< Packets to be retransmitted */
	struct packetlist lasttrans;                   /*!< Last transmitted / ACK'd packet */
	struct dundi_request *parent;                  /*!< Parent request (if there is one) */
	AST_LIST_ENTRY(dundi_transaction) parentlist;  /*!< Next with respect to the parent */
	AST_LIST_ENTRY(dundi_transaction) all;         /*!< Next with respect to all DUNDi transactions */
};

struct dundi_request {
	char dcontext[AST_MAX_EXTENSION];
	char number[AST_MAX_EXTENSION];
	dundi_eid query_eid;
	dundi_eid root_eid;
	struct dundi_result *dr;
	struct dundi_entity_info *dei;
	struct dundi_hint_metadata *hmd;
	int maxcount;
	int respcount;
	int expiration;
	int cbypass;
	int pfds[2];
	uint32_t crc32;                              /*!< CRC-32 of all but root EID's in avoid list */
	AST_LIST_HEAD_NOLOCK(, dundi_transaction) trans;  /*!< Transactions */
	AST_LIST_ENTRY(dundi_request) list;
};

struct dundi_mapping {
	char dcontext[AST_MAX_EXTENSION];
	char lcontext[AST_MAX_EXTENSION];
	int _weight;
	char *weightstr;
	int options;
	int tech;
	int dead;
	char dest[512];
	AST_LIST_ENTRY(dundi_mapping) list;
};

struct dundi_peer {
	dundi_eid eid;
	struct ast_sockaddr addr;              /*!< Address of DUNDi peer */
	AST_LIST_HEAD_NOLOCK(permissionlist, permission) permit;
	struct permissionlist include;
	dundi_eid us_eid;
	char inkey[80];
	char outkey[80];
	int dead;
	int registerid;
	int qualifyid;
	int sentfullkey;
	int order;
	unsigned char txenckey[256];           /*!< Transmitted encrypted key + sig */
	unsigned char rxenckey[256];           /*!< Cache received encrypted key + sig */
	uint32_t us_keycrc32;                  /*!< CRC-32 of our key */
	ast_aes_encrypt_key us_ecx;            /*!< Cached AES 128 Encryption context */
	ast_aes_decrypt_key us_dcx;            /*!< Cached AES 128 Decryption context */
	uint32_t them_keycrc32;                /*!< CRC-32 of our key */
	ast_aes_encrypt_key them_ecx;          /*!< Cached AES 128 Encryption context */
	ast_aes_decrypt_key them_dcx;          /*!< Cached AES 128 Decryption context */
	time_t keyexpire;                      /*!< When to expire/recreate key */
	int registerexpire;
	int lookuptimes[DUNDI_TIMING_HISTORY];
	char *lookups[DUNDI_TIMING_HISTORY];
	int avgms;
	struct dundi_transaction *regtrans;    /*!< Registration transaction */
	struct dundi_transaction *qualtrans;   /*!< Qualify transaction */
	int model;                             /*!< Pull model */
	int pcmodel;                           /*!< Push/precache model */
	/*! Dynamic peers register with us */
	unsigned int dynamic:1;
	int lastms;                            /*!< Last measured latency */
	int maxms;                             /*!< Max permissible latency */
	struct timeval qualtx;                 /*!< Time of transmit */
	AST_LIST_ENTRY(dundi_peer) list;
};

static AST_LIST_HEAD_STATIC(peers, dundi_peer);
static AST_LIST_HEAD_STATIC(pcq, dundi_precache_queue);
static AST_LIST_HEAD_NOLOCK_STATIC(mappings, dundi_mapping);
static AST_LIST_HEAD_NOLOCK_STATIC(requests, dundi_request);
static AST_LIST_HEAD_NOLOCK_STATIC(alltrans, dundi_transaction);

/*!
 * \brief Wildcard peer
 *
 * This peer is created if the [*] entry is specified in dundi.conf
 */
static struct dundi_peer *any_peer;

static int dundi_xmit(struct dundi_packet *pack);

static void dundi_debug_output(const char *data)
{
	if (dundidebug)
		ast_verbose("%s", data);
}

static void dundi_error_output(const char *data)
{
	ast_log(LOG_WARNING, "%s", data);
}

static int has_permission(struct permissionlist *permlist, char *cont)
{
	struct permission *perm;
	int res = 0;

	AST_LIST_TRAVERSE(permlist, perm, list) {
		if (!strcasecmp(perm->name, "all") || !strcasecmp(perm->name, cont))
			res = perm->allow;
	}

	return res;
}

static char *tech2str(int tech)
{
	switch(tech) {
	case DUNDI_PROTO_NONE:
		return "None";
	case DUNDI_PROTO_IAX:
		return "IAX2";
	case DUNDI_PROTO_SIP:
		return "SIP";
	case DUNDI_PROTO_H323:
		return "H323";
	default:
		return "Unknown";
	}
}

static int str2tech(char *str)
{
	if (!strcasecmp(str, "IAX") || !strcasecmp(str, "IAX2"))
		return DUNDI_PROTO_IAX;
	else if (!strcasecmp(str, "SIP"))
		return DUNDI_PROTO_SIP;
	else if (!strcasecmp(str, "H323"))
		return DUNDI_PROTO_H323;
	else
		return -1;
}

static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *md, int *expiration, int cybpass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[]);
static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[]);
static struct dundi_transaction *create_transaction(struct dundi_peer *p);

static struct dundi_transaction *find_transaction(struct dundi_hdr *hdr, struct ast_sockaddr *sin)
{
	struct dundi_transaction *trans;

	/* Look for an exact match first */
	AST_LIST_TRAVERSE(&alltrans, trans, all) {
		if (!ast_sockaddr_cmp(&trans->addr, sin) &&
			((trans->strans == (ntohs(hdr->dtrans) & 32767)) /* Matches our destination */ ||
				((trans->dtrans == (ntohs(hdr->strans) & 32767)) && (!hdr->dtrans))) /* We match their destination */) {
			if (hdr->strans) {
				trans->dtrans = ntohs(hdr->strans) & 32767;
			}
			return trans;
		}
	}

	switch(hdr->cmdresp & 0x7f) {
	case DUNDI_COMMAND_DPDISCOVER:
	case DUNDI_COMMAND_EIDQUERY:
	case DUNDI_COMMAND_PRECACHERQ:
	case DUNDI_COMMAND_REGREQ:
	case DUNDI_COMMAND_NULL:
	case DUNDI_COMMAND_ENCRYPT:
		if (!hdr->strans)
			break;
		/* Create new transaction */
		if (!(trans = create_transaction(NULL)))
			break;
		ast_sockaddr_copy(&trans->addr, sin);
		trans->dtrans = ntohs(hdr->strans) & 32767;
	default:
		break;
	}

	return trans;
}

static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied);

static int dundi_ack(struct dundi_transaction *trans, int final)
{
	return dundi_send(trans, DUNDI_COMMAND_ACK, 0, final, NULL);
}
static void dundi_reject(struct dundi_hdr *h, struct ast_sockaddr *sin)
{
	struct {
		struct dundi_packet pack;
		struct dundi_hdr hdr;
	} tmp;
	struct dundi_transaction trans;
	/* Never respond to an INVALID with another INVALID */
	if (h->cmdresp == DUNDI_COMMAND_INVALID)
		return;
	memset(&tmp, 0, sizeof(tmp));
	memset(&trans, 0, sizeof(trans));
	ast_sockaddr_copy(&trans.addr, sin);
	tmp.hdr.strans = h->dtrans;
	tmp.hdr.dtrans = h->strans;
	tmp.hdr.iseqno = h->oseqno;
	tmp.hdr.oseqno = h->iseqno;
	tmp.hdr.cmdresp = DUNDI_COMMAND_INVALID;
	tmp.hdr.cmdflags = 0;
	tmp.pack.h = (struct dundi_hdr *)tmp.pack.data;
	tmp.pack.datalen = sizeof(struct dundi_hdr);
	tmp.pack.parent = &trans;
	dundi_xmit(&tmp.pack);
}

static int get_trans_id(void)
{
	struct dundi_transaction *t;
	int stid = (ast_random() % 32766) + 1;
	int tid = stid;

	do {
		AST_LIST_TRAVERSE(&alltrans, t, all) {
			if (t->strans == tid)
				break;
		}
		if (!t)
			return tid;
		tid = (tid % 32766) + 1;
	} while (tid != stid);

	return 0;
}

static int reset_transaction(struct dundi_transaction *trans)
{
	int tid;
	tid = get_trans_id();
	if (tid < 1)
		return -1;
	trans->strans = tid;
	trans->dtrans = 0;
	trans->iseqno = 0;
	trans->oiseqno = 0;
	trans->oseqno = 0;
	trans->aseqno = 0;
	ast_clear_flag(trans, FLAG_FINAL);
	return 0;
}

static struct dundi_peer *find_peer(dundi_eid *eid)
{
	struct dundi_peer *cur = NULL;

	if (!eid)
		eid = &empty_eid;

	AST_LIST_TRAVERSE(&peers, cur, list) {
		if (!ast_eid_cmp(&cur->eid,eid))
			break;
	}

	if (!cur && any_peer)
		cur = any_peer;

	return cur;
}

static void build_iv(unsigned char *iv)
{
	/* XXX Would be nice to be more random XXX */
	unsigned int *fluffy;
	int x;
	fluffy = (unsigned int *)(iv);
	for (x=0;x<4;x++)
		fluffy[x] = ast_random();
}

struct dundi_query_state {
	dundi_eid *eids[DUNDI_MAX_STACK + 1];
	int directs[DUNDI_MAX_STACK + 1];
	dundi_eid reqeid;
	char called_context[AST_MAX_EXTENSION];
	char called_number[AST_MAX_EXTENSION];
	struct dundi_mapping *maps;
	int nummaps;
	int nocache;
	struct dundi_transaction *trans;
	void *chal;
	int challen;
	int ttl;
	char fluffy[0];
};

static int get_mapping_weight(struct dundi_mapping *map, struct varshead *headp)
{
	char buf[32];

	buf[0] = 0;
	if (map->weightstr) {
		if (headp) {
			pbx_substitute_variables_varshead(headp, map->weightstr, buf, sizeof(buf) - 1);
		} else {
			pbx_substitute_variables_helper(NULL, map->weightstr, buf, sizeof(buf) - 1);
		}

		if (sscanf(buf, "%30d", &map->_weight) != 1)
			map->_weight = MAX_WEIGHT;
	}

	return map->_weight;
}

static int dundi_lookup_local(struct dundi_result *dr, struct dundi_mapping *map, char *called_number, dundi_eid *us_eid, int anscnt, struct dundi_hint_metadata *hmd)
{
	struct ast_flags flags = {0};
	int x;
	if (!ast_strlen_zero(map->lcontext)) {
		if (ast_exists_extension(NULL, map->lcontext, called_number, 1, NULL))
			ast_set_flag(&flags, DUNDI_FLAG_EXISTS);
		if (ast_canmatch_extension(NULL, map->lcontext, called_number, 1, NULL))
			ast_set_flag(&flags, DUNDI_FLAG_CANMATCH);
		if (ast_matchmore_extension(NULL, map->lcontext, called_number, 1, NULL))
			ast_set_flag(&flags, DUNDI_FLAG_MATCHMORE);
		if (ast_ignore_pattern(map->lcontext, called_number))
			ast_set_flag(&flags, DUNDI_FLAG_IGNOREPAT);

		/* Clearly we can't say 'don't ask' anymore if we found anything... */
		if (ast_test_flag(&flags, AST_FLAGS_ALL))
			ast_clear_flag_nonstd(hmd, DUNDI_HINT_DONT_ASK);

		if (map->options & DUNDI_FLAG_INTERNAL_NOPARTIAL) {
			/* Skip partial answers */
			ast_clear_flag(&flags, DUNDI_FLAG_MATCHMORE|DUNDI_FLAG_CANMATCH);
		}
		if (ast_test_flag(&flags, AST_FLAGS_ALL)) {
			struct varshead headp;
			struct ast_var_t *newvariable;
			ast_set_flag(&flags, map->options & 0xffff);
			ast_copy_flags(dr + anscnt, &flags, AST_FLAGS_ALL);
			dr[anscnt].techint = map->tech;
			dr[anscnt].expiration = dundi_cache_time;
			ast_copy_string(dr[anscnt].tech, tech2str(map->tech), sizeof(dr[anscnt].tech));
			dr[anscnt].eid = *us_eid;
			ast_eid_to_str(dr[anscnt].eid_str, sizeof(dr[anscnt].eid_str), &dr[anscnt].eid);
			if (ast_test_flag(&flags, DUNDI_FLAG_EXISTS)) {
				AST_LIST_HEAD_INIT_NOLOCK(&headp);
				if ((newvariable = ast_var_assign("NUMBER", called_number))) {
					AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
				}
				if ((newvariable = ast_var_assign("EID", dr[anscnt].eid_str))) {
					AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
				}
				if ((newvariable = ast_var_assign("SECRET", cursecret))) {
					AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
				}
				if ((newvariable = ast_var_assign("IPADDR", ipaddr))) {
					AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
				}
				pbx_substitute_variables_varshead(&headp, map->dest, dr[anscnt].dest, sizeof(dr[anscnt].dest));
				dr[anscnt].weight = get_mapping_weight(map, &headp);
				while ((newvariable = AST_LIST_REMOVE_HEAD(&headp, entries)))
					ast_var_delete(newvariable);
			} else {
				dr[anscnt].dest[0] = '\0';
				dr[anscnt].weight = get_mapping_weight(map, NULL);
			}
			anscnt++;
		} else {
			/* No answers...  Find the fewest number of digits from the
			   number for which we have no answer. */
			char tmp[AST_MAX_EXTENSION + 1] = "";
			for (x = 0; x < (sizeof(tmp) - 1); x++) {
				tmp[x] = called_number[x];
				if (!tmp[x])
					break;
				if (!ast_canmatch_extension(NULL, map->lcontext, tmp, 1, NULL)) {
					/* Oops found something we can't match.  If this is longer
					   than the running hint, we have to consider it */
					if (strlen(tmp) > strlen(hmd->exten)) {
						ast_copy_string(hmd->exten, tmp, sizeof(hmd->exten));
					}
					break;
				}
			}
		}
	}
	return anscnt;
}

static void destroy_trans(struct dundi_transaction *trans, int fromtimeout);

static void *dundi_lookup_thread(void *data)
{
	struct dundi_query_state *st = data;
	struct dundi_result dr[MAX_RESULTS];
	struct dundi_ie_data ied;
	struct dundi_hint_metadata hmd;
	char eid_str[20];
	int res, x;
	int ouranswers=0;
	int max = 999999;
	int expiration = dundi_cache_time;

	ast_debug(1, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
			st->eids[0] ? ast_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) :  "ourselves");
	memset(&ied, 0, sizeof(ied));
	memset(&dr, 0, sizeof(dr));
	memset(&hmd, 0, sizeof(hmd));
	/* Assume 'don't ask for anything' and 'unaffected', no TTL expired */
	hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
	for (x=0;x<st->nummaps;x++)
		ouranswers = dundi_lookup_local(dr, st->maps + x, st->called_number, &st->trans->us_eid, ouranswers, &hmd);
	if (ouranswers < 0)
		ouranswers = 0;
	for (x=0;x<ouranswers;x++) {
		if (dr[x].weight < max)
			max = dr[x].weight;
	}

	if (max) {
		/* If we do not have a canonical result, keep looking */
		res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, st->called_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->nocache, 0, NULL, st->eids, st->directs);
		if (res > 0) {
			/* Append answer in result */
			ouranswers += res;
		} else {
			if ((res < -1) && (!ouranswers))
				dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_DUPLICATE, "Duplicate Request Pending");
		}
	}
	AST_LIST_LOCK(&peers);
	/* Truncate if "don't ask" isn't present */
	if (!ast_test_flag_nonstd(&hmd, DUNDI_HINT_DONT_ASK))
		hmd.exten[0] = '\0';
	if (ast_test_flag(st->trans, FLAG_DEAD)) {
		ast_debug(1, "Our transaction went away!\n");
		st->trans->thread = 0;
		destroy_trans(st->trans, 0);
	} else {
		for (x=0;x<ouranswers;x++) {
			/* Add answers */
			if (dr[x].expiration && (expiration > dr[x].expiration))
				expiration = dr[x].expiration;
			dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
		}
		dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
		dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
		dundi_send(st->trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
		st->trans->thread = 0;
	}
	AST_LIST_UNLOCK(&peers);
	ast_free(st);
	return NULL;
}

static void *dundi_precache_thread(void *data)
{
	struct dundi_query_state *st = data;
	struct dundi_ie_data ied;
	struct dundi_hint_metadata hmd = {0};
	char eid_str[20];

	ast_debug(1, "Whee, precaching '%s@%s' for '%s'\n", st->called_number, st->called_context,
		st->eids[0] ? ast_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) :  "ourselves");
	memset(&ied, 0, sizeof(ied));

	/* Now produce precache */
	dundi_precache_internal(st->called_context, st->called_number, st->ttl, st->eids);

	AST_LIST_LOCK(&peers);
	/* Truncate if "don't ask" isn't present */
	if (!ast_test_flag_nonstd(&hmd, DUNDI_HINT_DONT_ASK))
		hmd.exten[0] = '\0';
	if (ast_test_flag(st->trans, FLAG_DEAD)) {
		ast_debug(1, "Our transaction went away!\n");
		st->trans->thread = 0;
		destroy_trans(st->trans, 0);
	} else {
		dundi_send(st->trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
		st->trans->thread = 0;
	}
	AST_LIST_UNLOCK(&peers);
	ast_free(st);
	return NULL;
}

static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *dcontext, dundi_eid *eid, struct dundi_hint_metadata *hmd, int ttl, int blockempty, dundi_eid *avoid[]);

static void *dundi_query_thread(void *data)
{
	struct dundi_query_state *st = data;
	struct dundi_entity_info dei;
	struct dundi_ie_data ied;
	struct dundi_hint_metadata hmd;
	char eid_str[20];
	int res;

	ast_debug(1, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
		st->eids[0] ? ast_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) :  "ourselves");
	memset(&ied, 0, sizeof(ied));
	memset(&dei, 0, sizeof(dei));
	memset(&hmd, 0, sizeof(hmd));
	if (!ast_eid_cmp(&st->trans->us_eid, &st->reqeid)) {
		/* Ooh, it's us! */
		ast_debug(1, "Neat, someone look for us!\n");
		ast_copy_string(dei.orgunit, dept, sizeof(dei.orgunit));
		ast_copy_string(dei.org, org, sizeof(dei.org));
		ast_copy_string(dei.locality, locality, sizeof(dei.locality));
		ast_copy_string(dei.stateprov, stateprov, sizeof(dei.stateprov));
		ast_copy_string(dei.country, country, sizeof(dei.country));
		ast_copy_string(dei.email, email, sizeof(dei.email));
		ast_copy_string(dei.phone, phone, sizeof(dei.phone));
		res = 1;
	} else {
		/* If we do not have a canonical result, keep looking */
		res = dundi_query_eid_internal(&dei, st->called_context, &st->reqeid, &hmd, st->ttl, 1, st->eids);
	}
	AST_LIST_LOCK(&peers);
	if (ast_test_flag(st->trans, FLAG_DEAD)) {
		ast_debug(1, "Our transaction went away!\n");
		st->trans->thread = 0;
		destroy_trans(st->trans, 0);
	} else {
		if (res) {
			dundi_ie_append_str(&ied, DUNDI_IE_DEPARTMENT, dei.orgunit);
			dundi_ie_append_str(&ied, DUNDI_IE_ORGANIZATION, dei.org);
			dundi_ie_append_str(&ied, DUNDI_IE_LOCALITY, dei.locality);
			dundi_ie_append_str(&ied, DUNDI_IE_STATE_PROV, dei.stateprov);
			dundi_ie_append_str(&ied, DUNDI_IE_COUNTRY, dei.country);
			dundi_ie_append_str(&ied, DUNDI_IE_EMAIL, dei.email);
			dundi_ie_append_str(&ied, DUNDI_IE_PHONE, dei.phone);
			if (!ast_strlen_zero(dei.ipaddr))
				dundi_ie_append_str(&ied, DUNDI_IE_IPADDR, dei.ipaddr);
		}
		dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
		dundi_send(st->trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
		st->trans->thread = 0;
	}
	AST_LIST_UNLOCK(&peers);
	ast_free(st);
	return NULL;
}

static int dundi_answer_entity(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
{
	struct dundi_query_state *st;
	int totallen;
	int x;
	int skipfirst=0;
	char eid_str[20];
	char *s;
	pthread_t lookupthread;

	if (ies->eidcount > 1) {
		/* Since it is a requirement that the first EID is the authenticating host
		   and the last EID is the root, it is permissible that the first and last EID
		   could be the same.  In that case, we should go ahead copy only the "root" section
		   since we will not need it for authentication. */
		if (!ast_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
			skipfirst = 1;
	}
	totallen = sizeof(struct dundi_query_state);
	totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
	st = ast_calloc(1, totallen);
	if (st) {
		ast_copy_string(st->called_context, ies->called_context, sizeof(st->called_context));
		memcpy(&st->reqeid, ies->reqeid, sizeof(st->reqeid));
		st->trans = trans;
		st->ttl = ies->ttl - 1;
		if (st->ttl < 0)
			st->ttl = 0;
		s = st->fluffy;
		for (x=skipfirst;ies->eids[x];x++) {
			st->eids[x-skipfirst] = (dundi_eid *)s;
			*st->eids[x-skipfirst] = *ies->eids[x];
			s += sizeof(dundi_eid);
		}
		ast_debug(1, "Answering EID query for '%s@%s'!\n", ast_eid_to_str(eid_str, sizeof(eid_str), ies->reqeid), ies->called_context);

		trans->thread = 1;
		if (ast_pthread_create_detached(&lookupthread, NULL, dundi_query_thread, st)) {
			struct dundi_ie_data ied = { 0, };
			trans->thread = 0;
			ast_log(LOG_WARNING, "Unable to create thread!\n");
			ast_free(st);
			dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
			dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
			return -1;
		}
	} else {
		struct dundi_ie_data ied = { 0, };
		dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
		dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
		return -1;
	}
	return 0;
}

static int cache_save_hint(dundi_eid *eidpeer, struct dundi_request *req, struct dundi_hint *hint, int expiration)
{
	int unaffected;
	char key1[256];
	char key2[256];
	char eidpeer_str[20];
	char eidroot_str[20];
	char data[80];
	time_t timeout;

	if (expiration < 0)
		expiration = dundi_cache_time;

	/* Only cache hint if "don't ask" is there... */
	if (!ast_test_flag_nonstd(hint, htons(DUNDI_HINT_DONT_ASK)))
		return 0;

	unaffected = ast_test_flag_nonstd(hint, htons(DUNDI_HINT_UNAFFECTED));

	dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
	dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
	snprintf(key1, sizeof(key1), "hint/%s/%s/%s/e%08x", eidpeer_str, hint->data, req->dcontext, unaffected ? 0 : req->crc32);
	snprintf(key2, sizeof(key2), "hint/%s/%s/%s/r%s", eidpeer_str, hint->data, req->dcontext, eidroot_str);

	time(&timeout);
	timeout += expiration;
	snprintf(data, sizeof(data), "%ld|", (long)(timeout));

	ast_db_put("dundi/cache", key1, data);
	ast_debug(1, "Caching hint at '%s'\n", key1);
	ast_db_put("dundi/cache", key2, data);
	ast_debug(1, "Caching hint at '%s'\n", key2);
	return 0;
}

static int cache_save(dundi_eid *eidpeer, struct dundi_request *req, int start, int unaffected, int expiration, int push)
{
	int x;
	char key1[256];
	char key2[256];
	char data[1024];
	char eidpeer_str[20];
	char eidroot_str[20];
	time_t timeout;

	if (expiration < 1)
		expiration = dundi_cache_time;

	/* Keep pushes a little longer, cut pulls a little short */
	if (push)
		expiration += 10;
	else
		expiration -= 10;
	if (expiration < 1)
		expiration = 1;
	dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
	dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
	snprintf(key1, sizeof(key1), "%s/%s/%s/e%08x", eidpeer_str, req->number, req->dcontext, unaffected ? 0 : req->crc32);
	snprintf(key2, sizeof(key2), "%s/%s/%s/r%s", eidpeer_str, req->number, req->dcontext, eidroot_str);
	/* Build request string */
	time(&timeout);
	timeout += expiration;
	snprintf(data, sizeof(data), "%ld|", (long)(timeout));
	for (x=start;x<req->respcount;x++) {
		/* Skip anything with an illegal pipe in it */
		if (strchr(req->dr[x].dest, '|'))
			continue;
		snprintf(data + strlen(data), sizeof(data) - strlen(data), "%u/%d/%d/%s/%s|",
			req->dr[x].flags, req->dr[x].weight, req->dr[x].techint, req->dr[x].dest,
			dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), &req->dr[x].eid));
	}
	ast_db_put("dundi/cache", key1, data);
	ast_db_put("dundi/cache", key2, data);
	return 0;
}

static int dundi_prop_precache(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
{
	struct dundi_query_state *st;
	int totallen;
	int x,z;
	struct dundi_ie_data ied;
	char *s;
	struct dundi_result dr2[MAX_RESULTS];
	struct dundi_request dr;
	struct dundi_hint_metadata hmd;

	struct dundi_mapping *cur;
	int mapcount;
	int skipfirst = 0;

	pthread_t lookupthread;

	memset(&dr2, 0, sizeof(dr2));
	memset(&dr, 0, sizeof(dr));
	memset(&hmd, 0, sizeof(hmd));

	/* Forge request structure to hold answers for cache */
	hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
	dr.dr = dr2;
	dr.maxcount = MAX_RESULTS;
	dr.expiration = dundi_cache_time;
	dr.hmd = &hmd;
	dr.pfds[0] = dr.pfds[1] = -1;
	trans->parent = &dr;
	ast_copy_string(dr.dcontext, ies->called_context ? ies->called_context : "e164", sizeof(dr.dcontext));
	ast_copy_string(dr.number, ies->called_number, sizeof(dr.number));

	for (x=0;x<ies->anscount;x++) {
		if (trans->parent->respcount < trans->parent->maxcount) {
			/* Make sure it's not already there */
			for (z=0;z<trans->parent->respcount;z++) {
				if ((trans->parent->dr[z].techint == ies->answers[x]->protocol) &&
				    !strcmp(trans->parent->dr[z].dest, (char *)ies->answers[x]->data))
						break;
			}
			if (z == trans->parent->respcount) {
				/* Copy into parent responses */
				trans->parent->dr[trans->parent->respcount].flags = ntohs(ies->answers[x]->flags);
				trans->parent->dr[trans->parent->respcount].techint = ies->answers[x]->protocol;
				trans->parent->dr[trans->parent->respcount].weight = ntohs(ies->answers[x]->weight);
				trans->parent->dr[trans->parent->respcount].eid = ies->answers[x]->eid;
				if (ies->expiration > 0)
					trans->parent->dr[trans->parent->respcount].expiration = ies->expiration;
				else
					trans->parent->dr[trans->parent->respcount].expiration = dundi_cache_time;
				ast_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str,
					sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
					&ies->answers[x]->eid);
				ast_copy_string(trans->parent->dr[trans->parent->respcount].dest, (char *)ies->answers[x]->data,
					sizeof(trans->parent->dr[trans->parent->respcount].dest));
					ast_copy_string(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies->answers[x]->protocol),
					sizeof(trans->parent->dr[trans->parent->respcount].tech));
				trans->parent->respcount++;
				ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
			} else if (trans->parent->dr[z].weight > ntohs(ies->answers[x]->weight)) {
				/* Update weight if appropriate */
				trans->parent->dr[z].weight = ntohs(ies->answers[x]->weight);
			}
		} else
			ast_log(LOG_NOTICE, "Dropping excessive answers in precache for %s@%s\n",
				trans->parent->number, trans->parent->dcontext);

	}
	/* Save all the results (if any) we had.  Even if no results, still cache lookup. */
	cache_save(&trans->them_eid, trans->parent, 0, 0, ies->expiration, 1);
	if (ies->hint)
		cache_save_hint(&trans->them_eid, trans->parent, ies->hint, ies->expiration);

	totallen = sizeof(struct dundi_query_state);
	/* Count matching map entries */
	mapcount = 0;
	AST_LIST_TRAVERSE(&mappings, cur, list) {
		if (!strcasecmp(cur->dcontext, ccontext))
			mapcount++;
	}

	/* If no maps, return -1 immediately */
	if (!mapcount)
		return -1;

	if (ies->eidcount > 1) {
		/* Since it is a requirement that the first EID is the authenticating host
		   and the last EID is the root, it is permissible that the first and last EID
		   could be the same.  In that case, we should go ahead copy only the "root" section
		   since we will not need it for authentication. */
		if (!ast_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
			skipfirst = 1;
	}

	/* Prepare to run a query and then propagate that as necessary */
	totallen += mapcount * sizeof(struct dundi_mapping);
	totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
	st = ast_calloc(1, totallen);
	if (st) {
		ast_copy_string(st->called_context, dr.dcontext, sizeof(st->called_context));
		ast_copy_string(st->called_number, ies->called_number, sizeof(st->called_number));
		st->trans = trans;
		st->ttl = ies->ttl - 1;
		st->nocache = ies->cbypass;
		if (st->ttl < 0)
			st->ttl = 0;
		s = st->fluffy;
		for (x=skipfirst;ies->eids[x];x++) {
			st->eids[x-skipfirst] = (dundi_eid *)s;
			*st->eids[x-skipfirst] = *ies->eids[x];
			st->directs[x-skipfirst] = ies->eid_direct[x];
			s += sizeof(dundi_eid);
		}
		/* Append mappings */
		x = 0;
		st->maps = (struct dundi_mapping *)s;
		AST_LIST_TRAVERSE(&mappings, cur, list) {
			if (!strcasecmp(cur->dcontext, ccontext)) {
				if (x < mapcount) {
					st->maps[x] = *cur;
					st->maps[x].list.next = NULL;
					x++;
				}
			}
		}
		st->nummaps = mapcount;
		ast_debug(1, "Forwarding precache for '%s@%s'!\n", ies->called_number, ies->called_context);
		trans->thread = 1;
		if (ast_pthread_create_detached(&lookupthread, NULL, dundi_precache_thread, st)) {
			trans->thread = 0;
			ast_log(LOG_WARNING, "Unable to create thread!\n");
			ast_free(st);
			memset(&ied, 0, sizeof(ied));
			dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
			dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
			return -1;
		}
	} else {
		ast_log(LOG_WARNING, "Out of memory!\n");
		memset(&ied, 0, sizeof(ied));
		dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
		dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
		return -1;
	}
	return 0;
}

static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
{
	struct dundi_query_state *st;
	int totallen;
	int x;
	struct dundi_ie_data ied;
	char *s;
	struct dundi_mapping *cur;
	int mapcount = 0;
	int skipfirst = 0;

	pthread_t lookupthread;
	totallen = sizeof(struct dundi_query_state);
	/* Count matching map entries */
	AST_LIST_TRAVERSE(&mappings, cur, list) {
		if (!strcasecmp(cur->dcontext, ccontext))
			mapcount++;
	}
	/* If no maps, return -1 immediately */
	if (!mapcount)
		return -1;

	if (ies->eidcount > 1) {
		/* Since it is a requirement that the first EID is the authenticating host
		   and the last EID is the root, it is permissible that the first and last EID
		   could be the same.  In that case, we should go ahead copy only the "root" section
		   since we will not need it for authentication. */
		if (!ast_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
			skipfirst = 1;
	}

	totallen += mapcount * sizeof(struct dundi_mapping);
	totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
	st = ast_calloc(1, totallen);
	if (st) {
		ast_copy_string(st->called_context, ies->called_context, sizeof(st->called_context));
		ast_copy_string(st->called_number, ies->called_number, sizeof(st->called_number));
		st->trans = trans;
		st->ttl = ies->ttl - 1;
		st->nocache = ies->cbypass;
		if (st->ttl < 0)
			st->ttl = 0;
		s = st->fluffy;
		for (x=skipfirst;ies->eids[x];x++) {
			st->eids[x-skipfirst] = (dundi_eid *)s;
			*st->eids[x-skipfirst] = *ies->eids[x];
			st->directs[x-skipfirst] = ies->eid_direct[x];
			s += sizeof(dundi_eid);
		}
		/* Append mappings */
		x = 0;
		st->maps = (struct dundi_mapping *)s;
		AST_LIST_TRAVERSE(&mappings, cur, list) {
			if (!strcasecmp(cur->dcontext, ccontext)) {
				if (x < mapcount) {
					st->maps[x] = *cur;
					st->maps[x].list.next = NULL;
					x++;
				}
			}
		}
		st->nummaps = mapcount;
		ast_debug(1, "Answering query for '%s@%s'!\n", ies->called_number, ies->called_context);
		trans->thread = 1;
		if (ast_pthread_create_detached(&lookupthread, NULL, dundi_lookup_thread, st)) {
			trans->thread = 0;
			ast_log(LOG_WARNING, "Unable to create thread!\n");
			ast_free(st);
			memset(&ied, 0, sizeof(ied));
			dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
			dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
			return -1;
		}
	} else {
		ast_log(LOG_WARNING, "Out of memory!\n");
		memset(&ied, 0, sizeof(ied));
		dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
		dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
		return -1;
	}
	return 0;
}

static int cache_lookup_internal(time_t now, struct dundi_request *req, char *key, char *eid_str_full, int *lowexpiration)
{
	char data[1024];
	char *ptr, *term, *src;
	int tech;
	struct ast_flags flags;
	int weight;
	int length;
	int z;
	char fs[256];

	/* Build request string */
	if (!ast_db_get("dundi/cache", key, data, sizeof(data))) {
		time_t timeout;
		ptr = data;
		if (!ast_get_time_t(ptr, &timeout, 0, &length)) {
			int expiration = timeout - now;
			if (expiration > 0) {
				ast_debug(1, "Found cache expiring in %d seconds!\n", expiration);
				ptr += length + 1;
				while((sscanf(ptr, "%30d/%30d/%30d/%n", (int *)&(flags.flags), &weight, &tech, &length) == 3)) {
					ptr += length;
					term = strchr(ptr, '|');
					if (term) {
						*term = '\0';
						src = strrchr(ptr, '/');
						if (src) {
							*src = '\0';
							src++;
						} else
							src = "";
						ast_debug(1, "Found cached answer '%s/%s' originally from '%s' with flags '%s' on behalf of '%s'\n",
							tech2str(tech), ptr, src, dundi_flags2str(fs, sizeof(fs), flags.flags), eid_str_full);
						/* Make sure it's not already there */
						for (z=0;z<req->respcount;z++) {
							if ((req->dr[z].techint == tech) &&
							    !strcmp(req->dr[z].dest, ptr))
									break;
						}
						if (z == req->respcount) {
							/* Copy into parent responses */
							ast_copy_flags(&(req->dr[req->respcount]), &flags, AST_FLAGS_ALL);
							req->dr[req->respcount].weight = weight;
							req->dr[req->respcount].techint = tech;
							req->dr[req->respcount].expiration = expiration;
							dundi_str_short_to_eid(&req->dr[req->respcount].eid, src);
							ast_eid_to_str(req->dr[req->respcount].eid_str,
								sizeof(req->dr[req->respcount].eid_str), &req->dr[req->respcount].eid);
							ast_copy_string(req->dr[req->respcount].dest, ptr,
								sizeof(req->dr[req->respcount].dest));
							ast_copy_string(req->dr[req->respcount].tech, tech2str(tech),
								sizeof(req->dr[req->respcount].tech));
							req->respcount++;
							ast_clear_flag_nonstd(req->hmd, DUNDI_HINT_DONT_ASK);
						} else if (req->dr[z].weight > weight)
							req->dr[z].weight = weight;
						ptr = term + 1;
					}
				}
				/* We found *something* cached */
				if (expiration < *lowexpiration)
					*lowexpiration = expiration;
				return 1;
			} else
				ast_db_del("dundi/cache", key);
		} else
			ast_db_del("dundi/cache", key);
	}

	return 0;
}

static int cache_lookup(struct dundi_request *req, dundi_eid *peer_eid, uint32_t crc, int *lowexpiration)
{
	char eid_str[20];
	char eidroot_str[20];
	time_t now;
	int res=0;
	int res2=0;
	char eid_str_full[20];
	char tmp[256]="";
	/* Enough space for largest value that can be stored in key. */
	char key[sizeof(eid_str) + sizeof(tmp) + sizeof(req->dcontext) + sizeof(eidroot_str) + sizeof("hint////r")];
	int x;

	time(&now);
	dundi_eid_to_str_short(eid_str, sizeof(eid_str), peer_eid);
	dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
	ast_eid_to_str(eid_str_full, sizeof(eid_str_full), peer_eid);
	snprintf(key, sizeof(key), "%s/%s/%s/e%08x", eid_str, req->number, req->dcontext, crc);
	res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
	snprintf(key, sizeof(key), "%s/%s/%s/e%08x", eid_str, req->number, req->dcontext, (unsigned)0);
	res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
	snprintf(key, sizeof(key), "%s/%s/%s/r%s", eid_str, req->number, req->dcontext, eidroot_str);
	res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
	x = 0;
	if (!req->respcount) {
		while(!res2) {
			/* Look and see if we have a hint that would preclude us from looking at this
			   peer for this number. */
			if (!(tmp[x] = req->number[x]))
				break;
			x++;
			/* Check for hints */
			snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08x", eid_str, tmp, req->dcontext, crc);
			res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
			snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08x", eid_str, tmp, req->dcontext, (unsigned)0);
			res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
			snprintf(key, sizeof(key), "hint/%s/%s/%s/r%s", eid_str, tmp, req->dcontext, eidroot_str);
			res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
			if (res2) {
				if (strlen(tmp) > strlen(req->hmd->exten)) {
					/* Update meta data if appropriate */
					ast_copy_string(req->hmd->exten, tmp, sizeof(req->hmd->exten));
				}
			}
		}
		res |= res2;
	}

	return res;
}

static void qualify_peer(struct dundi_peer *peer, int schedonly);

static void apply_peer(struct dundi_transaction *trans, struct dundi_peer *p)
{
	if (ast_sockaddr_isnull(&trans->addr)) {
		ast_sockaddr_copy(&trans->addr, &p->addr);
	}
	trans->us_eid = p->us_eid;
	trans->them_eid = p->eid;
	/* Enable encryption if appropriate */
	if (!ast_strlen_zero(p->inkey))
		ast_set_flag(trans, FLAG_ENCRYPT);
	if (p->maxms) {
		trans->autokilltimeout = p->maxms;
		trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
		if (p->lastms > 1) {
			trans->retranstimer = p->lastms * 2;
			/* Keep it from being silly */
			if (trans->retranstimer < 150)
				trans->retranstimer = 150;
		}
		if (trans->retranstimer > DUNDI_DEFAULT_RETRANS_TIMER)
			trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
	} else
		trans->autokilltimeout = global_autokilltimeout;
}

/*! \note Called with the peers list already locked */
static int do_register_expire(const void *data)
{
	struct dundi_peer *peer = (struct dundi_peer *)data;
	char eid_str[20];

	ast_debug(1, "Register expired for '%s'\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
	ast_db_del("dundi/dpeers", dundi_eid_to_str_short(eid_str, sizeof(eid_str), &peer->eid));
	peer->registerexpire = -1;
	peer->lastms = 0;
	ast_sockaddr_setnull(&peer->addr);
	return 0;
}

static int update_key(struct dundi_peer *peer)
{
	unsigned char key[16];
	struct ast_key *ekey, *skey;
	char eid_str[20];
	int res;
	if (!peer->keyexpire || (peer->keyexpire < time(NULL))) {
		build_iv(key);
		ast_aes_set_encrypt_key(key, &peer->us_ecx);
		ast_aes_set_decrypt_key(key, &peer->us_dcx);
		ekey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
		if (!ekey) {
			ast_log(LOG_NOTICE, "No such key '%s' for creating RSA encrypted shared key for '%s'!\n",
				peer->inkey, ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
			return -1;
		}
		skey = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
		if (!skey) {
			ast_log(LOG_NOTICE, "No such key '%s' for signing RSA encrypted shared key for '%s'!\n",
				peer->outkey, ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
			return -1;
		}
		if ((res = ast_encrypt_bin(peer->txenckey, key, sizeof(key), ekey)) != 128) {
			ast_log(LOG_NOTICE, "Whoa, got a weird encrypt size (%d != %d)!\n", res, 128);
			return -1;
		}
		if ((res = ast_sign_bin(skey, (char *)peer->txenckey, 128, peer->txenckey + 128))) {
			ast_log(LOG_NOTICE, "Failed to sign key (%d)!\n", res);
			return -1;
		}
		peer->us_keycrc32 = crc32(0L, peer->txenckey, 128);
		peer->sentfullkey = 0;
		/* Looks good */
		time(&peer->keyexpire);
		peer->keyexpire += dundi_key_ttl;
	}
	return 0;
}

static int encrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, ast_aes_encrypt_key *ecx)
{
	unsigned char curblock[16];
	int x;
	memcpy(curblock, iv, sizeof(curblock));
	while(len > 0) {
		for (x=0;x<16;x++)
			curblock[x] ^= src[x];
		ast_aes_encrypt(curblock, dst, ecx);
		memcpy(curblock, dst, sizeof(curblock));
		dst += 16;
		src += 16;
		len -= 16;
	}
	return 0;
}
static int decrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, ast_aes_decrypt_key *dcx)
{
	unsigned char lastblock[16];
	int x;
	memcpy(lastblock, iv, sizeof(lastblock));
	while(len > 0) {
		ast_aes_decrypt(src, dst, dcx);
		for (x=0;x<16;x++)
			dst[x] ^= lastblock[x];
		memcpy(lastblock, src, sizeof(lastblock));
		dst += 16;
		src += 16;
		len -= 16;
	}
	return 0;
}

static struct dundi_hdr *dundi_decrypt(struct dundi_transaction *trans, unsigned char *dst, int *dstlen, struct dundi_hdr *ohdr, struct dundi_encblock *src, int srclen)
{
	int space = *dstlen;
	unsigned long bytes;
	struct dundi_hdr *h;
	unsigned char *decrypt_space;
	decrypt_space = ast_alloca(srclen);
	decrypt_memcpy(decrypt_space, src->encdata, srclen, src->iv, &trans->dcx);
	/* Setup header */
	h = (struct dundi_hdr *)dst;
	*h = *ohdr;
	bytes = space - 6;
	if (uncompress(dst + 6, &bytes, decrypt_space, srclen) != Z_OK) {
		ast_debug(1, "Ouch, uncompress failed :(\n");
		return NULL;
	}
	/* Update length */
	*dstlen = bytes + 6;
	/* Return new header */
	return h;
}

static int dundi_encrypt(struct dundi_transaction *trans, struct dundi_packet *pack)
{
	unsigned char *compress_space;
	int len;
	int res;
	unsigned long bytes;
	struct dundi_ie_data ied;
	struct dundi_peer *peer;
	unsigned char iv[16];
	len = pack->datalen + pack->datalen / 100 + 42;
	compress_space = ast_alloca(len);
	memset(compress_space, 0, len);
	/* We care about everthing save the first 6 bytes of header */
	bytes = len;
	res = compress(compress_space, &bytes, pack->data + 6, pack->datalen - 6);
	if (res != Z_OK) {
		ast_debug(1, "Ouch, compression failed!\n");
		return -1;
	}
	memset(&ied, 0, sizeof(ied));
	/* Say who we are */
	if (!pack->h->iseqno && !pack->h->oseqno) {
		/* Need the key in the first copy */
		if (!(peer = find_peer(&trans->them_eid)))
			return -1;
		if (update_key(peer))
			return -1;
		if (!peer->sentfullkey)
			ast_set_flag(trans, FLAG_SENDFULLKEY);
		/* Append key data */
		dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
		if (ast_test_flag(trans, FLAG_SENDFULLKEY)) {
			dundi_ie_append_raw(&ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
			dundi_ie_append_raw(&ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
		} else {
			dundi_ie_append_int(&ied, DUNDI_IE_KEYCRC32, peer->us_keycrc32);
		}
		/* Setup contexts */
		trans->ecx = peer->us_ecx;
		trans->dcx = peer->us_dcx;

		/* We've sent the full key */
		peer->sentfullkey = 1;
	}
	/* Build initialization vector */
	build_iv(iv);
	/* Add the field, rounded up to 16 bytes */
	dundi_ie_append_encdata(&ied, DUNDI_IE_ENCDATA, iv, NULL, ((bytes + 15) / 16) * 16);
	/* Copy the data */
	if ((ied.pos + bytes) >= sizeof(ied.buf)) {
		ast_log(LOG_NOTICE, "Final packet too large!\n");
		return -1;
	}
	encrypt_memcpy(ied.buf + ied.pos, compress_space, bytes, iv, &trans->ecx);
	ied.pos += ((bytes + 15) / 16) * 16;
	/* Reconstruct header */
	pack->datalen = sizeof(struct dundi_hdr);
	pack->h->cmdresp = DUNDI_COMMAND_ENCRYPT;
	pack->h->cmdflags = 0;
	memcpy(pack->h->ies, ied.buf, ied.pos);
	pack->datalen += ied.pos;
	return 0;
}

static int check_key(struct dundi_peer *peer, unsigned char *newkey, unsigned char *newsig, uint32_t keycrc32)
{
	unsigned char dst[128];
	int res;
	struct ast_key *key, *skey;
	char eid_str[20];
	ast_debug(1, "Expected '%08x' got '%08x'\n", peer->them_keycrc32, keycrc32);
	if (peer->them_keycrc32 && (peer->them_keycrc32 == keycrc32)) {
		/* A match */
		return 1;
	} else if (!newkey || !newsig)
		return 0;
	if (!memcmp(peer->rxenckey, newkey, 128) &&
	    !memcmp(peer->rxenckey + 128, newsig, 128)) {
		/* By definition, a match */
		return 1;
	}
	/* Decrypt key */
	key = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
	if (!key) {
		ast_log(LOG_NOTICE, "Unable to find key '%s' to decode shared key from '%s'\n",
			peer->outkey, ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		return -1;
	}

	skey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
	if (!skey) {
		ast_log(LOG_NOTICE, "Unable to find key '%s' to verify shared key from '%s'\n",
			peer->inkey, ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		return -1;
	}

	/* First check signature */
	res = ast_check_signature_bin(skey, (char *)newkey, 128, newsig);
	if (res)
		return 0;

	res = ast_decrypt_bin(dst, newkey, sizeof(dst), key);
	if (res != 16) {
		if (res >= 0)
			ast_log(LOG_NOTICE, "Weird, key decoded to the wrong size (%d)\n", res);
		return 0;
	}
	/* Decrypted, passes signature */
	ast_debug(1, "Wow, new key combo passed signature and decrypt!\n");
	memcpy(peer->rxenckey, newkey, 128);
	memcpy(peer->rxenckey + 128, newsig, 128);
	peer->them_keycrc32 = crc32(0L, peer->rxenckey, 128);
	ast_aes_set_decrypt_key(dst, &peer->them_dcx);
	ast_aes_set_encrypt_key(dst, &peer->them_ecx);
	return 1;
}

static void deep_copy_peer(struct dundi_peer *peer_dst, const struct dundi_peer *peer_src)
{
	struct permission *cur, *perm;

	*peer_dst = *peer_src;
	AST_LIST_NEXT(peer_dst, list) = NULL;

	/* Scheduled items cannot go with the copy */
	peer_dst->registerid = -1;
	peer_dst->qualifyid = -1;
	peer_dst->registerexpire = -1;

	/* Transactions and lookup history cannot go with the copy either */
	peer_dst->regtrans = NULL;
	peer_dst->qualtrans = NULL;
	memset(&peer_dst->lookups, 0, sizeof(peer_dst->lookups));

	memset(&peer_dst->permit, 0, sizeof(peer_dst->permit));
	memset(&peer_dst->include, 0, sizeof(peer_dst->permit));

	AST_LIST_TRAVERSE(&peer_src->permit, cur, list) {
		if (!(perm = ast_calloc(1, sizeof(*perm) + strlen(cur->name) + 1)))
			continue;

		perm->allow = cur->allow;
		strcpy(perm->name, cur->name);

		AST_LIST_INSERT_HEAD(&peer_dst->permit, perm, list);
	}

	AST_LIST_TRAVERSE(&peer_src->include, cur, list) {
		if (!(perm = ast_calloc(1, sizeof(*perm) + strlen(cur->name) + 1)))
			continue;

		perm->allow = cur->allow;
		strcpy(perm->name, cur->name);

		AST_LIST_INSERT_HEAD(&peer_dst->include, perm, list);
	}
}

static int handle_command_response(struct dundi_transaction *trans, struct dundi_hdr *hdr, int datalen, int encrypted)
{
	/* Handle canonical command / response */
	int final = hdr->cmdresp & 0x80;
	int cmd = hdr->cmdresp & 0x7f;
	int x,y,z;
	int resp;
	int res;
	int authpass=0;
	unsigned char *bufcpy;
#ifdef LOW_MEMORY
	struct dundi_ie_data *ied = ast_calloc(1, sizeof(*ied));
#else
	struct dundi_ie_data _ied = {
		.pos = 0,
	};
	struct dundi_ie_data *ied = &_ied;
#endif
	struct dundi_ies ies = {
		.eidcount = 0,
	};
	struct dundi_peer *peer = NULL;
	char eid_str[20];
	char eid_str2[20];
	int retval = -1;

	if (!ied) {
		return -1;
	}

	if (datalen) {
		bufcpy = ast_alloca(datalen);
		/* Make a copy for parsing */
		memcpy(bufcpy, hdr->ies, datalen);
		ast_debug(1, "Got canonical message %d (%d), %d bytes data%s\n", cmd, hdr->oseqno, datalen, final ? " (Final)" : "");
		if (dundi_parse_ies(&ies, bufcpy, datalen) < 0) {
			ast_log(LOG_WARNING, "Failed to parse DUNDI information elements!\n");
			goto return_cleanup;
		}
	}
	switch(cmd) {
	case DUNDI_COMMAND_DPDISCOVER:
	case DUNDI_COMMAND_EIDQUERY:
	case DUNDI_COMMAND_PRECACHERQ:
		if (cmd == DUNDI_COMMAND_EIDQUERY)
			resp = DUNDI_COMMAND_EIDRESPONSE;
		else if (cmd == DUNDI_COMMAND_PRECACHERQ)
			resp = DUNDI_COMMAND_PRECACHERP;
		else
			resp = DUNDI_COMMAND_DPRESPONSE;
		/* A dialplan or entity discover -- qualify by highest level entity */
		peer = find_peer(ies.eids[0]);
		if (!peer) {
			dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
			dundi_send(trans, resp, 0, 1, ied);
		} else {
			int hasauth = 0;
			trans->us_eid = peer->us_eid;
			if (strlen(peer->inkey)) {
				hasauth = encrypted;
			} else
				hasauth = 1;
			if (hasauth) {
				/* Okay we're authentiated and all, now we check if they're authorized */
				if (!ies.called_context)
					ies.called_context = "e164";
				if (cmd == DUNDI_COMMAND_EIDQUERY) {
					res = dundi_answer_entity(trans, &ies, ies.called_context);
				} else {
					if (ast_strlen_zero(ies.called_number)) {
						/* They're not permitted to access that context */
						dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Invalid or missing number/entity");
						dundi_send(trans, resp, 0, 1, ied);
					} else if ((cmd == DUNDI_COMMAND_DPDISCOVER) &&
					           (peer->model & DUNDI_MODEL_INBOUND) &&
							   has_permission(&peer->permit, ies.called_context)) {
						res = dundi_answer_query(trans, &ies, ies.called_context);
						if (res < 0) {
							/* There is no such dundi context */
							dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
							dundi_send(trans, resp, 0, 1, ied);
						}
					} else if ((cmd = DUNDI_COMMAND_PRECACHERQ) &&
					           (peer->pcmodel & DUNDI_MODEL_INBOUND) &&
							   has_permission(&peer->include, ies.called_context)) {
						res = dundi_prop_precache(trans, &ies, ies.called_context);
						if (res < 0) {
							/* There is no such dundi context */
							dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
							dundi_send(trans, resp, 0, 1, ied);
						}
					} else {
						/* They're not permitted to access that context */
						dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Permission to context denied");
						dundi_send(trans, resp, 0, 1, ied);
					}
				}
			} else {
				/* They're not permitted to access that context */
				dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unencrypted responses not permitted");
				dundi_send(trans, resp, 0, 1, ied);
			}
		}
		break;
	case DUNDI_COMMAND_REGREQ:
		/* A register request -- should only have one entity */
		peer = find_peer(ies.eids[0]);

		/* if the peer is not found and we have a valid 'any_peer' setting */
		if (any_peer && peer == any_peer) {
			/* copy any_peer into a new peer object */
			peer = ast_calloc(1, sizeof(*peer));
			if (peer) {
				deep_copy_peer(peer, any_peer);

				/* set EID to remote EID */
				peer->eid = *ies.eids[0];

				AST_LIST_LOCK(&peers);
				AST_LIST_INSERT_HEAD(&peers, peer, list);
				AST_LIST_UNLOCK(&peers);
			}
		}

		if (!peer || !peer->dynamic) {
			dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
			dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, ied);
		} else {
			int hasauth = 0;
			trans->us_eid = peer->us_eid;
			if (!ast_strlen_zero(peer->inkey)) {
				hasauth = encrypted;
			} else
				hasauth = 1;
			if (hasauth) {
				int expire = default_expiration;
				char data[256];
				int needqual = 0;
				AST_SCHED_DEL(sched, peer->registerexpire);
				peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
				snprintf(data, sizeof(data), "%s:%d", ast_sockaddr_stringify(&trans->addr), expire);
				ast_db_put("dundi/dpeers", dundi_eid_to_str_short(eid_str, sizeof(eid_str), &peer->eid), data);
				if (ast_sockaddr_cmp(&peer->addr, &trans->addr)) {
					ast_verb(3, "Registered DUNDi peer '%s' at '%s'\n",
						ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
						ast_sockaddr_stringify(&trans->addr));
					needqual = 1;
				}

				ast_sockaddr_copy(&peer->addr, &trans->addr);
				dundi_ie_append_short(ied, DUNDI_IE_EXPIRATION, default_expiration);
				dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, ied);
				if (needqual)
					qualify_peer(peer, 1);
			}
		}
		break;
	case DUNDI_COMMAND_DPRESPONSE:
		/* A dialplan response, lets see what we got... */
		if (ies.cause < 1) {
			/* Success of some sort */
			ast_debug(1, "Looks like success of some sort (%d), %d answers\n", ies.cause, ies.anscount);
			if (ast_test_flag(trans, FLAG_ENCRYPT)) {
				authpass = encrypted;
			} else
				authpass = 1;
			if (authpass) {
				/* Pass back up answers */
				if (trans->parent && trans->parent->dr) {
					y = trans->parent->respcount;
					for (x=0;x<ies.anscount;x++) {
						if (trans->parent->respcount < trans->parent->maxcount) {
							/* Make sure it's not already there */
							for (z=0;z<trans->parent->respcount;z++) {
								if ((trans->parent->dr[z].techint == ies.answers[x]->protocol) &&
								    !strcmp(trans->parent->dr[z].dest, (char *)ies.answers[x]->data))
										break;
							}
							if (z == trans->parent->respcount) {
								/* Copy into parent responses */
								trans->parent->dr[trans->parent->respcount].flags = ntohs(ies.answers[x]->flags);
								trans->parent->dr[trans->parent->respcount].techint = ies.answers[x]->protocol;
								trans->parent->dr[trans->parent->respcount].weight = ntohs(ies.answers[x]->weight);
								trans->parent->dr[trans->parent->respcount].eid = ies.answers[x]->eid;
								if (ies.expiration > 0)
									trans->parent->dr[trans->parent->respcount].expiration = ies.expiration;
								else
									trans->parent->dr[trans->parent->respcount].expiration = dundi_cache_time;
								ast_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str,
									sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
									&ies.answers[x]->eid);
								ast_copy_string(trans->parent->dr[trans->parent->respcount].dest, (char *)ies.answers[x]->data,
									sizeof(trans->parent->dr[trans->parent->respcount].dest));
								ast_copy_string(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies.answers[x]->protocol),
									sizeof(trans->parent->dr[trans->parent->respcount].tech));
								trans->parent->respcount++;
								ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
							} else if (trans->parent->dr[z].weight > ntohs(ies.answers[x]->weight)) {
								/* Update weight if appropriate */
								trans->parent->dr[z].weight = ntohs(ies.answers[x]->weight);
							}
						} else
							ast_log(LOG_NOTICE, "Dropping excessive answers to request for %s@%s\n",
								trans->parent->number, trans->parent->dcontext);
					}
					/* Save all the results (if any) we had.  Even if no results, still cache lookup.  Let
					   the cache know if this request was unaffected by our entity list. */
					cache_save(&trans->them_eid, trans->parent, y,
							ies.hint ? ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_UNAFFECTED)) : 0, ies.expiration, 0);
					if (ies.hint) {
						cache_save_hint(&trans->them_eid, trans->parent, ies.hint, ies.expiration);
						if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_TTL_EXPIRED)))
							ast_set_flag_nonstd(trans->parent->hmd, DUNDI_HINT_TTL_EXPIRED);
						if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_DONT_ASK))) {
							if (strlen((char *)ies.hint->data) > strlen(trans->parent->hmd->exten)) {
								ast_copy_string(trans->parent->hmd->exten, (char *)ies.hint->data,
									sizeof(trans->parent->hmd->exten));
							}
						} else {
							ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
						}
					}
					if (ies.expiration > 0) {
						if (trans->parent->expiration > ies.expiration) {
							trans->parent->expiration = ies.expiration;
						}
					}
				}
				/* Close connection if not final */
				if (!final)
					dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}

		} else {
			/* Auth failure, check for data */
			if (!final) {
				/* Cancel if they didn't already */
				dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}
		}
		break;
	case DUNDI_COMMAND_EIDRESPONSE:
		/* A dialplan response, lets see what we got... */
		if (ies.cause < 1) {
			/* Success of some sort */
			ast_debug(1, "Looks like success of some sort (%d)\n", ies.cause);
			if (ast_test_flag(trans, FLAG_ENCRYPT)) {
				authpass = encrypted;
			} else
				authpass = 1;
			if (authpass) {
				/* Pass back up answers */
				if (trans->parent && trans->parent->dei && ies.q_org) {
					if (!trans->parent->respcount) {
						trans->parent->respcount++;
						if (ies.q_dept)
							ast_copy_string(trans->parent->dei->orgunit, ies.q_dept, sizeof(trans->parent->dei->orgunit));
						if (ies.q_org)
							ast_copy_string(trans->parent->dei->org, ies.q_org, sizeof(trans->parent->dei->org));
						if (ies.q_locality)
							ast_copy_string(trans->parent->dei->locality, ies.q_locality, sizeof(trans->parent->dei->locality));
						if (ies.q_stateprov)
							ast_copy_string(trans->parent->dei->stateprov, ies.q_stateprov, sizeof(trans->parent->dei->stateprov));
						if (ies.q_country)
							ast_copy_string(trans->parent->dei->country, ies.q_country, sizeof(trans->parent->dei->country));
						if (ies.q_email)
							ast_copy_string(trans->parent->dei->email, ies.q_email, sizeof(trans->parent->dei->email));
						if (ies.q_phone)
							ast_copy_string(trans->parent->dei->phone, ies.q_phone, sizeof(trans->parent->dei->phone));
						if (ies.q_ipaddr)
							ast_copy_string(trans->parent->dei->ipaddr, ies.q_ipaddr, sizeof(trans->parent->dei->ipaddr));
						if (!ast_eid_cmp(&trans->them_eid, &trans->parent->query_eid)) {
							/* If it's them, update our address */
							ast_copy_string(trans->parent->dei->ipaddr, ast_sockaddr_stringify_addr(&trans->addr), sizeof(trans->parent->dei->ipaddr));
						}
					}
					if (ies.hint) {
						if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_TTL_EXPIRED)))
							ast_set_flag_nonstd(trans->parent->hmd, DUNDI_HINT_TTL_EXPIRED);
					}
				}
				/* Close connection if not final */
				if (!final)
					dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}

		} else {
			/* Auth failure, check for data */
			if (!final) {
				/* Cancel if they didn't already */
				dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}
		}
		break;
	case DUNDI_COMMAND_REGRESPONSE:
		/* A dialplan response, lets see what we got... */
		if (ies.cause < 1) {
			int hasauth;
			/* Success of some sort */
			if (ast_test_flag(trans, FLAG_ENCRYPT)) {
				hasauth = encrypted;
			} else
				hasauth = 1;

			if (!hasauth) {
				ast_log(LOG_NOTICE, "Reponse to register not authorized!\n");
				if (!final) {
					dundi_ie_append_cause(ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Improper signature in answer");
					dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, ied);
				}
			} else {
				ast_debug(1, "Yay, we've registered as '%s' to '%s'\n", ast_eid_to_str(eid_str, sizeof(eid_str), &trans->us_eid),
						ast_eid_to_str(eid_str2, sizeof(eid_str2), &trans->them_eid));
				/* Close connection if not final */
				if (!final)
					dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}
		} else {
			/* Auth failure, cancel if they didn't for some reason */
			if (!final) {
				dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
			}
		}
		break;
	case DUNDI_COMMAND_INVALID:
	case DUNDI_COMMAND_NULL:
	case DUNDI_COMMAND_PRECACHERP:
		/* Do nothing special */
		if (!final)
			dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
		break;
	case DUNDI_COMMAND_ENCREJ:
		if ((ast_test_flag(trans, FLAG_SENDFULLKEY)) || AST_LIST_EMPTY(&trans->lasttrans) || !(peer = find_peer(&trans->them_eid))) {
			/* No really, it's over at this point */
			if (!final)
				dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
		} else {
			/* Send with full key */
			ast_set_flag(trans, FLAG_SENDFULLKEY);
			if (final) {
				/* Ooops, we got a final message, start by sending ACK... */
				dundi_ack(trans, hdr->cmdresp & 0x80);
				trans->aseqno = trans->iseqno;
				/* Now, we gotta create a new transaction */
				if (!reset_transaction(trans)) {
					/* Make sure handle_frame doesn't destroy us */
					hdr->cmdresp &= 0x7f;
					/* Parse the message we transmitted */
					memset(&ies, 0, sizeof(ies));
					dundi_parse_ies(&ies, (AST_LIST_FIRST(&trans->lasttrans))->h->ies, (AST_LIST_FIRST(&trans->lasttrans))->datalen - sizeof(struct dundi_hdr));
					/* Reconstruct outgoing encrypted packet */
					memset(ied, 0, sizeof(*ied));
					dundi_ie_append_eid(ied, DUNDI_IE_EID, &trans->us_eid);
					dundi_ie_append_raw(ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
					dundi_ie_append_raw(ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
					if (ies.encblock)
						dundi_ie_append_encdata(ied, DUNDI_IE_ENCDATA, ies.encblock->iv, ies.encblock->encdata, ies.enclen);
					dundi_send(trans, DUNDI_COMMAND_ENCRYPT, 0, (AST_LIST_FIRST(&trans->lasttrans))->h->cmdresp & 0x80, ied);
					peer->sentfullkey = 1;
				}
			}
		}
		break;
	case DUNDI_COMMAND_ENCRYPT:
		if (!encrypted) {
			/* No nested encryption! */
			if ((trans->iseqno == 1) && !trans->oseqno) {
				if (!ies.eids[0] || !(peer = find_peer(ies.eids[0])) ||
					((!ies.encsharedkey || !ies.encsig) && !ies.keycrc32) ||
					(check_key(peer, ies.encsharedkey, ies.encsig, ies.keycrc32) < 1)) {
					if (!final) {
						dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
					}
					break;
				}
				apply_peer(trans, peer);
				/* Key passed, use new contexts for this session */
				trans->ecx = peer->them_ecx;
				trans->dcx = peer->them_dcx;
			}
			if (ast_test_flag(trans, FLAG_ENCRYPT) && ies.encblock && ies.enclen) {
				struct dundi_hdr *dhdr;
				unsigned char decoded[MAX_PACKET_SIZE];
				int ddatalen;
				ddatalen = sizeof(decoded);
				dhdr = dundi_decrypt(trans, decoded, &ddatalen, hdr, ies.encblock, ies.enclen);
				if (dhdr) {
					/* Handle decrypted response */
					if (dundidebug)
						dundi_showframe(dhdr, 3, &trans->addr, ddatalen - sizeof(struct dundi_hdr));
					handle_command_response(trans, dhdr, ddatalen - sizeof(struct dundi_hdr), 1);
					/* Carry back final flag */
					hdr->cmdresp |= dhdr->cmdresp & 0x80;
					break;
				} else {
					ast_debug(1, "Ouch, decrypt failed :(\n");
				}
			}
		}
		if (!final) {
			/* Turn off encryption */
			ast_clear_flag(trans, FLAG_ENCRYPT);
			dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
		}
		break;
	default:
		/* Send unknown command if we don't know it, with final flag IFF it's the
		   first command in the dialog and only if we haven't received final notification */
		if (!final) {
			dundi_ie_append_byte(ied, DUNDI_IE_UNKNOWN, cmd);
			dundi_send(trans, DUNDI_COMMAND_UNKNOWN, 0, !hdr->oseqno, ied);
		}
	}

	retval = 0;

return_cleanup:
#ifdef LOW_MEMORY
	ast_free(ied);
#endif
	return retval;
}

static void destroy_packet(struct dundi_packet *pack, int needfree);
static void destroy_packets(struct packetlist *p)
{
	struct dundi_packet *pack;

	while ((pack = AST_LIST_REMOVE_HEAD(p, list))) {
		AST_SCHED_DEL(sched, pack->retransid);
		ast_free(pack);
	}
}


static int ack_trans(struct dundi_transaction *trans, int iseqno)
{
	struct dundi_packet *pack;

	/* Ack transmitted packet corresponding to iseqno */
	AST_LIST_TRAVERSE(&trans->packets, pack, list) {
		if ((pack->h->oseqno + 1) % 255 == iseqno) {
			destroy_packet(pack, 0);
			if (!AST_LIST_EMPTY(&trans->lasttrans)) {
				ast_log(LOG_WARNING, "Whoa, there was still a last trans?\n");
				destroy_packets(&trans->lasttrans);
			}
			AST_LIST_INSERT_HEAD(&trans->lasttrans, pack, list);
			AST_SCHED_DEL(sched, trans->autokillid);
			return 1;
		}
	}

	return 0;
}

static int handle_frame(struct dundi_hdr *h, struct ast_sockaddr *sin, int datalen)
{
	struct dundi_transaction *trans;
	trans = find_transaction(h, sin);
	if (!trans) {
		dundi_reject(h, sin);
		return 0;
	}
	/* Got a transaction, see where this header fits in */
	if (h->oseqno == trans->iseqno) {
		/* Just what we were looking for...  Anything but ack increments iseqno */
		if (ack_trans(trans, h->iseqno) && ast_test_flag(trans, FLAG_FINAL)) {
			/* If final, we're done */
			destroy_trans(trans, 0);
			return 0;
		}
		if (h->cmdresp != DUNDI_COMMAND_ACK) {
			trans->oiseqno = trans->iseqno;
			trans->iseqno++;
			handle_command_response(trans, h, datalen, 0);
		}
		if (trans->aseqno != trans->iseqno) {
			dundi_ack(trans, h->cmdresp & 0x80);
			trans->aseqno = trans->iseqno;
		}
		/* Delete any saved last transmissions */
		destroy_packets(&trans->lasttrans);
		if (h->cmdresp & 0x80) {
			/* Final -- destroy now */
			destroy_trans(trans, 0);
		}
	} else if (h->oseqno == trans->oiseqno) {
		/* Last incoming sequence number -- send ACK without processing */
		dundi_ack(trans, 0);
	} else {
		/* Out of window -- simply drop */
		ast_debug(1, "Dropping packet out of window!\n");
	}
	return 0;
}

static int socket_read(int *id, int fd, short events, void *sock)
{
	struct ast_sockaddr sin;
	int res;
	struct dundi_hdr *h;
	char buf[MAX_PACKET_SIZE];

	res = ast_recvfrom(*((int *)sock), buf, sizeof(buf), 0, &sin);
	if (res < 0) {
		if (errno != ECONNREFUSED)
			ast_log(LOG_WARNING, "Error: %s\n", strerror(errno));
		return 1;
	}
	if (res < sizeof(struct dundi_hdr)) {
		ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int)sizeof(struct dundi_hdr));
		return 1;
	}
	buf[res] = '\0';
	h = (struct dundi_hdr *) buf;
	if (dundidebug)
		dundi_showframe(h, 1, &sin, res - sizeof(struct dundi_hdr));
	AST_LIST_LOCK(&peers);
	handle_frame(h, &sin, res - sizeof(struct dundi_hdr));
	AST_LIST_UNLOCK(&peers);
	return 1;
}

static void build_secret(char *secret, int seclen)
{
	unsigned char tmp[16];
	char *s;
	build_iv(tmp);
	secret[0] = '\0';
	ast_base64encode(secret, tmp, sizeof(tmp), seclen);
	/* Eliminate potential bad characters */
	while((s = strchr(secret, ';'))) *s = '+';
	while((s = strchr(secret, '/'))) *s = '+';
	while((s = strchr(secret, ':'))) *s = '+';
	while((s = strchr(secret, '@'))) *s = '+';
}


static void save_secret(const char *newkey, const char *oldkey)
{
	char tmp[350];
	if (oldkey)
		snprintf(tmp, sizeof(tmp), "%s;%s", oldkey, newkey);
	else
		snprintf(tmp, sizeof(tmp), "%s", newkey);
	rotatetime = time(NULL) + DUNDI_SECRET_TIME;
	ast_db_put(secretpath, "secret", tmp);
	snprintf(tmp, sizeof(tmp), "%d", (int)rotatetime);
	ast_db_put(secretpath, "secretexpiry", tmp);
}

static void load_password(void)
{
	char *current=NULL;
	char *last=NULL;
	char tmp[256];
	time_t expired;

	ast_db_get(secretpath, "secretexpiry", tmp, sizeof(tmp));
	if (!ast_get_time_t(tmp, &expired, 0, NULL)) {
		ast_db_get(secretpath, "secret", tmp, sizeof(tmp));
		current = strchr(tmp, ';');
		if (!current)
			current = tmp;
		else {
			*current = '\0';
			current++;
		};
		if ((time(NULL) - expired) < 0) {
			if ((expired - time(NULL)) > DUNDI_SECRET_TIME)
				expired = time(NULL) + DUNDI_SECRET_TIME;
		} else if ((time(NULL) - (expired + DUNDI_SECRET_TIME)) < 0) {
			last = current;
			current = NULL;
		} else {
			last = NULL;
			current = NULL;
		}
	}
	if (current) {
		/* Current key is still valid, just setup rotation properly */
		ast_copy_string(cursecret, current, sizeof(cursecret));
		rotatetime = expired;
	} else {
		/* Current key is out of date, rotate or eliminate all together */
		build_secret(cursecret, sizeof(cursecret));
		save_secret(cursecret, last);
	}
}

static void check_password(void)
{
	char oldsecret[80];
	time_t now;

	time(&now);
#if 0
	printf("%ld/%ld\n", now, rotatetime);
#endif
	if ((now - rotatetime) >= 0) {
		/* Time to rotate keys */
		ast_copy_string(oldsecret, cursecret, sizeof(oldsecret));
		build_secret(cursecret, sizeof(cursecret));
		save_secret(cursecret, oldsecret);
	}
}

static void *network_thread(void *ignore)
{
	/* Our job is simple: Send queued messages, retrying if necessary.  Read frames
	   from the network, and queue them for delivery to the channels */
	int res;
	/* Establish I/O callback for socket read */
	int *socket_read_id = ast_io_add(io, netsocket, socket_read, AST_IO_IN, &netsocket);
	int *socket_read_id2 = NULL;
	if (netsocket2 >= 0) {
		socket_read_id2 = ast_io_add(io, netsocket2, socket_read, AST_IO_IN, &netsocket2);
	}

	while (!dundi_shutdown) {
		res = ast_sched_wait(sched);
		if ((res > 1000) || (res < 0))
			res = 1000;
		res = ast_io_wait(io, res);
		if (res >= 0) {
			AST_LIST_LOCK(&peers);
			ast_sched_runq(sched);
			AST_LIST_UNLOCK(&peers);
		}
		check_password();
	}

	ast_io_remove(io, socket_read_id);

	if (socket_read_id2) {
		ast_io_remove(io, socket_read_id2);
	}

	return NULL;
}

static void *process_clearcache(void *ignore)
{
	struct ast_db_entry *db_entry, *db_tree;
	int striplen = sizeof("/dundi/cache");
	time_t now;

	while (!dundi_shutdown) {
		pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

		time(&now);

		db_entry = db_tree = ast_db_gettree("dundi/cache", NULL);
		for (; db_entry; db_entry = db_entry->next) {
			time_t expiry;

			if (!ast_get_time_t(db_entry->data, &expiry, 0, NULL)) {
				if (expiry < now) {
					ast_debug(1, "clearing expired DUNDI cache entry: %s\n", db_entry->key);
					ast_db_del("dundi/cache", db_entry->key + striplen);
				}
			}
		}
		ast_db_freetree(db_tree);

		pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
		pthread_testcancel();
		sleep(60);
		pthread_testcancel();
	}

	return NULL;
}

static void *process_precache(void *ign)
{
	struct dundi_precache_queue *qe;
	time_t now;
	char context[256];
	char number[256];
	int run;

	while (!dundi_shutdown) {
		time(&now);
		run = 0;
		AST_LIST_LOCK(&pcq);
		if ((qe = AST_LIST_FIRST(&pcq))) {
			if (!qe->expiration) {
				/* Gone...  Remove... */
				AST_LIST_REMOVE_HEAD(&pcq, list);
				ast_free(qe);
			} else if (qe->expiration < now) {
				/* Process this entry */
				qe->expiration = 0;
				ast_copy_string(context, qe->context, sizeof(context));
				ast_copy_string(number, qe->number, sizeof(number));
				run = 1;
			}
		}
		AST_LIST_UNLOCK(&pcq);
		if (run) {
			dundi_precache(context, number);
		} else
			sleep(1);
	}

	return NULL;
}

static int start_network_thread(void)
{
	ast_pthread_create_background(&netthreadid, NULL, network_thread, NULL);
	ast_pthread_create_background(&precachethreadid, NULL, process_precache, NULL);
	ast_pthread_create_background(&clearcachethreadid, NULL, process_clearcache, NULL);
	return 0;
}

static char *dundi_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi set debug {on|off}";
		e->usage =
			"Usage: dundi set debug {on|off}\n"
			"       Enables/Disables dumping of DUNDi packets for debugging purposes\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if (a->argc != e->args) {
		return CLI_SHOWUSAGE;
	}
	if (!strncasecmp(a->argv[e->args -1], "on", 2)) {
		dundidebug = 1;
		ast_cli(a->fd, "DUNDi Debugging Enabled\n");
	} else {
		dundidebug = 0;
		ast_cli(a->fd, "DUNDi Debugging Disabled\n");
	}
	return CLI_SUCCESS;
}

static char *dundi_store_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi store history {on|off}";
		e->usage =
			"Usage: dundi store history {on|off}\n"
			"       Enables/Disables storing of DUNDi requests and times for debugging\n"
			"purposes\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if (a->argc != e->args) {
		return CLI_SHOWUSAGE;
	}
	if (!strncasecmp(a->argv[e->args -1], "on", 2)) {
		global_storehistory = 1;
		ast_cli(a->fd, "DUNDi History Storage Enabled\n");
	} else {
		global_storehistory = 0;
		ast_cli(a->fd, "DUNDi History Storage Disabled\n");
	}
	return CLI_SUCCESS;
}

static char *dundi_flush(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	int stats = 0;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi flush [stats]";
		e->usage =
			"Usage: dundi flush [stats]\n"
			"       Flushes DUNDi answer cache, used primarily for debug.  If\n"
			"'stats' is present, clears timer statistics instead of normal\n"
			"operation.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if ((a->argc < 2) || (a->argc > 3)) {
		return CLI_SHOWUSAGE;
	}
	if (a->argc > 2) {
		if (!strcasecmp(a->argv[2], "stats")) {
			stats = 1;
		} else {
			return CLI_SHOWUSAGE;
		}
	}
	if (stats) {
		/* Flush statistics */
		struct dundi_peer *p;
		int x;
		AST_LIST_LOCK(&peers);
		AST_LIST_TRAVERSE(&peers, p, list) {
			for (x = 0;x < DUNDI_TIMING_HISTORY; x++) {
				ast_free(p->lookups[x]);
				p->lookups[x] = NULL;
				p->lookuptimes[x] = 0;
			}
			p->avgms = 0;
		}
		AST_LIST_UNLOCK(&peers);
	} else {
		ast_db_deltree("dundi/cache", NULL);
		ast_cli(a->fd, "DUNDi Cache Flushed\n");
	}
	return CLI_SUCCESS;
}

static char *model2str(int model)
{
	switch(model) {
	case DUNDI_MODEL_INBOUND:
		return "Inbound";
	case DUNDI_MODEL_OUTBOUND:
		return "Outbound";
	case DUNDI_MODEL_SYMMETRIC:
		return "Symmetric";
	default:
		return "Unknown";
	}
}

static char *complete_peer_helper(const char *line, const char *word, int pos, int state, int rpos)
{
	int which=0, len;
	char *ret = NULL;
	struct dundi_peer *p;
	char eid_str[20];

	if (pos != rpos)
		return NULL;
	AST_LIST_LOCK(&peers);
	len = strlen(word);
	AST_LIST_TRAVERSE(&peers, p, list) {
		const char *s = ast_eid_to_str(eid_str, sizeof(eid_str), &p->eid);
		if (!strncasecmp(word, s, len) && ++which > state) {
			ret = ast_strdup(s);
			break;
		}
	}
	AST_LIST_UNLOCK(&peers);
	return ret;
}

static int rescomp(const void *a, const void *b)
{
	const struct dundi_result *resa, *resb;
	resa = a;
	resb = b;
	if (resa->weight < resb->weight)
		return -1;
	if (resa->weight > resb->weight)
		return 1;
	return 0;
}

static void sort_results(struct dundi_result *results, int count)
{
	qsort(results, count, sizeof(results[0]), rescomp);
}

static char *dundi_do_lookup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	int res;
	char tmp[256];
	char fs[80] = "";
	char *context;
	int x;
	int bypass = 0;
	struct dundi_result dr[MAX_RESULTS];
	struct timeval start;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi lookup";
		e->usage =
			"Usage: dundi lookup <number>[@context] [bypass]\n"
			"       Lookup the given number within the given DUNDi context\n"
			"(or e164 if none is specified).  Bypasses cache if 'bypass'\n"
			"keyword is specified.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if ((a->argc < 3) || (a->argc > 4)) {
		return CLI_SHOWUSAGE;
	}
	if (a->argc > 3) {
		if (!strcasecmp(a->argv[3], "bypass")) {
			bypass=1;
		} else {
			return CLI_SHOWUSAGE;
		}
	}
	ast_copy_string(tmp, a->argv[2], sizeof(tmp));
	context = strchr(tmp, '@');
	if (context) {
		*context = '\0';
		context++;
	}
	start = ast_tvnow();
	res = dundi_lookup(dr, MAX_RESULTS, NULL, context, tmp, bypass);

	if (res < 0)
		ast_cli(a->fd, "DUNDi lookup returned error.\n");
	else if (!res)
		ast_cli(a->fd, "DUNDi lookup returned no results.\n");
	else
		sort_results(dr, res);
	for (x=0;x<res;x++) {
		ast_cli(a->fd, "%3d. %5d %s/%s (%s)\n", x + 1, dr[x].weight, dr[x].tech, dr[x].dest, dundi_flags2str(fs, sizeof(fs), dr[x].flags));
		ast_cli(a->fd, "     from %s, expires in %d s\n", dr[x].eid_str, dr[x].expiration);
	}
	ast_cli(a->fd, "DUNDi lookup completed in %" PRIi64 " ms\n", ast_tvdiff_ms(ast_tvnow(), start));
	return CLI_SUCCESS;
}

static char *dundi_do_precache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	int res;
	char tmp[256];
	char *context;
	struct timeval start;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi precache";
		e->usage =
			"Usage: dundi precache <number>[@context]\n"
			"       Lookup the given number within the given DUNDi context\n"
			"(or e164 if none is specified) and precaches the results to any\n"
			"upstream DUNDi push servers.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if ((a->argc < 3) || (a->argc > 3)) {
		return CLI_SHOWUSAGE;
	}
	ast_copy_string(tmp, a->argv[2], sizeof(tmp));
	context = strchr(tmp, '@');
	if (context) {
		*context = '\0';
		context++;
	}
	start = ast_tvnow();
	res = dundi_precache(context, tmp);

	if (res < 0)
		ast_cli(a->fd, "DUNDi precache returned error.\n");
	else if (!res)
		ast_cli(a->fd, "DUNDi precache returned no error.\n");
	ast_cli(a->fd, "DUNDi lookup completed in %" PRIi64 " ms\n", ast_tvdiff_ms(ast_tvnow(), start));
	return CLI_SUCCESS;
}

static char *dundi_do_query(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	int res;
	char tmp[256];
	char *context;
	dundi_eid eid;
	struct dundi_entity_info dei;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi query";
		e->usage =
			"Usage: dundi query <entity>[@context]\n"
			"       Attempts to retrieve contact information for a specific\n"
			"DUNDi entity identifier (EID) within a given DUNDi context (or\n"
			"e164 if none is specified).\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if ((a->argc < 3) || (a->argc > 3)) {
		return CLI_SHOWUSAGE;
	}
	if (ast_str_to_eid(&eid, a->argv[2])) {
		ast_cli(a->fd, "'%s' is not a valid EID!\n", a->argv[2]);
		return CLI_SHOWUSAGE;
	}
	ast_copy_string(tmp, a->argv[2], sizeof(tmp));
	context = strchr(tmp, '@');
	if (context) {
		*context = '\0';
		context++;
	}
	res = dundi_query_eid(&dei, context, eid);
	if (res < 0)
		ast_cli(a->fd, "DUNDi Query EID returned error.\n");
	else if (!res)
		ast_cli(a->fd, "DUNDi Query EID returned no results.\n");
	else {
		ast_cli(a->fd, "DUNDi Query EID succeeded:\n");
		ast_cli(a->fd, "Department:      %s\n", dei.orgunit);
		ast_cli(a->fd, "Organization:    %s\n", dei.org);
		ast_cli(a->fd, "City/Locality:   %s\n", dei.locality);
		ast_cli(a->fd, "State/Province:  %s\n", dei.stateprov);
		ast_cli(a->fd, "Country:         %s\n", dei.country);
		ast_cli(a->fd, "E-mail:          %s\n", dei.email);
		ast_cli(a->fd, "Phone:           %s\n", dei.phone);
		ast_cli(a->fd, "IP Address:      %s\n", dei.ipaddr);
	}
	return CLI_SUCCESS;
}

static char *dundi_sockaddr_stringify_host(const struct ast_sockaddr *addr)
{
	if (ast_sockaddr_isnull(addr)) {
		return "(Unspecified)";
	}
	return ast_sockaddr_stringify_host(addr);
}

static uint16_t dundi_sockaddr_port(const struct ast_sockaddr *addr)
{
	/*
	 * Test to avoid a debug message complaining about addr
	 * not being an IPv4 or IPv6 address.
	 */
	if (ast_sockaddr_isnull(addr)) {
		return 0;
	}
	return ast_sockaddr_port(addr);
}

static char *dundi_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	struct dundi_peer *peer;
	struct permission *p;
	char *order;
	char eid_str[20];
	int x, cnt;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show peer";
		e->usage =
			"Usage: dundi show peer [peer]\n"
			"       Provide a detailed description of a specifid DUNDi peer.\n";
		return NULL;
	case CLI_GENERATE:
		return complete_peer_helper(a->line, a->word, a->pos, a->n, 3);
	}
	if (a->argc != 4) {
		return CLI_SHOWUSAGE;
	}
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&peers, peer, list) {
		if (!strcasecmp(ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), a->argv[3]))
			break;
	}
	if (peer) {
		switch(peer->order) {
		case 0:
			order = "Primary";
			break;
		case 1:
			order = "Secondary";
			break;
		case 2:
			order = "Tertiary";
			break;
		case 3:
			order = "Quartiary";
			break;
		default:
			order = "Unknown";
		}
		ast_cli(a->fd, "Peer:    %s\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		ast_cli(a->fd, "Model:   %s\n", model2str(peer->model));
		ast_cli(a->fd, "Order:   %s\n", order);
		ast_cli(a->fd, "Host:    %s\n", ast_sockaddr_isnull(&peer->addr) ? "<Unspecified>" : ast_sockaddr_stringify_host(&peer->addr));
		ast_cli(a->fd, "Port:    %d\n", dundi_sockaddr_port(&peer->addr));
		ast_cli(a->fd, "Dynamic: %s\n", peer->dynamic ? "yes" : "no");
		ast_cli(a->fd, "Reg:     %s\n", peer->registerid < 0 ? "No" : "Yes");
		ast_cli(a->fd, "In Key:  %s\n", ast_strlen_zero(peer->inkey) ? "<None>" : peer->inkey);
		ast_cli(a->fd, "Out Key: %s\n", ast_strlen_zero(peer->outkey) ? "<None>" : peer->outkey);
		if (!AST_LIST_EMPTY(&peer->include))
			ast_cli(a->fd, "Include logic%s:\n", peer->model & DUNDI_MODEL_OUTBOUND ? "" : " (IGNORED)");
		AST_LIST_TRAVERSE(&peer->include, p, list)
			ast_cli(a->fd, "-- %s %s\n", p->allow ? "include" : "do not include", p->name);
		if (!AST_LIST_EMPTY(&peer->permit))
			ast_cli(a->fd, "Query logic%s:\n", peer->model & DUNDI_MODEL_INBOUND ? "" : " (IGNORED)");
		AST_LIST_TRAVERSE(&peer->permit, p, list)
			ast_cli(a->fd, "-- %s %s\n", p->allow ? "permit" : "deny", p->name);
		cnt = 0;
		for (x = 0;x < DUNDI_TIMING_HISTORY; x++) {
			if (peer->lookups[x]) {
				if (!cnt)
					ast_cli(a->fd, "Last few query times:\n");
				ast_cli(a->fd, "-- %d. %s (%d ms)\n", x + 1, peer->lookups[x], peer->lookuptimes[x]);
				cnt++;
			}
		}
		if (cnt)
			ast_cli(a->fd, "Average query time: %d ms\n", peer->avgms);
	} else
		ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]);
	AST_LIST_UNLOCK(&peers);
	return CLI_SUCCESS;
}

static char *dundi_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-20.20s %-41s     %-6.6s %-10.10s %-8.8s %-15.15s\n"
#define FORMAT "%-20.20s %-41s %s %-6d %-10.10s %-8.8s %-15.15s\n"
	struct dundi_peer *peer;
	int registeredonly=0;
	char avgms[20];
	char eid_str[20];
	int online_peers = 0;
	int offline_peers = 0;
	int unmonitored_peers = 0;
	int total_peers = 0;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show peers [registered|include|exclude|begin]";
		e->usage =
			"Usage: dundi show peers [registered|include|exclude|begin]\n"
			"       Lists all known DUNDi peers.\n"
			"       If 'registered' is present, only registered peers are shown.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if ((a->argc != 3) && (a->argc != 4) && (a->argc != 5)) {
		return CLI_SHOWUSAGE;
	}
	if ((a->argc == 4)) {
		if (!strcasecmp(a->argv[3], "registered")) {
			registeredonly = 1;
		} else {
			return CLI_SHOWUSAGE;
		}
	}
	AST_LIST_LOCK(&peers);
	ast_cli(a->fd, FORMAT2, "EID", "Host", "Port", "Model", "AvgTime", "Status");
	AST_LIST_TRAVERSE(&peers, peer, list) {
		char status[64];
		int print_line = -1;
		char srch[2000];

		total_peers++;
		if (registeredonly && ast_sockaddr_isnull(&peer->addr)) {
			continue;
		}
		if (peer->maxms) {
			if (peer->lastms < 0) {
				strcpy(status, "UNREACHABLE");
				offline_peers++;
			}
			else if (peer->lastms > peer->maxms) {
				snprintf(status, sizeof(status), "LAGGED (%d ms)", peer->lastms);
				offline_peers++;
			}
			else if (peer->lastms) {
				snprintf(status, sizeof(status), "OK (%d ms)", peer->lastms);
				online_peers++;
			}
			else {
				strcpy(status, "UNKNOWN");
				offline_peers++;
			}
		} else {
			strcpy(status, "Unmonitored");
			unmonitored_peers++;
		}
		if (peer->avgms)
			snprintf(avgms, sizeof(avgms), "%d ms", peer->avgms);
		else
			strcpy(avgms, "Unavail");
		snprintf(srch, sizeof(srch), FORMAT, ast_eid_to_str(eid_str, sizeof(eid_str),
			&peer->eid), dundi_sockaddr_stringify_host(&peer->addr),
			peer->dynamic ? "(D)" : "(S)", dundi_sockaddr_port(&peer->addr), model2str(peer->model), avgms, status);

                if (a->argc == 5) {
                  if (!strcasecmp(a->argv[3],"include") && strstr(srch,a->argv[4])) {
                        print_line = -1;
                   } else if (!strcasecmp(a->argv[3],"exclude") && !strstr(srch,a->argv[4])) {
                        print_line = 1;
                   } else if (!strcasecmp(a->argv[3],"begin") && !strncasecmp(srch,a->argv[4],strlen(a->argv[4]))) {
                        print_line = -1;
                   } else {
                        print_line = 0;
                  }
                }

        if (print_line) {
			ast_cli(a->fd, FORMAT, ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
				dundi_sockaddr_stringify_host(&peer->addr),
				peer->dynamic ? "(D)" : "(S)", dundi_sockaddr_port(&peer->addr), model2str(peer->model), avgms, status);
		}
	}
	ast_cli(a->fd, "%d dundi peers [%d online, %d offline, %d unmonitored]\n", total_peers, online_peers, offline_peers, unmonitored_peers);
	AST_LIST_UNLOCK(&peers);
	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static char *dundi_show_trans(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-47s %-5.5s %-5.5s %-3.3s %-3.3s %-3.3s\n"
#define FORMAT "%-41s:%5d %-5.5d %-5.5d %-3.3d %-3.3d %-3.3d\n"
	struct dundi_transaction *trans;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show trans";
		e->usage =
			"Usage: dundi show trans\n"
			"       Lists all known DUNDi transactions.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}
	AST_LIST_LOCK(&peers);
	ast_cli(a->fd, FORMAT2, "Remote", "Src", "Dst", "Tx", "Rx", "Ack");
	AST_LIST_TRAVERSE(&alltrans, trans, all) {
		ast_cli(a->fd, FORMAT, ast_sockaddr_stringify_host(&trans->addr),
			ast_sockaddr_port(&trans->addr), trans->strans, trans->dtrans,
			trans->oseqno, trans->iseqno, trans->aseqno);
	}
	AST_LIST_UNLOCK(&peers);
	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static char *dundi_show_entityid(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	char eid_str[20];
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show entityid";
		e->usage =
			"Usage: dundi show entityid\n"
			"       Displays the global entityid for this host.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}
	AST_LIST_LOCK(&peers);
	ast_eid_to_str(eid_str, sizeof(eid_str), &global_eid);
	AST_LIST_UNLOCK(&peers);
	ast_cli(a->fd, "Global EID for this system is '%s'\n", eid_str);
	return CLI_SUCCESS;
}

static char *dundi_show_requests(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-15s %-15s %-15s %-3.3s %-3.3s\n"
#define FORMAT "%-15s %-15s %-15s %-3.3d %-3.3d\n"
	struct dundi_request *req;
	char eidstr[20];
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show requests";
		e->usage =
			"Usage: dundi show requests\n"
			"       Lists all known pending DUNDi requests.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}
	AST_LIST_LOCK(&peers);
	ast_cli(a->fd, FORMAT2, "Number", "Context", "Root", "Max", "Rsp");
	AST_LIST_TRAVERSE(&requests, req, list) {
		ast_cli(a->fd, FORMAT, req->number, req->dcontext,
			dundi_eid_zero(&req->root_eid) ? "<unspecified>" : ast_eid_to_str(eidstr, sizeof(eidstr), &req->root_eid), req->maxcount, req->respcount);
	}
	AST_LIST_UNLOCK(&peers);
	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

/* Grok-a-dial DUNDi */

static char *dundi_show_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-12.12s %-7.7s %-12.12s %-10.10s %-5.5s %-25.25s\n"
#define FORMAT "%-12.12s %-7s %-12.12s %-10.10s %-5.5s %-25.25s\n"
	struct dundi_mapping *map;
	char fs[256];
	char weight[8];
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show mappings";
		e->usage =
			"Usage: dundi show mappings\n"
			"       Lists all known DUNDi mappings.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}
	AST_LIST_LOCK(&peers);
	ast_cli(a->fd, FORMAT2, "DUNDi Cntxt", "Weight", "Local Cntxt", "Options", "Tech", "Destination");
	AST_LIST_TRAVERSE(&mappings, map, list) {
		snprintf(weight, sizeof(weight), "%d", get_mapping_weight(map, NULL));
		ast_cli(a->fd, FORMAT, map->dcontext, weight,
			ast_strlen_zero(map->lcontext) ? "<none>" : map->lcontext,
			dundi_flags2str(fs, sizeof(fs), map->options), tech2str(map->tech), map->dest);
	}
	AST_LIST_UNLOCK(&peers);
	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static char *dundi_show_precache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-12.12s %-12.12s %-10.10s\n"
#define FORMAT "%-12.12s %-12.12s %02d:%02d:%02d\n"
	struct dundi_precache_queue *qe;
	int h,m,s;
	time_t now;
	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show precache";
		e->usage =
			"Usage: dundi show precache\n"
			"       Lists all known DUNDi scheduled precache updates.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}
	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}
	time(&now);
	ast_cli(a->fd, FORMAT2, "Number", "Context", "Expiration");
	AST_LIST_LOCK(&pcq);
	AST_LIST_TRAVERSE(&pcq, qe, list) {
		s = qe->expiration - now;
		h = s / 3600;
		s = s % 3600;
		m = s / 60;
		s = s % 60;
		ast_cli(a->fd, FORMAT, qe->number, qe->context, h,m,s);
	}
	AST_LIST_UNLOCK(&pcq);

	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static char *dundi_show_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-12.12s %-16.16s %-10.10s  %-18s %-7s %s\n"
#define FORMAT "%-12.12s %-16.16s %6d sec  %-18s %-7d %s/%s (%s)\n"
        struct ast_db_entry *db_tree, *db_entry;
	int cnt = 0;
	time_t ts, now;
	dundi_eid src_eid;
	char src_eid_str[20];
	int expiry, tech, weight;
	struct ast_flags flags;
	char fs[256];
	int length;
	char *ptr, *term, *src, *number, *context, *dst;

	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show cache";
		e->usage =
		"Usage: dundi show cache\n"
		"       Lists all DUNDi cache entries.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}

	time(&now);
	db_tree = ast_db_gettree("dundi/cache", NULL);
	ast_cli(a->fd, FORMAT2, "Number", "Context", "Expiration", "From", "Weight", "Destination (Flags)");
	for (db_entry = db_tree; db_entry; db_entry = db_entry->next) {
		char *rest;

		if ((strncmp(db_entry->key, "/dundi/cache/hint/", 18) == 0) || ast_get_time_t(db_entry->data, &ts, 0, &length)) {
			continue;
		}

		expiry = ts - now;

		if (expiry <= 0) {
			continue;
		}

		ptr = db_entry->key + sizeof("/dundi/cache");
		strtok_r(ptr, "/", &rest);
		number = strtok_r(NULL, "/", &rest);
		context = strtok_r(NULL, "/", &rest);
		ptr = strtok_r(NULL, "/", &rest);

		if (*ptr != 'e') {
			continue;
		}

		ptr = db_entry->data + length + 1;

		if ((sscanf(ptr, "%30u/%30d/%30d/%n", &(flags.flags), &weight, &tech, &length) != 3)) {
			continue;
		}

		ptr += length;
		dst = ptr;
		term = strchr(ptr, '|');

		if (!term) {
			continue;
		}

		/* Ok, at this point we know we aren't going to skp the entry, so we go ahead and increment the count. */
		cnt++;

		*term = '\0';
		src = strrchr(ptr, '/');
		dundi_eid_zero(&src_eid);

		if (src) {
			*src = '\0';
			src++;
			dundi_str_short_to_eid(&src_eid, src);
			ast_eid_to_str(src_eid_str, sizeof(src_eid_str), &src_eid);
		}

		ast_cli(a->fd, FORMAT, number, context, expiry, src_eid_str, weight, tech2str(tech), dst, dundi_flags2str(fs, sizeof(fs), flags.flags));
	}

	ast_cli(a->fd, "Number of entries: %d\n", cnt);
	ast_db_freetree(db_tree);

	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static char *dundi_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT2 "%-12.12s %-16.16s %-10.10s  %-18s\n"
#define FORMAT "%-12.12s %-16.16s %6d sec  %-18s\n"
	struct ast_db_entry *db_tree, *db_entry;
	int cnt = 0;
	time_t ts, now;
	dundi_eid src_eid;
	char src_eid_str[20];
	int expiry;
	int length;
	char *ptr, *src, *number, *context;

	switch (cmd) {
	case CLI_INIT:
		e->command = "dundi show hints";
		e->usage =
			"Usage: dundi show hints\n"
			"       Lists all DUNDi 'DONTASK' hints in the cache.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if (a->argc != 3) {
		return CLI_SHOWUSAGE;
	}

	time(&now);
	db_tree = ast_db_gettree("dundi/cache/hint", NULL);
	ast_cli(a->fd, FORMAT2, "Prefix", "Context", "Expiration", "From");

	for (db_entry = db_tree; db_entry; db_entry = db_entry->next) {
		char *rest = NULL;

		if (ast_get_time_t(db_entry->data, &ts, 0, &length)) {
			continue;
		}

		expiry = ts - now;

		if (expiry <= 0) {
			continue;
		}

		ptr = db_entry->key + sizeof("/dundi/cache/hint");
		src = strtok_r(ptr, "/", &rest);
		number = strtok_r(NULL, "/", &rest);
		context = strtok_r(NULL, "/", &rest);
		ptr = strtok_r(NULL, "/", &rest);

		if (*ptr != 'e') {
			continue;
		}

		cnt++;
		dundi_str_short_to_eid(&src_eid, src);
		ast_eid_to_str(src_eid_str, sizeof(src_eid_str), &src_eid);
		ast_cli(a->fd, FORMAT, number, context, expiry, src_eid_str);
	}

	ast_cli(a->fd, "Number of entries: %d\n", cnt);
	ast_db_freetree(db_tree);

	return CLI_SUCCESS;
#undef FORMAT
#undef FORMAT2
}

static struct ast_cli_entry cli_dundi[] = {
	AST_CLI_DEFINE(dundi_set_debug, "Enable/Disable DUNDi debugging"),
	AST_CLI_DEFINE(dundi_store_history, "Enable/Disable DUNDi historic records"),
	AST_CLI_DEFINE(dundi_flush, "Flush DUNDi cache"),
	AST_CLI_DEFINE(dundi_show_peers, "Show defined DUNDi peers"),
	AST_CLI_DEFINE(dundi_show_trans, "Show active DUNDi transactions"),
	AST_CLI_DEFINE(dundi_show_entityid, "Display Global Entity ID"),
	AST_CLI_DEFINE(dundi_show_mappings, "Show DUNDi mappings"),
	AST_CLI_DEFINE(dundi_show_precache, "Show DUNDi precache"),
	AST_CLI_DEFINE(dundi_show_requests, "Show DUNDi requests"),
	AST_CLI_DEFINE(dundi_show_peer, "Show info on a specific DUNDi peer"),
	AST_CLI_DEFINE(dundi_show_cache, "Show DUNDi cache"),
	AST_CLI_DEFINE(dundi_show_hints, "Show DUNDi hints in the cache"),
	AST_CLI_DEFINE(dundi_do_precache, "Precache a number in DUNDi"),
	AST_CLI_DEFINE(dundi_do_lookup, "Lookup a number in DUNDi"),
	AST_CLI_DEFINE(dundi_do_query, "Query a DUNDi EID"),
};

static struct dundi_transaction *create_transaction(struct dundi_peer *p)
{
	struct dundi_transaction *trans;
	int tid;

	/* Don't allow creation of transactions to non-registered peers */
	if (p && ast_sockaddr_isnull(&p->addr)) {
		return NULL;
	}
	tid = get_trans_id();
	if (tid < 1)
		return NULL;
	if (!(trans = ast_calloc(1, sizeof(*trans))))
		return NULL;

	if (global_storehistory) {
		trans->start = ast_tvnow();
		ast_set_flag(trans, FLAG_STOREHIST);
	}
	trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
	trans->autokillid = -1;
	if (p) {
		apply_peer(trans, p);
		if (!p->sentfullkey)
			ast_set_flag(trans, FLAG_SENDFULLKEY);
	}
	trans->strans = tid;
	AST_LIST_INSERT_HEAD(&alltrans, trans, all);

	return trans;
}

static int dundi_xmit(struct dundi_packet *pack)
{
	int res;
	if (dundidebug)
		dundi_showframe(pack->h, 0, &pack->parent->addr, pack->datalen - sizeof(struct dundi_hdr));

	if (netsocket2 < 0) {
		res = ast_sendto(netsocket, pack->data, pack->datalen, 0, &pack->parent->addr);
	} else {
		if (ast_sockaddr_is_ipv4(&pack->parent->addr)) {
			res = ast_sendto(netsocket, pack->data, pack->datalen, 0, &pack->parent->addr);
		} else {
			res = ast_sendto(netsocket2, pack->data, pack->datalen, 0, &pack->parent->addr);
		}
	}

	if (res < 0) {
		ast_log(LOG_WARNING, "Failed to transmit to '%s': %s\n",
			ast_sockaddr_stringify(&pack->parent->addr), strerror(errno));
	}
	if (res > 0)
		res = 0;
	return res;
}

static void destroy_packet(struct dundi_packet *pack, int needfree)
{
	if (pack->parent)
		AST_LIST_REMOVE(&pack->parent->packets, pack, list);
	AST_SCHED_DEL(sched, pack->retransid);
	if (needfree)
		ast_free(pack);
}

static void destroy_trans(struct dundi_transaction *trans, int fromtimeout)
{
	struct dundi_peer *peer;
	int ms;
	int x;
	int cnt;
	char eid_str[20];
	if (ast_test_flag(trans, FLAG_ISREG | FLAG_ISQUAL | FLAG_STOREHIST)) {
		AST_LIST_TRAVERSE(&peers, peer, list) {
			if (peer->regtrans == trans)
				peer->regtrans = NULL;
			if (peer->qualtrans == trans) {
				if (fromtimeout) {
					if (peer->lastms > -1)
						ast_log(LOG_NOTICE, "Peer '%s' has become UNREACHABLE!\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
					peer->lastms = -1;
				} else {
					ms = ast_tvdiff_ms(ast_tvnow(), peer->qualtx);
					if (ms < 1)
						ms = 1;
					if (ms < peer->maxms) {
						if ((peer->lastms >= peer->maxms) || (peer->lastms < 0))
							ast_log(LOG_NOTICE, "Peer '%s' has become REACHABLE!\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
					} else if (peer->lastms < peer->maxms) {
						ast_log(LOG_NOTICE, "Peer '%s' has become TOO LAGGED (%d ms)\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), ms);
					}
					peer->lastms = ms;
				}
				peer->qualtrans = NULL;
			}
			if (ast_test_flag(trans, FLAG_STOREHIST)) {
				if (trans->parent && !ast_strlen_zero(trans->parent->number)) {
					if (!ast_eid_cmp(&trans->them_eid, &peer->eid)) {
						peer->avgms = 0;
						cnt = 0;
						ast_free(peer->lookups[DUNDI_TIMING_HISTORY - 1]);
						for (x=DUNDI_TIMING_HISTORY-1;x>0;x--) {
							peer->lookuptimes[x] = peer->lookuptimes[x-1];
							peer->lookups[x] = peer->lookups[x-1];
							if (peer->lookups[x]) {
								peer->avgms += peer->lookuptimes[x];
								cnt++;
							}
						}
						peer->lookuptimes[0] = ast_tvdiff_ms(ast_tvnow(), trans->start);
						peer->lookups[0] = ast_malloc(strlen(trans->parent->number) + strlen(trans->parent->dcontext) + 2);
						if (peer->lookups[0]) {
							sprintf(peer->lookups[0], "%s@%s", trans->parent->number, trans->parent->dcontext);
							peer->avgms += peer->lookuptimes[0];
							cnt++;
						}
						if (cnt)
							peer->avgms /= cnt;
					}
				}
			}
		}
	}
	if (trans->parent) {
		/* Unlink from parent if appropriate */
		AST_LIST_REMOVE(&trans->parent->trans, trans, parentlist);
		if (AST_LIST_EMPTY(&trans->parent->trans)) {
			/* Wake up sleeper */
			if (trans->parent->pfds[1] > -1) {
				if (write(trans->parent->pfds[1], "killa!", 6) < 0) {
					ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
				}
			}
		}
	}
	/* Unlink from all trans */
	AST_LIST_REMOVE(&alltrans, trans, all);
	destroy_packets(&trans->packets);
	destroy_packets(&trans->lasttrans);
	AST_SCHED_DEL(sched, trans->autokillid);
	if (trans->thread) {
		/* If used by a thread, mark as dead and be done */
		ast_set_flag(trans, FLAG_DEAD);
	} else
		ast_free(trans);
}

static int dundi_rexmit(const void *data)
{
	struct dundi_packet *pack = (struct dundi_packet *)data;
	int res;
	AST_LIST_LOCK(&peers);
	if (pack->retrans < 1) {
		pack->retransid = -1;
		if (!ast_test_flag(pack->parent, FLAG_ISQUAL)) {
			ast_log(LOG_NOTICE, "Max retries exceeded to host '%s' msg %d on call %d\n",
				ast_sockaddr_stringify(&pack->parent->addr), pack->h->oseqno, ntohs(pack->h->strans));
		}
		destroy_trans(pack->parent, 1);
		res = 0;
	} else {
		/* Decrement retransmission, try again */
		pack->retrans--;
		dundi_xmit(pack);
		res = 1;
	}
	AST_LIST_UNLOCK(&peers);
	return res;
}

static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied)
{
	struct dundi_packet *pack;
	int res;
	int len;
	char eid_str[20];
	len = sizeof(struct dundi_packet) + sizeof(struct dundi_hdr) + (ied ? ied->pos : 0);
	/* Reserve enough space for encryption */
	if (ast_test_flag(trans, FLAG_ENCRYPT))
		len += 384;
	pack = ast_calloc(1, len);
	if (pack) {
		pack->h = (struct dundi_hdr *)(pack->data);
		pack->retransid = -1;
		if (cmdresp != DUNDI_COMMAND_ACK) {
			pack->retransid = ast_sched_add(sched, trans->retranstimer, dundi_rexmit, pack);
			pack->retrans = DUNDI_DEFAULT_RETRANS - 1;
			AST_LIST_INSERT_HEAD(&trans->packets, pack, list);
		}
		pack->parent = trans;
		pack->h->strans = htons(trans->strans);
		pack->h->dtrans = htons(trans->dtrans);
		pack->h->iseqno = trans->iseqno;
		pack->h->oseqno = trans->oseqno;
		pack->h->cmdresp = cmdresp;
		pack->datalen = sizeof(struct dundi_hdr);
		if (ied) {
			memcpy(pack->h->ies, ied->buf, ied->pos);
			pack->datalen += ied->pos;
		}
		if (final) {
			pack->h->cmdresp |= DUNDI_COMMAND_FINAL;
			ast_set_flag(trans, FLAG_FINAL);
		}
		pack->h->cmdflags = flags;
		if (cmdresp != DUNDI_COMMAND_ACK) {
			trans->oseqno++;
			trans->oseqno = trans->oseqno % 256;
		}
		trans->aseqno = trans->iseqno;
		/* If we have their public key, encrypt */
		if (ast_test_flag(trans, FLAG_ENCRYPT)) {
			switch(cmdresp) {
			case DUNDI_COMMAND_REGREQ:
			case DUNDI_COMMAND_REGRESPONSE:
			case DUNDI_COMMAND_DPDISCOVER:
			case DUNDI_COMMAND_DPRESPONSE:
			case DUNDI_COMMAND_EIDQUERY:
			case DUNDI_COMMAND_EIDRESPONSE:
			case DUNDI_COMMAND_PRECACHERQ:
			case DUNDI_COMMAND_PRECACHERP:
				if (dundidebug)
					dundi_showframe(pack->h, 2, &trans->addr, pack->datalen - sizeof(struct dundi_hdr));
				res = dundi_encrypt(trans, pack);
				break;
			default:
				res = 0;
			}
		} else
			res = 0;
		if (!res)
			res = dundi_xmit(pack);
		if (res)
			ast_log(LOG_NOTICE, "Failed to send packet to '%s'\n", ast_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));

		if (cmdresp == DUNDI_COMMAND_ACK)
			ast_free(pack);
		return res;
	}
	return -1;
}

static int do_autokill(const void *data)
{
	struct dundi_transaction *trans = (struct dundi_transaction *)data;
	char eid_str[20];
	ast_log(LOG_NOTICE, "Transaction to '%s' took too long to ACK, destroying\n",
		ast_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));
	trans->autokillid = -1;
	destroy_trans(trans, 0); /* We could actually set it to 1 instead of 0, but we won't ;-) */
	return 0;
}

static void dundi_ie_append_eid_appropriately(struct dundi_ie_data *ied, char *context, dundi_eid *eid, dundi_eid *us)
{
	struct dundi_peer *p;
	if (!ast_eid_cmp(eid, us)) {
		dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
		return;
	}
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&peers, p, list) {
		if (!ast_eid_cmp(&p->eid, eid)) {
			if (has_permission(&p->include, context))
				dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
			else
				dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
			break;
		}
	}
	if (!p)
		dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
	AST_LIST_UNLOCK(&peers);
}

static int dundi_discover(struct dundi_transaction *trans)
{
	struct dundi_ie_data ied;
	int x;
	if (!trans->parent) {
		ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
		return -1;
	}
	memset(&ied, 0, sizeof(ied));
	dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
	if (!dundi_eid_zero(&trans->us_eid))
		dundi_ie_append_eid(&ied, DUNDI_IE_EID_DIRECT, &trans->us_eid);
	for (x=0;x<trans->eidcount;x++)
		dundi_ie_append_eid_appropriately(&ied, trans->parent->dcontext, &trans->eids[x], &trans->us_eid);
	dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
	dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
	dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
	if (trans->parent->cbypass)
		dundi_ie_append(&ied, DUNDI_IE_CACHEBYPASS);
	if (trans->autokilltimeout)
		trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
	return dundi_send(trans, DUNDI_COMMAND_DPDISCOVER, 0, 0, &ied);
}

static int precache_trans(struct dundi_transaction *trans, struct dundi_mapping *maps, int mapcount, int *minexp, int *foundanswers)
{
	struct dundi_ie_data ied;
	int x, res;
	int max = 999999;
	int expiration = dundi_cache_time;
	int ouranswers=0;
	dundi_eid *avoid[1] = { NULL, };
	int direct[1] = { 0, };
	struct dundi_result dr[MAX_RESULTS];
	struct dundi_hint_metadata hmd;
	if (!trans->parent) {
		ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
		return -1;
	}
	memset(&hmd, 0, sizeof(hmd));
	memset(&dr, 0, sizeof(dr));
	/* Look up the answers we're going to include */
	for (x=0;x<mapcount;x++)
		ouranswers = dundi_lookup_local(dr, maps + x, trans->parent->number, &trans->us_eid, ouranswers, &hmd);
	if (ouranswers < 0)
		ouranswers = 0;
	for (x=0;x<ouranswers;x++) {
		if (dr[x].weight < max)
			max = dr[x].weight;
	}
	if (max) {
		/* If we do not have a canonical result, keep looking */
		res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, trans->parent->dcontext, trans->parent->number, trans->ttl, 1, &hmd, &expiration, 0, 1, &trans->them_eid, avoid, direct);
		if (res > 0) {
			/* Append answer in result */
			ouranswers += res;
		}
	}

	if (ouranswers > 0) {
		*foundanswers += ouranswers;
		memset(&ied, 0, sizeof(ied));
		dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
		if (!dundi_eid_zero(&trans->us_eid))
			dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
		for (x=0;x<trans->eidcount;x++)
			dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
		dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
		dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
		dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
		for (x=0;x<ouranswers;x++) {
			/* Add answers */
			if (dr[x].expiration && (expiration > dr[x].expiration))
				expiration = dr[x].expiration;
			dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
		}
		dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
		dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
		if (trans->autokilltimeout)
			trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
		if (expiration < *minexp)
			*minexp = expiration;
		return dundi_send(trans, DUNDI_COMMAND_PRECACHERQ, 0, 0, &ied);
	} else {
		/* Oops, nothing to send... */
		destroy_trans(trans, 0);
		return 0;
	}
}

static int dundi_query(struct dundi_transaction *trans)
{
	struct dundi_ie_data ied;
	int x;
	if (!trans->parent) {
		ast_log(LOG_WARNING, "Tried to query a transaction with no parent?!?\n");
		return -1;
	}
	memset(&ied, 0, sizeof(ied));
	dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
	if (!dundi_eid_zero(&trans->us_eid))
		dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
	for (x=0;x<trans->eidcount;x++)
		dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
	dundi_ie_append_eid(&ied, DUNDI_IE_REQEID, &trans->parent->query_eid);
	dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
	dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
	if (trans->autokilltimeout)
		trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
	return dundi_send(trans, DUNDI_COMMAND_EIDQUERY, 0, 0, &ied);
}

static int discover_transactions(struct dundi_request *dr)
{
	struct dundi_transaction *trans;
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
		dundi_discover(trans);
	}
	AST_LIST_UNLOCK(&peers);
	return 0;
}

static int precache_transactions(struct dundi_request *dr, struct dundi_mapping *maps, int mapcount, int *expiration, int *foundanswers)
{
	struct dundi_transaction *trans;

	/* Mark all as "in thread" so they don't disappear */
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
		if (trans->thread)
			ast_log(LOG_WARNING, "This shouldn't happen, really...\n");
		trans->thread = 1;
	}
	AST_LIST_UNLOCK(&peers);

	AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
		if (!ast_test_flag(trans, FLAG_DEAD))
			precache_trans(trans, maps, mapcount, expiration, foundanswers);
	}

	/* Cleanup any that got destroyed in the mean time */
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE_SAFE_BEGIN(&dr->trans, trans, parentlist) {
		trans->thread = 0;
		if (ast_test_flag(trans, FLAG_DEAD)) {
			ast_debug(1, "Our transaction went away!\n");
			/* This is going to remove the transaction from the dundi_request's list, as well
			 * as the global transactions list */
			destroy_trans(trans, 0);
		}
	}
	AST_LIST_TRAVERSE_SAFE_END
	AST_LIST_UNLOCK(&peers);

	return 0;
}

static int query_transactions(struct dundi_request *dr)
{
	struct dundi_transaction *trans;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
		dundi_query(trans);
	}
	AST_LIST_UNLOCK(&peers);

	return 0;
}

static int optimize_transactions(struct dundi_request *dr, int order)
{
	/* Minimize the message propagation through DUNDi by
	   alerting the network to hops which should be not be considered */
	struct dundi_transaction *trans;
	struct dundi_peer *peer;
	dundi_eid tmp;
	int x;
	int needpush;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
		/* Pop off the true root */
		if (trans->eidcount) {
			tmp = trans->eids[--trans->eidcount];
			needpush = 1;
		} else {
			tmp = trans->us_eid;
			needpush = 0;
		}

		AST_LIST_TRAVERSE(&peers, peer, list) {
			if (ast_eid_cmp(&peer->eid, &empty_eid) &&			/* peer's eid is not empty (in case of dynamic peers) */
				(peer->lastms > -1) &&							/* peer is reachable */
				has_permission(&peer->include, dr->dcontext) &&	/* peer has destination context */
				ast_eid_cmp(&peer->eid, &trans->them_eid) &&	/* peer is not transaction endpoint */
				(peer->order <= order)) {
				/* For each other transaction, make sure we don't
				   ask this EID about the others if they're not
				   already in the list */
				if (!ast_eid_cmp(&tmp, &peer->eid))
					x = -1;
				else {
					for (x=0;x<trans->eidcount;x++) {
						if (!ast_eid_cmp(&trans->eids[x], &peer->eid))
							break;
					}
				}
				if (x == trans->eidcount) {
					/* Nope not in the list, if needed, add us at the end since we're the source */
					if (trans->eidcount < DUNDI_MAX_STACK - needpush) {
						trans->eids[trans->eidcount++] = peer->eid;
						/* Need to insert the real root (or us) at the bottom now as
						   a requirement now.  */
						needpush = 1;
					}
				}
			}
		}
		/* If necessary, push the true root back on the end */
		if (needpush)
			trans->eids[trans->eidcount++] = tmp;
	}
	AST_LIST_UNLOCK(&peers);

	return 0;
}

static int append_transaction(struct dundi_request *dr, struct dundi_peer *p, int ttl, dundi_eid *avoid[])
{
	struct dundi_transaction *trans;
	int x;
	char eid_str[20];
	char eid_str2[20];

	/* Ignore if not registered */
	if (ast_sockaddr_isnull(&p->addr)) {
		return 0;
	}
	if (p->maxms && ((p->lastms < 0) || (p->lastms >= p->maxms)))
		return 0;

	if (ast_strlen_zero(dr->number))
		ast_debug(1, "Will query peer '%s' for '%s' (context '%s')\n", ast_eid_to_str(eid_str, sizeof(eid_str), &p->eid), ast_eid_to_str(eid_str2, sizeof(eid_str2), &dr->query_eid), dr->dcontext);
	else
		ast_debug(1, "Will query peer '%s' for '%s@%s'\n", ast_eid_to_str(eid_str, sizeof(eid_str), &p->eid), dr->number, dr->dcontext);

	trans = create_transaction(p);
	if (!trans)
		return -1;
	trans->parent = dr;
	trans->ttl = ttl;
	for (x = 0; avoid[x] && (x < DUNDI_MAX_STACK); x++)
		trans->eids[x] = *avoid[x];
	trans->eidcount = x;
	AST_LIST_INSERT_HEAD(&dr->trans, trans, parentlist);

	return 0;
}

static void cancel_request(struct dundi_request *dr)
{
	struct dundi_transaction *trans;

	AST_LIST_LOCK(&peers);
	while ((trans = AST_LIST_REMOVE_HEAD(&dr->trans, parentlist))) {
		/* Orphan transaction from request */
		trans->parent = NULL;
		/* Send final cancel */
		dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
	}
	AST_LIST_UNLOCK(&peers);
}

static void abort_request(struct dundi_request *dr)
{
	struct dundi_transaction *trans;

	AST_LIST_LOCK(&peers);
	while ((trans = AST_LIST_FIRST(&dr->trans))) {
		/* This will remove the transaction from the list */
		destroy_trans(trans, 0);
	}
	AST_LIST_UNLOCK(&peers);
}

static void build_transactions(struct dundi_request *dr, int ttl, int order, int *foundcache, int *skipped, int blockempty, int nocache, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int directs[])
{
	struct dundi_peer *p;
	int x;
	int res;
	int pass;
	int allowconnect;
	char eid_str[20];
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&peers, p, list) {
		if (modeselect == 1) {
			/* Send the precache to push upstreams only! */
			pass = has_permission(&p->permit, dr->dcontext) && (p->pcmodel & DUNDI_MODEL_OUTBOUND);
			allowconnect = 1;
		} else {
			/* Normal lookup / EID query */
			pass = has_permission(&p->include, dr->dcontext);
			allowconnect = p->model & DUNDI_MODEL_OUTBOUND;
		}
		if (skip) {
			if (!ast_eid_cmp(skip, &p->eid))
				pass = 0;
		}
		if (pass) {
			if (p->order <= order) {
				/* Check order first, then check cache, regardless of
				   omissions, this gets us more likely to not have an
				   affected answer. */
				if((nocache || !(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration)))) {
					res = 0;
					/* Make sure we haven't already seen it and that it won't
					   affect our answer */
					for (x=0;avoid[x];x++) {
						if (!ast_eid_cmp(avoid[x], &p->eid) || !ast_eid_cmp(avoid[x], &p->us_eid)) {
							/* If not a direct connection, it affects our answer */
							if (directs && !directs[x])
								ast_clear_flag_nonstd(dr->hmd, DUNDI_HINT_UNAFFECTED);
							break;
						}
					}
					/* Make sure we can ask */
					if (allowconnect) {
						if (!avoid[x] && (!blockempty || !dundi_eid_zero(&p->us_eid))) {
							/* Check for a matching or 0 cache entry */
							append_transaction(dr, p, ttl, avoid);
						} else {
							ast_debug(1, "Avoiding '%s' in transaction\n", ast_eid_to_str(eid_str, sizeof(eid_str), avoid[x]));
						}
					}
				}
				*foundcache |= res;
			} else if (!*skipped || (p->order < *skipped))
				*skipped = p->order;
		}
	}
	AST_LIST_UNLOCK(&peers);
}

static int register_request(struct dundi_request *dr, struct dundi_request **pending)
{
	struct dundi_request *cur;
	int res=0;
	char eid_str[20];
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&requests, cur, list) {
		ast_debug(1, "Checking '%s@%s' vs '%s@%s'\n", cur->dcontext, cur->number,
			dr->dcontext, dr->number);
		if (!strcasecmp(cur->dcontext, dr->dcontext) &&
		    !strcasecmp(cur->number, dr->number) &&
		    (!ast_eid_cmp(&cur->root_eid, &dr->root_eid) || (cur->crc32 == dr->crc32))) {
			ast_debug(1, "Found existing query for '%s@%s' for '%s' crc '%08x'\n",
				cur->dcontext, cur->number, ast_eid_to_str(eid_str, sizeof(eid_str), &cur->root_eid), cur->crc32);
			*pending = cur;
			res = 1;
			break;
		}
	}
	if (!res) {
		ast_debug(1, "Registering request for '%s@%s' on behalf of '%s' crc '%08x'\n",
				dr->number, dr->dcontext, ast_eid_to_str(eid_str, sizeof(eid_str), &dr->root_eid), dr->crc32);
		/* Go ahead and link us in since nobody else is searching for this */
		AST_LIST_INSERT_HEAD(&requests, dr, list);
		*pending = NULL;
	}
	AST_LIST_UNLOCK(&peers);
	return res;
}

static void unregister_request(struct dundi_request *dr)
{
	AST_LIST_LOCK(&peers);
	AST_LIST_REMOVE(&requests, dr, list);
	AST_LIST_UNLOCK(&peers);
}

static int check_request(struct dundi_request *dr)
{
	struct dundi_request *cur;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&requests, cur, list) {
		if (cur == dr)
			break;
	}
	AST_LIST_UNLOCK(&peers);

	return cur ? 1 : 0;
}

static unsigned long avoid_crc32(dundi_eid *avoid[])
{
	/* Idea is that we're calculating a checksum which is independent of
	   the order that the EID's are listed in */
	uint32_t acrc32 = 0;
	int x;
	for (x=0;avoid[x];x++) {
		/* Order doesn't matter */
		if (avoid[x+1]) {
			acrc32 ^= crc32(0L, (unsigned char *)avoid[x], sizeof(dundi_eid));
		}
	}
	return acrc32;
}

static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *hmd, int *expiration, int cbypass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[])
{
	int res;
	struct dundi_request dr, *pending;
	dundi_eid *rooteid=NULL;
	int x;
	int ttlms;
	int ms;
	int foundcache;
	int skipped=0;
	int order=0;
	char eid_str[20];
	struct timeval start;

	/* Don't do anthing for a hungup channel */
	if (chan && ast_check_hangup(chan))
		return 0;

	ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;

	for (x=0;avoid[x];x++)
		rooteid = avoid[x];
	/* Now perform real check */
	memset(&dr, 0, sizeof(dr));
	if (pipe(dr.pfds)) {
		ast_log(LOG_WARNING, "pipe failed: %s\n" , strerror(errno));
		return -1;
	}
	dr.dr = result;
	dr.hmd = hmd;
	dr.maxcount = maxret;
	dr.expiration = *expiration;
	dr.cbypass = cbypass;
	dr.crc32 = avoid_crc32(avoid);
	ast_copy_string(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext));
	ast_copy_string(dr.number, number, sizeof(dr.number));
	if (rooteid)
		dr.root_eid = *rooteid;
	res = register_request(&dr, &pending);
	if (res) {
		/* Already a request */
		if (rooteid && !ast_eid_cmp(&dr.root_eid, &pending->root_eid)) {
			/* This is on behalf of someone else.  Go ahead and close this out since
			   they'll get their answer anyway. */
			ast_debug(1, "Oooh, duplicate request for '%s@%s' for '%s'\n",
				dr.number,dr.dcontext,ast_eid_to_str(eid_str, sizeof(eid_str), &dr.root_eid));
			close(dr.pfds[0]);
			close(dr.pfds[1]);
			return -2;
		} else {
			/* Wait for the cache to populate */
			ast_debug(1, "Waiting for similar request for '%s@%s' for '%s'\n",
				dr.number,dr.dcontext,ast_eid_to_str(eid_str, sizeof(eid_str), &pending->root_eid));
			start = ast_tvnow();
			while(check_request(pending) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms) && (!chan || !ast_check_hangup(chan))) {
				/* XXX Would be nice to have a way to poll/select here XXX */
				/* XXX this is a busy wait loop!!! */
				usleep(1);
			}
			/* Continue on as normal, our cache should kick in */
		}
	}
	/* Create transactions */
	do {
		order = skipped;
		skipped = 0;
		foundcache = 0;
		build_transactions(&dr, ttl, order, &foundcache, &skipped, blockempty, cbypass, modeselect, skip, avoid, direct);
	} while (skipped && !foundcache && AST_LIST_EMPTY(&dr.trans));
	/* If no TTL, abort and return 0 now after setting TTL expired hint.  Couldn't
	   do this earlier because we didn't know if we were going to have transactions
	   or not. */
	if (!ttl) {
		ast_set_flag_nonstd(hmd, DUNDI_HINT_TTL_EXPIRED);
		abort_request(&dr);
		unregister_request(&dr);
		close(dr.pfds[0]);
		close(dr.pfds[1]);
		return 0;
	}

	/* Optimize transactions */
	optimize_transactions(&dr, order);
	/* Actually perform transactions */
	discover_transactions(&dr);
	/* Wait for transaction to come back */
	start = ast_tvnow();
	while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms) && (!chan || !ast_check_hangup(chan))) {
		ms = 100;
		ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL);
	}
	if (chan && ast_check_hangup(chan))
		ast_debug(1, "Hrm, '%s' hungup before their query for %s@%s finished\n", ast_channel_name(chan), dr.number, dr.dcontext);
	cancel_request(&dr);
	unregister_request(&dr);
	res = dr.respcount;
	*expiration = dr.expiration;
	close(dr.pfds[0]);
	close(dr.pfds[1]);
	return res;
}

int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int cbypass)
{
	struct dundi_hint_metadata hmd;
	dundi_eid *avoid[1] = { NULL, };
	int direct[1] = { 0, };
	int expiration = dundi_cache_time;
	memset(&hmd, 0, sizeof(hmd));
	hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
	return dundi_lookup_internal(result, maxret, chan, dcontext, number, dundi_ttl, 0, &hmd, &expiration, cbypass, 0, NULL, avoid, direct);
}

static void reschedule_precache(const char *number, const char *context, int expiration)
{
	struct dundi_precache_queue *qe, *prev;

	AST_LIST_LOCK(&pcq);
	AST_LIST_TRAVERSE_SAFE_BEGIN(&pcq, qe, list) {
		if (!strcmp(number, qe->number) && !strcasecmp(context, qe->context)) {
			AST_LIST_REMOVE_CURRENT(list);
			break;
		}
	}
	AST_LIST_TRAVERSE_SAFE_END;
	if (!qe) {
		int len = sizeof(*qe);
		int num_len = strlen(number) + 1;
		int context_len = strlen(context) + 1;
		if (!(qe = ast_calloc(1, len + num_len + context_len))) {
			AST_LIST_UNLOCK(&pcq);
			return;
		}
		strcpy(qe->number, number);
		qe->context = qe->number + num_len + 1;
		ast_copy_string(qe->context, context, context_len);
	}
	time(&qe->expiration);
	qe->expiration += expiration;
	if ((prev = AST_LIST_FIRST(&pcq))) {
		while (AST_LIST_NEXT(prev, list) && ((AST_LIST_NEXT(prev, list))->expiration <= qe->expiration))
			prev = AST_LIST_NEXT(prev, list);
		AST_LIST_INSERT_AFTER(&pcq, prev, qe, list);
	} else
		AST_LIST_INSERT_HEAD(&pcq, qe, list);
	AST_LIST_UNLOCK(&pcq);
}

static void dundi_precache_full(void)
{
	struct dundi_mapping *cur;
	struct ast_context *con;
	struct ast_exten *e;

	AST_LIST_TRAVERSE(&mappings, cur, list) {
		ast_log(LOG_NOTICE, "Should precache context '%s'\n", cur->dcontext);
		ast_rdlock_contexts();
		con = NULL;
		while ((con = ast_walk_contexts(con))) {
			if (strcasecmp(cur->lcontext, ast_get_context_name(con)))
				continue;
			/* Found the match, now queue them all up */
			ast_rdlock_context(con);
			e = NULL;
			while ((e = ast_walk_context_extensions(con, e)))
				reschedule_precache(ast_get_extension_name(e), cur->dcontext, 0);
			ast_unlock_context(con);
		}
		ast_unlock_contexts();
	}
}

static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[])
{
	struct dundi_request dr;
	struct dundi_hint_metadata hmd;
	struct dundi_result dr2[MAX_RESULTS];
	struct timeval start;
	struct dundi_mapping *maps = NULL, *cur;
	int nummaps = 0;
	int foundanswers;
	int foundcache, skipped, ttlms, ms;
	if (!context)
		context = "e164";
	ast_debug(1, "Precache internal (%s@%s)!\n", number, context);

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&mappings, cur, list) {
		if (!strcasecmp(cur->dcontext, context))
			nummaps++;
	}
	if (nummaps) {
		maps = ast_alloca(nummaps * sizeof(*maps));
		nummaps = 0;
		AST_LIST_TRAVERSE(&mappings, cur, list) {
			if (!strcasecmp(cur->dcontext, context))
				maps[nummaps++] = *cur;
		}
	}
	AST_LIST_UNLOCK(&peers);
	if (!nummaps) {
		return -1;
	}
	ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
	memset(&dr2, 0, sizeof(dr2));
	memset(&dr, 0, sizeof(dr));
	memset(&hmd, 0, sizeof(hmd));
	dr.dr = dr2;
	ast_copy_string(dr.number, number, sizeof(dr.number));
	ast_copy_string(dr.dcontext, context ? context : "e164", sizeof(dr.dcontext));
	dr.maxcount = MAX_RESULTS;
	dr.expiration = dundi_cache_time;
	dr.hmd = &hmd;
	dr.pfds[0] = dr.pfds[1] = -1;
	if (pipe(dr.pfds) < 0) {
		ast_log(LOG_WARNING, "pipe() failed: %s\n", strerror(errno));
		return -1;
	}
	build_transactions(&dr, ttl, 0, &foundcache, &skipped, 0, 1, 1, NULL, avoids, NULL);
	optimize_transactions(&dr, 0);
	foundanswers = 0;
	precache_transactions(&dr, maps, nummaps, &dr.expiration, &foundanswers);
	if (foundanswers) {
		if (dr.expiration > 0)
			reschedule_precache(dr.number, dr.dcontext, dr.expiration);
		else
			ast_log(LOG_NOTICE, "Weird, expiration = %d, but need to precache for %s@%s?!\n", dr.expiration, dr.number, dr.dcontext);
	}
	start = ast_tvnow();
	while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms)) {
		if (dr.pfds[0] > -1) {
			ms = 100;
			ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL);
		} else
			usleep(1);
	}
	cancel_request(&dr);
	if (dr.pfds[0] > -1) {
		close(dr.pfds[0]);
		close(dr.pfds[1]);
	}
	return 0;
}

int dundi_precache(const char *context, const char *number)
{
	dundi_eid *avoid[1] = { NULL, };
	return dundi_precache_internal(context, number, dundi_ttl, avoid);
}

static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *dcontext, dundi_eid *eid, struct dundi_hint_metadata *hmd, int ttl, int blockempty, dundi_eid *avoid[])
{
	int res;
	struct dundi_request dr;
	dundi_eid *rooteid=NULL;
	int x;
	int ttlms;
	int skipped=0;
	int foundcache=0;
	struct timeval start;

	ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;

	for (x=0;avoid[x];x++)
		rooteid = avoid[x];
	/* Now perform real check */
	memset(&dr, 0, sizeof(dr));
	dr.hmd = hmd;
	dr.dei = dei;
	dr.pfds[0] = dr.pfds[1] = -1;
	ast_copy_string(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext));
	memcpy(&dr.query_eid, eid, sizeof(dr.query_eid));
	if (rooteid)
		dr.root_eid = *rooteid;
	/* Create transactions */
	build_transactions(&dr, ttl, 9999, &foundcache, &skipped, blockempty, 0, 0, NULL, avoid, NULL);

	/* If no TTL, abort and return 0 now after setting TTL expired hint.  Couldn't
	   do this earlier because we didn't know if we were going to have transactions
	   or not. */
	if (!ttl) {
		ast_set_flag_nonstd(hmd, DUNDI_HINT_TTL_EXPIRED);
		return 0;
	}

	/* Optimize transactions */
	optimize_transactions(&dr, 9999);
	/* Actually perform transactions */
	query_transactions(&dr);
	/* Wait for transaction to come back */
	start = ast_tvnow();
	while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms))
		usleep(1);
	res = dr.respcount;
	return res;
}

int dundi_query_eid(struct dundi_entity_info *dei, const char *dcontext, dundi_eid eid)
{
	dundi_eid *avoid[1] = { NULL, };
	struct dundi_hint_metadata hmd;
	memset(&hmd, 0, sizeof(hmd));
	return dundi_query_eid_internal(dei, dcontext, &eid, &hmd, dundi_ttl, 0, avoid);
}

enum {
	OPT_BYPASS_CACHE = (1 << 0),
};

AST_APP_OPTIONS(dundi_query_opts, BEGIN_OPTIONS
	AST_APP_OPTION('b', OPT_BYPASS_CACHE),
END_OPTIONS );

static int dundifunc_read(struct ast_channel *chan, const char *cmd, char *num, char *buf, size_t len)
{
	int results;
	int x;
	struct dundi_result dr[MAX_RESULTS];
	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(number);
		AST_APP_ARG(context);
		AST_APP_ARG(options);
	);
	char *parse;
	struct ast_flags opts = { 0, };

	buf[0] = '\0';

	if (ast_strlen_zero(num)) {
		ast_log(LOG_WARNING, "DUNDILOOKUP requires an argument (number)\n");
		return -1;
	}

	parse = ast_strdupa(num);

	AST_STANDARD_APP_ARGS(args, parse);

	if (!ast_strlen_zero(args.options)) {
		ast_app_parse_options(dundi_query_opts, &opts, NULL, args.options);
	}
	if (ast_strlen_zero(args.context)) {
		args.context = "e164";
	}

	results = dundi_lookup(dr, MAX_RESULTS, NULL, args.context, args.number, ast_test_flag(&opts, OPT_BYPASS_CACHE));
	if (results > 0) {
		sort_results(dr, results);
		for (x = 0; x < results; x++) {
			if (ast_test_flag(dr + x, DUNDI_FLAG_EXISTS)) {
				snprintf(buf, len, "%s/%s", dr[x].tech, dr[x].dest);
				break;
			}
		}
	}

	return 0;
}

/*! DUNDILOOKUP
 * \ingroup functions
*/

static struct ast_custom_function dundi_function = {
	.name = "DUNDILOOKUP",
	.read = dundifunc_read,
};

static unsigned int dundi_result_id;

struct dundi_result_datastore {
	struct dundi_result results[MAX_RESULTS];
	int num_results;
	unsigned int id;
};

static void drds_destroy(struct dundi_result_datastore *drds)
{
	ast_free(drds);
}

static void drds_destroy_cb(void *data)
{
	struct dundi_result_datastore *drds = data;
	drds_destroy(drds);
}

static const struct ast_datastore_info dundi_result_datastore_info = {
	.type = "DUNDIQUERY",
	.destroy = drds_destroy_cb,
};

static int dundi_query_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(number);
		AST_APP_ARG(context);
		AST_APP_ARG(options);
	);
	struct ast_flags opts = { 0, };
	char *parse;
	struct dundi_result_datastore *drds;
	struct ast_datastore *datastore;

	if (ast_strlen_zero(data)) {
		ast_log(LOG_WARNING, "DUNDIQUERY requires an argument (number)\n");
		return -1;
	}

	if (!chan) {
		ast_log(LOG_ERROR, "DUNDIQUERY can not be used without a channel!\n");
		return -1;
	}

	parse = ast_strdupa(data);

	AST_STANDARD_APP_ARGS(args, parse);

	if (!ast_strlen_zero(args.options))
		ast_app_parse_options(dundi_query_opts, &opts, NULL, args.options);

	if (ast_strlen_zero(args.context))
		args.context = "e164";

	if (!(drds = ast_calloc(1, sizeof(*drds)))) {
		return -1;
	}

	drds->id = ast_atomic_fetchadd_int((int *) &dundi_result_id, 1);
	snprintf(buf, len, "%u", drds->id);

	if (!(datastore = ast_datastore_alloc(&dundi_result_datastore_info, buf))) {
		drds_destroy(drds);
		return -1;
	}

	datastore->data = drds;

	drds->num_results = dundi_lookup(drds->results, ARRAY_LEN(drds->results), NULL, args.context,
		args.number, ast_test_flag(&opts, OPT_BYPASS_CACHE));

	if (drds->num_results > 0)
		sort_results(drds->results, drds->num_results);

	ast_channel_lock(chan);
	ast_channel_datastore_add(chan, datastore);
	ast_channel_unlock(chan);

	return 0;
}

static struct ast_custom_function dundi_query_function = {
	.name = "DUNDIQUERY",
	.read = dundi_query_read,
};

static int dundi_result_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(id);
		AST_APP_ARG(resultnum);
	);
	char *parse;
	unsigned int num;
	struct dundi_result_datastore *drds;
	struct ast_datastore *datastore;
	int res = -1;

	if (ast_strlen_zero(data)) {
		ast_log(LOG_WARNING, "DUNDIRESULT requires an argument (id and resultnum)\n");
		goto finish;
	}

	if (!chan) {
		ast_log(LOG_ERROR, "DUNDRESULT can not be used without a channel!\n");
		goto finish;
	}

	parse = ast_strdupa(data);

	AST_STANDARD_APP_ARGS(args, parse);

	if (ast_strlen_zero(args.id)) {
		ast_log(LOG_ERROR, "A result ID must be provided to DUNDIRESULT\n");
		goto finish;
	}

	if (ast_strlen_zero(args.resultnum)) {
		ast_log(LOG_ERROR, "A result number must be given to DUNDIRESULT!\n");
		goto finish;
	}

	ast_channel_lock(chan);
	datastore = ast_channel_datastore_find(chan, &dundi_result_datastore_info, args.id);
	ast_channel_unlock(chan);

	if (!datastore) {
		ast_log(LOG_WARNING, "No DUNDi results found for query ID '%s'\n", args.id);
		goto finish;
	}

	drds = datastore->data;

	if (!strcasecmp(args.resultnum, "getnum")) {
		snprintf(buf, len, "%d", drds->num_results < 0 ? 0 : drds->num_results);
		res = 0;
		goto finish;
	}

	if (sscanf(args.resultnum, "%30u", &num) != 1) {
		ast_log(LOG_ERROR, "Invalid value '%s' for resultnum to DUNDIRESULT!\n",
			args.resultnum);
		goto finish;
	}

	if (num && drds->num_results > 0 && num <= drds->num_results) {
		snprintf(buf, len, "%s/%s", drds->results[num - 1].tech, drds->results[num - 1].dest);
		res = 0;
	} else
		ast_log(LOG_WARNING, "Result number %u is not valid for DUNDi query results for ID %s!\n", num, args.id);

finish:
	return res;
}

static struct ast_custom_function dundi_result_function = {
	.name = "DUNDIRESULT",
	.read = dundi_result_read,
};

static void mark_peers(void)
{
	struct dundi_peer *peer;
	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&peers, peer, list) {
		peer->dead = 1;
	}
	AST_LIST_UNLOCK(&peers);
}

static void mark_mappings(void)
{
	struct dundi_mapping *map;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&mappings, map, list) {
		map->dead = 1;
	}
	AST_LIST_UNLOCK(&peers);
}

static void destroy_permissions(struct permissionlist *permlist)
{
	struct permission *perm;

	while ((perm = AST_LIST_REMOVE_HEAD(permlist, list)))
		ast_free(perm);
}

static void destroy_peer(struct dundi_peer *peer)
{
	int idx;

	AST_SCHED_DEL(sched, peer->registerexpire);
	AST_SCHED_DEL(sched, peer->registerid);
	if (peer->regtrans) {
		destroy_trans(peer->regtrans, 0);
	}
	AST_SCHED_DEL(sched, peer->qualifyid);
	if (peer->qualtrans) {
		destroy_trans(peer->qualtrans, 0);
	}
	destroy_permissions(&peer->permit);
	destroy_permissions(&peer->include);

	/* Release lookup history */
	for (idx = 0; idx < ARRAY_LEN(peer->lookups); ++idx) {
		ast_free(peer->lookups[idx]);
	}

	ast_free(peer);
}

static void destroy_map(struct dundi_mapping *map)
{
	ast_free(map->weightstr);
	ast_free(map);
}

static void prune_peers(void)
{
	struct dundi_peer *peer;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, peer, list) {
		if (peer->dead) {
			AST_LIST_REMOVE_CURRENT(list);
			destroy_peer(peer);
		}
	}
	AST_LIST_TRAVERSE_SAFE_END;
	AST_LIST_UNLOCK(&peers);
}

static void prune_mappings(void)
{
	struct dundi_mapping *map;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE_SAFE_BEGIN(&mappings, map, list) {
		if (map->dead) {
			AST_LIST_REMOVE_CURRENT(list);
			destroy_map(map);
		}
	}
	AST_LIST_TRAVERSE_SAFE_END;
	AST_LIST_UNLOCK(&peers);
}

static void append_permission(struct permissionlist *permlist, const char *s, int allow)
{
	struct permission *perm;

	if (!(perm = ast_calloc(1, sizeof(*perm) + strlen(s) + 1)))
		return;

	strcpy(perm->name, s);
	perm->allow = allow;

	AST_LIST_INSERT_TAIL(permlist, perm, list);
}

#define MAX_OPTS 128

static void build_mapping(const char *name, const char *value)
{
	char *t, *fields[MAX_OPTS];
	struct dundi_mapping *map;
	int x;
	int y;

	t = ast_strdupa(value);

	AST_LIST_TRAVERSE(&mappings, map, list) {
		/* Find a double match */
		if (!strcasecmp(map->dcontext, name) &&
			(!strncasecmp(map->lcontext, value, strlen(map->lcontext)) &&
			  (!value[strlen(map->lcontext)] ||
			   (value[strlen(map->lcontext)] == ','))))
			break;
	}
	if (!map) {
		if (!(map = ast_calloc(1, sizeof(*map))))
			return;
		AST_LIST_INSERT_HEAD(&mappings, map, list);
		map->dead = 1;
	}
	map->options = 0;
	memset(fields, 0, sizeof(fields));
	x = 0;
	while (t && x < MAX_OPTS) {
		fields[x++] = t;
		t = strchr(t, ',');
		if (t) {
			*t = '\0';
			t++;
		}
	} /* Russell was here, arrrr! */
	if ((x == 1) && ast_strlen_zero(fields[0])) {
		/* Placeholder mapping */
		ast_copy_string(map->dcontext, name, sizeof(map->dcontext));
		map->dead = 0;
	} else if (x >= 4) {
		ast_copy_string(map->dcontext, name, sizeof(map->dcontext));
		ast_copy_string(map->lcontext, fields[0], sizeof(map->lcontext));
		if ((sscanf(fields[1], "%30d", &map->_weight) == 1) && (map->_weight >= 0) && (map->_weight <= MAX_WEIGHT)) {
			ast_copy_string(map->dest, fields[3], sizeof(map->dest));
			if ((map->tech = str2tech(fields[2])))
				map->dead = 0;
		} else if (!strncmp(fields[1], "${", 2) && fields[1][strlen(fields[1]) - 1] == '}') {
			map->weightstr = ast_strdup(fields[1]);
			ast_copy_string(map->dest, fields[3], sizeof(map->dest));
			if ((map->tech = str2tech(fields[2])))
				map->dead = 0;
		} else {
			ast_log(LOG_WARNING, "Invalid weight '%s' specified, deleting entry '%s/%s'\n", fields[1], map->dcontext, map->lcontext);
		}
		for (y = 4;y < x; y++) {
			if (!strcasecmp(fields[y], "nounsolicited"))
				map->options |= DUNDI_FLAG_NOUNSOLICITED;
			else if (!strcasecmp(fields[y], "nocomunsolicit"))
				map->options |= DUNDI_FLAG_NOCOMUNSOLICIT;
			else if (!strcasecmp(fields[y], "residential"))
				map->options |= DUNDI_FLAG_RESIDENTIAL;
			else if (!strcasecmp(fields[y], "commercial"))
				map->options |= DUNDI_FLAG_COMMERCIAL;
			else if (!strcasecmp(fields[y], "mobile"))
				map->options |= DUNDI_FLAG_MOBILE;
			else if (!strcasecmp(fields[y], "nopartial"))
				map->options |= DUNDI_FLAG_INTERNAL_NOPARTIAL;
			else
				ast_log(LOG_WARNING, "Don't know anything about option '%s'\n", fields[y]);
		}
	} else
		ast_log(LOG_WARNING, "Expected at least %d arguments in map, but got only %d\n", 4, x);
}

/*! \note Called with the peers list already locked */
static int do_register(const void *data)
{
	struct dundi_ie_data ied;
	struct dundi_peer *peer = (struct dundi_peer *)data;
	char eid_str[20];
	char eid_str2[20];
	ast_debug(1, "Register us as '%s' to '%s'\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->us_eid), ast_eid_to_str(eid_str2, sizeof(eid_str2), &peer->eid));
	peer->registerid = ast_sched_add(sched, default_expiration * 1000, do_register, data);
	/* Destroy old transaction if there is one */
	if (peer->regtrans)
		destroy_trans(peer->regtrans, 0);
	peer->regtrans = create_transaction(peer);
	if (peer->regtrans) {
		ast_set_flag(peer->regtrans, FLAG_ISREG);
		memset(&ied, 0, sizeof(ied));
		dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
		dundi_ie_append_eid(&ied, DUNDI_IE_EID, &peer->regtrans->us_eid);
		dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, default_expiration);
		dundi_send(peer->regtrans, DUNDI_COMMAND_REGREQ, 0, 0, &ied);

	} else
		ast_log(LOG_NOTICE, "Unable to create new transaction for registering to '%s'!\n", ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));

	return 0;
}

static int do_qualify(const void *data)
{
	struct dundi_peer *peer = (struct dundi_peer *)data;
	peer->qualifyid = -1;
	qualify_peer(peer, 0);
	return 0;
}

static void qualify_peer(struct dundi_peer *peer, int schedonly)
{
	int when;
	AST_SCHED_DEL(sched, peer->qualifyid);
	if (peer->qualtrans)
		destroy_trans(peer->qualtrans, 0);
	peer->qualtrans = NULL;
	if (peer->maxms > 0) {
		when = 60000;
		if (peer->lastms < 0)
			when = 10000;
		if (schedonly)
			when = 5000;
		peer->qualifyid = ast_sched_add(sched, when, do_qualify, peer);
		if (!schedonly)
			peer->qualtrans = create_transaction(peer);
		if (peer->qualtrans) {
			peer->qualtx = ast_tvnow();
			ast_set_flag(peer->qualtrans, FLAG_ISQUAL);
			dundi_send(peer->qualtrans, DUNDI_COMMAND_NULL, 0, 1, NULL);
		}
	}
}
static void populate_addr(struct dundi_peer *peer, dundi_eid *eid)
{
	char data[256];
	char *c;
	int port, expire;
	char eid_str[20];
	ast_eid_to_str(eid_str, sizeof(eid_str), eid);
	if (!ast_db_get("dundi/dpeers", eid_str, data, sizeof(data))) {
		/*
		 * data is in the form:
		 * IPv6 address: [ffff:ffff::ffff:ffff]:port:expire
		 * IPv4 address: a.b.c.d:port:expire
		 */
		c = data;
		if (*c == '[') {
			/* Need to skip over the IPv6 address. */
			c = strchr(c, ']');
		}
		if (c) {
			c = strchr(c, ':');
		}
		if (c) {
			*c = '\0';
			c++;
			if (sscanf(c, "%5d:%30d", &port, &expire) == 2) {
				/* Got it! */
				struct ast_sockaddr *addrs;

				if (ast_sockaddr_resolve(&addrs, data, PARSE_PORT_FORBID, AF_UNSPEC) > 0){
					ast_sockaddr_copy(&peer->addr, &addrs[0]);
					ast_free(addrs);
				}
				ast_sockaddr_set_port(&peer->addr, port);
				peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
			}
		}
	}
}


static void build_peer(dundi_eid *eid, struct ast_variable *v, int *globalpcmode)
{
	struct dundi_peer *peer;
	dundi_eid testeid;
	int needregister=0;
	char eid_str[20];
	int port = 0;

	AST_LIST_LOCK(&peers);
	AST_LIST_TRAVERSE(&peers, peer, list) {
		if (!ast_eid_cmp(&peer->eid, eid)) {
			break;
		}
	}
	if (!peer) {
		/* Add us into the list */
		if (!(peer = ast_calloc(1, sizeof(*peer)))) {
			AST_LIST_UNLOCK(&peers);
			return;
		}
		peer->registerid = -1;
		peer->registerexpire = -1;
		peer->qualifyid = -1;
		populate_addr(peer, eid);
		AST_LIST_INSERT_HEAD(&peers, peer, list);
	}
	peer->dead = 0;
	peer->eid = *eid;
	peer->us_eid = global_eid;
	destroy_permissions(&peer->permit);
	destroy_permissions(&peer->include);
	AST_SCHED_DEL(sched, peer->registerid);
	for (; v; v = v->next) {
		if (!strcasecmp(v->name, "inkey")) {
			ast_copy_string(peer->inkey, v->value, sizeof(peer->inkey));
		} else if (!strcasecmp(v->name, "outkey")) {
			ast_copy_string(peer->outkey, v->value, sizeof(peer->outkey));
		} else if (!strcasecmp(v->name, "port")) {
			port = atoi(v->value);
		} else if (!strcasecmp(v->name, "host")) {
			if (!strcasecmp(v->value, "dynamic")) {
				peer->dynamic = 1;
			} else {
				struct ast_sockaddr *addrs;

				if (ast_sockaddr_resolve(&addrs, v->value, PARSE_PORT_FORBID, AF_UNSPEC) > 0) {
					ast_sockaddr_copy(&peer->addr, &addrs[0]);
					peer->dynamic = 0;
					ast_free(addrs);
				} else {
					ast_log(LOG_WARNING, "Unable to find host '%s' at line %d\n", v->value, v->lineno);
					peer->dead = 1;
				}
			}
		} else if (!strcasecmp(v->name, "ustothem")) {
			if (!ast_str_to_eid(&testeid, v->value))
				peer->us_eid = testeid;
			else
				ast_log(LOG_WARNING, "'%s' is not a valid DUNDi Entity Identifier at line %d\n", v->value, v->lineno);
		} else if (!strcasecmp(v->name, "include")) {
			append_permission(&peer->include, v->value, 1);
		} else if (!strcasecmp(v->name, "permit")) {
			append_permission(&peer->permit, v->value, 1);
		} else if (!strcasecmp(v->name, "noinclude")) {
			append_permission(&peer->include, v->value, 0);
		} else if (!strcasecmp(v->name, "deny")) {
			append_permission(&peer->permit, v->value, 0);
		} else if (!strcasecmp(v->name, "register")) {
			needregister = ast_true(v->value);
		} else if (!strcasecmp(v->name, "order")) {
			if (!strcasecmp(v->value, "primary"))
				peer->order = 0;
			else if (!strcasecmp(v->value, "secondary"))
				peer->order = 1;
			else if (!strcasecmp(v->value, "tertiary"))
				peer->order = 2;
			else if (!strcasecmp(v->value, "quartiary"))
				peer->order = 3;
			else {
				ast_log(LOG_WARNING, "'%s' is not a valid order, should be primary, secondary, tertiary or quartiary at line %d\n", v->value, v->lineno);
			}
		} else if (!strcasecmp(v->name, "qualify")) {
			if (!strcasecmp(v->value, "no")) {
				peer->maxms = 0;
			} else if (!strcasecmp(v->value, "yes")) {
				peer->maxms = DEFAULT_MAXMS;
			} else if (sscanf(v->value, "%30d", &peer->maxms) != 1) {
				ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of dundi.conf\n",
					ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), v->lineno);
				peer->maxms = 0;
			}
		} else if (!strcasecmp(v->name, "model")) {
			if (!strcasecmp(v->value, "inbound"))
				peer->model = DUNDI_MODEL_INBOUND;
			else if (!strcasecmp(v->value, "outbound"))
				peer->model = DUNDI_MODEL_OUTBOUND;
			else if (!strcasecmp(v->value, "symmetric"))
				peer->model = DUNDI_MODEL_SYMMETRIC;
			else if (!strcasecmp(v->value, "none"))
				peer->model = 0;
			else {
				ast_log(LOG_WARNING, "Unknown model '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n",
					v->value, v->lineno);
			}
		} else if (!strcasecmp(v->name, "precache")) {
			if (!strcasecmp(v->value, "inbound"))
				peer->pcmodel = DUNDI_MODEL_INBOUND;
			else if (!strcasecmp(v->value, "outbound"))
				peer->pcmodel = DUNDI_MODEL_OUTBOUND;
			else if (!strcasecmp(v->value, "symmetric"))
				peer->pcmodel = DUNDI_MODEL_SYMMETRIC;
			else if (!strcasecmp(v->value, "none"))
				peer->pcmodel = 0;
			else {
				ast_log(LOG_WARNING, "Unknown pcmodel '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n",
					v->value, v->lineno);
			}
		}
	}

	if (!ast_sockaddr_isnull(&peer->addr)) {
		ast_sockaddr_set_port(&peer->addr, (0 < port) ? port : DUNDI_PORT);
	}

	(*globalpcmode) |= peer->pcmodel;
	if (!peer->model && !peer->pcmodel) {
		ast_log(LOG_WARNING, "Peer '%s' lacks a model or pcmodel, discarding!\n",
			ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		peer->dead = 1;
	} else if ((peer->model & DUNDI_MODEL_INBOUND) && (peer->pcmodel & DUNDI_MODEL_OUTBOUND)) {
		ast_log(LOG_WARNING, "Peer '%s' may not be both inbound/symmetric model and outbound/symmetric precache model, discarding!\n",
			ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		peer->dead = 1;
	} else if ((peer->model & DUNDI_MODEL_OUTBOUND) && (peer->pcmodel & DUNDI_MODEL_INBOUND)) {
		ast_log(LOG_WARNING, "Peer '%s' may not be both outbound/symmetric model and inbound/symmetric precache model, discarding!\n",
			ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
		peer->dead = 1;
	} else if (!AST_LIST_EMPTY(&peer->include) && !(peer->model & DUNDI_MODEL_OUTBOUND) && !(peer->pcmodel & DUNDI_MODEL_INBOUND)) {
		ast_log(LOG_WARNING, "Peer '%s' is supposed to be included in outbound searches but isn't an outbound peer or inbound precache!\n",
			ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
	} else if (!AST_LIST_EMPTY(&peer->permit) && !(peer->model & DUNDI_MODEL_INBOUND) && !(peer->pcmodel & DUNDI_MODEL_OUTBOUND)) {
		ast_log(LOG_WARNING, "Peer '%s' is supposed to have permission for some inbound searches but isn't an inbound peer or outbound precache!\n",
			ast_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
	} else {
		if (ast_eid_cmp(&peer->eid, &empty_eid)) {
			/* Schedule any items for explicitly configured peers. */
			if (needregister) {
				peer->registerid = ast_sched_add(sched, 2000, do_register, peer);
			}
			qualify_peer(peer, 1);
		}
	}
	AST_LIST_UNLOCK(&peers);
}

static int dundi_helper(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *data, int flag)
{
	struct dundi_result results[MAX_RESULTS];
	int res;
	int x;
	int found = 0;
	if (!strncasecmp(context, "macro-", 6)) {
		if (!chan) {
			ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
			return -1;
		}
		/* If done as a macro, use macro extension */
		if (!strcasecmp(exten, "s")) {
			exten = pbx_builtin_getvar_helper(chan, "ARG1");
			if (ast_strlen_zero(exten))
				exten = ast_channel_macroexten(chan);
			if (ast_strlen_zero(exten))
				exten = ast_channel_exten(chan);
			if (ast_strlen_zero(exten)) {
				ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
				return -1;
			}
		}
		if (ast_strlen_zero(data))
			data = "e164";
	} else {
		if (ast_strlen_zero(data))
			data = context;
	}
	res = dundi_lookup(results, MAX_RESULTS, chan, data, exten, 0);
	for (x=0;x<res;x++) {
		if (ast_test_flag(results + x, flag))
			found++;
	}
	if (found >= priority)
		return 1;
	return 0;
}

static int dundi_exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
{
	return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_EXISTS);
}

static int dundi_canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
{
	return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_CANMATCH);
}

static int dundi_exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
{
	struct dundi_result results[MAX_RESULTS];
	int res;
	int x=0;
	char req[1024];
	const char *dundiargs;
	struct ast_app *dial;

	if (!strncasecmp(context, "macro-", 6)) {
		if (!chan) {
			ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
			return -1;
		}
		/* If done as a macro, use macro extension */
		if (!strcasecmp(exten, "s")) {
			exten = pbx_builtin_getvar_helper(chan, "ARG1");
			if (ast_strlen_zero(exten))
				exten = ast_channel_macroexten(chan);
			if (ast_strlen_zero(exten))
				exten = ast_channel_exten(chan);
			if (ast_strlen_zero(exten)) {
				ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
				return -1;
			}
		}
		if (ast_strlen_zero(data))
			data = "e164";
	} else {
		if (ast_strlen_zero(data))
			data = context;
	}
	res = dundi_lookup(results, MAX_RESULTS, chan, data, exten, 0);
	if (res > 0) {
		sort_results(results, res);
		for (x=0;x<res;x++) {
			if (ast_test_flag(results + x, DUNDI_FLAG_EXISTS)) {
				if (!--priority)
					break;
			}
		}
	}
	if (x < res) {
		/* Got a hit! */
		dundiargs = pbx_builtin_getvar_helper(chan, "DUNDIDIALARGS");
		snprintf(req, sizeof(req), "%s/%s,,%s", results[x].tech, results[x].dest,
			S_OR(dundiargs, ""));
		dial = pbx_findapp("Dial");
		if (dial)
			res = pbx_exec(chan, dial, req);
	} else
		res = -1;
	return res;
}

static int dundi_matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
{
	return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_MATCHMORE);
}

static struct ast_switch dundi_switch = {
	.name        = "DUNDi",
	.description = "DUNDi Discovered Dialplan Switch",
	.exists      = dundi_exists,
	.canmatch    = dundi_canmatch,
	.exec        = dundi_exec,
	.matchmore   = dundi_matchmore,
};

static int get_ipaddress(char *ip, size_t size, const char *str, int family)
{
	struct ast_sockaddr *addrs;

	if (!ast_sockaddr_resolve(&addrs, str, 0, family)) {
		return -1;
	}

	ast_copy_string(ip, ast_sockaddr_stringify_host(&addrs[0]), size);
	ast_free(addrs);

	return 0;
}

static void set_host_ipaddr(struct ast_sockaddr *sin)
{
	char hn[MAXHOSTNAMELEN];
	struct addrinfo hints;
	int family;

	memset(&hints, 0, sizeof(hints));

	if (ast_sockaddr_is_ipv6(sin)) {
		family = AF_INET6;
	} else {
		family = AF_INET;
	}

	if (gethostname(hn, sizeof(hn) - 1) < 0) {
		ast_log(LOG_WARNING, "Unable to get host name!\n");
		return;
	}

	get_ipaddress(ipaddr, sizeof(ipaddr), hn, family);
}

static int set_config(char *config_file, struct ast_sockaddr *sin, int reload, struct ast_sockaddr *sin2)
{
	struct ast_config *cfg;
	struct ast_variable *v;
	char *cat;
	int x;
	struct ast_flags config_flags = { 0 };
	static int last_port = 0;
	int port = 0;
	int globalpcmodel = 0;
	dundi_eid testeid;
	char bind_addr[80]={0,};
	char bind_addr2[80]={0,};

	if (!(cfg = ast_config_load(config_file, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
		ast_log(LOG_ERROR, "Unable to load config %s\n", config_file);
		return -1;
	}

	dundi_ttl = DUNDI_DEFAULT_TTL;
	dundi_cache_time = DUNDI_DEFAULT_CACHE_TIME;
	any_peer = NULL;

	AST_LIST_LOCK(&peers);

	if (ast_eid_is_empty(&ast_eid_default)) {
		ast_log(LOG_WARNING, "Entity ID is not set.\n");
	}
	memcpy(&global_eid, &ast_eid_default, sizeof(global_eid));

	global_storehistory = 0;
	ast_copy_string(secretpath, "dundi", sizeof(secretpath));
	v = ast_variable_browse(cfg, "general");
	while(v) {
		if (!strcasecmp(v->name, "port")){
			port = atoi(v->value);
		} else if (!strcasecmp(v->name, "bindaddr")) {
			if (get_ipaddress(bind_addr, sizeof(bind_addr), v->value, AF_UNSPEC) == 0) {
				if (!ast_sockaddr_parse(sin, bind_addr, 0)) {
					ast_log(LOG_WARNING, "Invalid host/IP '%s'\n", v->value);
				}
			}
		} else if (!strcasecmp(v->name, "bindaddr2")) {
			if (get_ipaddress(bind_addr2, sizeof(bind_addr2), v->value, AF_UNSPEC) == 0) {
				if (!ast_sockaddr_parse(sin2, bind_addr2, 0)) {
					ast_log(LOG_WARNING, "Invalid host/IP '%s'\n", v->value);
				}
			}
		} else if (!strcasecmp(v->name, "authdebug")) {
			authdebug = ast_true(v->value);
		} else if (!strcasecmp(v->name, "ttl")) {
			if ((sscanf(v->value, "%30d", &x) == 1) && (x > 0) && (x < DUNDI_DEFAULT_TTL)) {
				dundi_ttl = x;
			} else {
				ast_log(LOG_WARNING, "'%s' is not a valid TTL at line %d, must be number from 1 to %d\n",
					v->value, v->lineno, DUNDI_DEFAULT_TTL);
			}
		} else if (!strcasecmp(v->name, "autokill")) {
			if (sscanf(v->value, "%30d", &x) == 1) {
				if (x >= 0)
					global_autokilltimeout = x;
				else
					ast_log(LOG_NOTICE, "Nice try, but autokill has to be >0 or 'yes' or 'no' at line %d\n", v->lineno);
			} else if (ast_true(v->value)) {
				global_autokilltimeout = DEFAULT_MAXMS;
			} else {
				global_autokilltimeout = 0;
			}
		} else if (!strcasecmp(v->name, "entityid")) {
			if (!ast_str_to_eid(&testeid, v->value))
				global_eid = testeid;
			else
				ast_log(LOG_WARNING, "Invalid global endpoint identifier '%s' at line %d\n", v->value, v->lineno);
		} else if (!strcasecmp(v->name, "tos")) {
			if (ast_str2tos(v->value, &tos))
				ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
		} else if (!strcasecmp(v->name, "department")) {
			ast_copy_string(dept, v->value, sizeof(dept));
		} else if (!strcasecmp(v->name, "organization")) {
			ast_copy_string(org, v->value, sizeof(org));
		} else if (!strcasecmp(v->name, "locality")) {
			ast_copy_string(locality, v->value, sizeof(locality));
		} else if (!strcasecmp(v->name, "stateprov")) {
			ast_copy_string(stateprov, v->value, sizeof(stateprov));
		} else if (!strcasecmp(v->name, "country")) {
			ast_copy_string(country, v->value, sizeof(country));
		} else if (!strcasecmp(v->name, "email")) {
			ast_copy_string(email, v->value, sizeof(email));
		} else if (!strcasecmp(v->name, "phone")) {
			ast_copy_string(phone, v->value, sizeof(phone));
		} else if (!strcasecmp(v->name, "storehistory")) {
			global_storehistory = ast_true(v->value);
		} else if (!strcasecmp(v->name, "cachetime")) {
			if ((sscanf(v->value, "%30d", &x) == 1)) {
				dundi_cache_time = x;
			} else {
				ast_log(LOG_WARNING, "'%s' is not a valid cache time at line %d. Using default value '%d'.\n",
					v->value, v->lineno, DUNDI_DEFAULT_CACHE_TIME);
			}
		}
		v = v->next;
	}

	if (port == 0) {
		port = DUNDI_PORT;
	}

	if (ast_sockaddr_isnull(sin)) {
		sprintf(bind_addr, "0.0.0.0:%d", port);
		ast_sockaddr_parse(sin, bind_addr, 0);
	} else {
		ast_sockaddr_set_port(sin, port);
	}

	if (last_port == 0) {
		last_port = port;
	} else if (last_port != port) {
		ast_log(LOG_WARNING, "change to port ignored until next asterisk re-start\n");
	}

	set_host_ipaddr(sin);

	if (!ast_sockaddr_isnull(sin2)) {
		ast_sockaddr_set_port(sin2, port);
	}

	AST_LIST_UNLOCK(&peers);

	mark_mappings();
	v = ast_variable_browse(cfg, "mappings");
	while (v) {
		AST_LIST_LOCK(&peers);
		build_mapping(v->name, v->value);
		AST_LIST_UNLOCK(&peers);
		v = v->next;
	}
	prune_mappings();

	mark_peers();
	cat = ast_category_browse(cfg, NULL);
	while(cat) {
		if (strcasecmp(cat, "general") && strcasecmp(cat, "mappings")) {
			/* Entries */
			if (!ast_str_to_eid(&testeid, cat))
				build_peer(&testeid, ast_variable_browse(cfg, cat), &globalpcmodel);
			else if (!strcasecmp(cat, "*")) {
				build_peer(&empty_eid, ast_variable_browse(cfg, cat), &globalpcmodel);
				any_peer = find_peer(NULL);
			} else
				ast_log(LOG_NOTICE, "Ignoring invalid EID entry '%s'\n", cat);
		}
		cat = ast_category_browse(cfg, cat);
	}
	prune_peers();

	ast_config_destroy(cfg);
	load_password();
	if (globalpcmodel & DUNDI_MODEL_OUTBOUND)
		dundi_precache_full();
	return 0;
}

static int unload_module(void)
{
	ast_cli_unregister_multiple(cli_dundi, ARRAY_LEN(cli_dundi));
	ast_unregister_switch(&dundi_switch);
	ast_custom_function_unregister(&dundi_function);
	ast_custom_function_unregister(&dundi_query_function);
	ast_custom_function_unregister(&dundi_result_function);

	/* Stop all currently running threads */
	dundi_shutdown = 1;
	if (netthreadid != AST_PTHREADT_NULL) {
		pthread_kill(netthreadid, SIGURG);
		pthread_join(netthreadid, NULL);
		netthreadid = AST_PTHREADT_NULL;
	}
	if (precachethreadid != AST_PTHREADT_NULL) {
		pthread_kill(precachethreadid, SIGURG);
		pthread_join(precachethreadid, NULL);
		precachethreadid = AST_PTHREADT_NULL;
	}
 	if (clearcachethreadid != AST_PTHREADT_NULL) {
 		pthread_cancel(clearcachethreadid);
 		pthread_join(clearcachethreadid, NULL);
		clearcachethreadid = AST_PTHREADT_NULL;
 	}

	if (netsocket >= 0) {
		close(netsocket);
	}

	if (netsocket2 >= 0) {
		close(netsocket2);
	}

	mark_mappings();
	prune_mappings();
	mark_peers();
	prune_peers();

	if (-1 < netsocket) {
		close(netsocket);
		netsocket = -1;
	}
	if (io) {
		io_context_destroy(io);
		io = NULL;
	}

	if (sched) {
		ast_sched_context_destroy(sched);
		sched = NULL;
	}

	return 0;
}

static int reload(void)
{
	struct ast_sockaddr sin;
	struct ast_sockaddr sin2;

	ast_sockaddr_setnull(&sin);
	ast_sockaddr_setnull(&sin2);

	if (set_config("dundi.conf", &sin, 1, &sin2))
		return AST_MODULE_LOAD_FAILURE;

	return AST_MODULE_LOAD_SUCCESS;
}

static int load_module(void)
{
	struct ast_sockaddr sin;
	struct ast_sockaddr sin2;

	dundi_set_output(dundi_debug_output);
	dundi_set_error(dundi_error_output);

	/* Make a UDP socket */
	io = io_context_create();
	sched = ast_sched_context_create();

	if (!io || !sched) {
		goto declined;
	}

	ast_sockaddr_setnull(&sin);
	ast_sockaddr_setnull(&sin2);

	if (set_config("dundi.conf", &sin, 0, &sin2)) {
		goto declined;
	}

	if (!ast_sockaddr_isnull(&sin2)) {
		if ((ast_sockaddr_is_ipv4(&sin) == ast_sockaddr_is_ipv4(&sin2)) || (ast_sockaddr_is_ipv6(&sin) == ast_sockaddr_is_ipv6(&sin2))) {
			ast_log(LOG_ERROR, "bindaddr & bindaddr2 should be different IP protocols.\n");
			goto declined;
		}

		/*bind netsocket to ipv4, netsocket2 to ipv6 */

		netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
		netsocket2 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
		if (netsocket < 0 || netsocket2 < 0) {
			ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
			goto declined;
		}
		if (ast_sockaddr_is_ipv4(&sin)) {
			if (ast_bind(netsocket, &sin)) {
				ast_log(LOG_ERROR, "Unable to bind to %s : %s\n",
				ast_sockaddr_stringify(&sin), strerror(errno));
				goto declined;
			}
			if (ast_bind(netsocket2, &sin2)) {
				ast_log(LOG_ERROR, "Unable to bind to %s : %s\n",
				ast_sockaddr_stringify(&sin2), strerror(errno));
				goto declined;
			}
		} else {
			if (ast_bind(netsocket, &sin2)) {
				ast_log(LOG_ERROR, "Unable to bind to %s : %s\n",
				ast_sockaddr_stringify(&sin2), strerror(errno));
				goto declined;
			}
			if (ast_bind(netsocket2, &sin)) {
				ast_log(LOG_ERROR, "Unable to bind to %s : %s\n",
					ast_sockaddr_stringify(&sin), strerror(errno));
				goto declined;
			}
		}
		ast_set_qos(netsocket, tos, 0, "DUNDi");
		ast_set_qos(netsocket2, tos, 0, "DUNDi");
	} else {
		if (ast_sockaddr_is_ipv6(&sin)) {
			netsocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
		} else {
			netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
		}
		if (netsocket < 0) {
			ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
			goto declined;
		}
		if (ast_bind(netsocket, &sin))  {
			ast_log(LOG_ERROR, "Unable to bind to %s : %s\n",
				ast_sockaddr_stringify(&sin), strerror(errno));
			goto declined;
		}
		ast_set_qos(netsocket, tos, 0, "DUNDi");
	}

	if (start_network_thread()) {
		ast_log(LOG_ERROR, "Unable to start network thread\n");
		goto declined;
	}

	ast_cli_register_multiple(cli_dundi, ARRAY_LEN(cli_dundi));
	if (ast_register_switch(&dundi_switch))
		ast_log(LOG_ERROR, "Unable to register DUNDi switch\n");
	ast_custom_function_register(&dundi_function);
	ast_custom_function_register(&dundi_query_function);
	ast_custom_function_register(&dundi_result_function);

	ast_verb(2, "DUNDi Ready and Listening on %s\n", ast_sockaddr_stringify(&sin));
	if (!ast_sockaddr_isnull(&sin2))
		ast_verb(2, "DUNDi Ready and Listening on %s\n", ast_sockaddr_stringify(&sin2));

	return AST_MODULE_LOAD_SUCCESS;

declined:
	unload_module();
	return AST_MODULE_LOAD_DECLINE;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Distributed Universal Number Discovery (DUNDi)",
	.support_level = AST_MODULE_SUPPORT_EXTENDED,
	.load = load_module,
	.unload = unload_module,
	.reload = reload,
	.optional_modules = "res_crypto",
);
