[asterisk-commits] dvossel: branch 1.2 r215958 - in /branches/1.2: ./ channels/ configs/ include...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Sep 3 11:57:42 CDT 2009


Author: dvossel
Date: Thu Sep  3 11:57:35 2009
New Revision: 215958

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=215958
Log:
Merge code associated with AST-2009-006

(closes issue #12912)
Reported by: rathaus
Tested by: tilghman, russell, dvossel, dbrooks


Added:
    branches/1.2/include/asterisk/sha1.h   (with props)
    branches/1.2/sha1.c   (with props)
Modified:
    branches/1.2/Makefile
    branches/1.2/acl.c
    branches/1.2/astobj2.c
    branches/1.2/channels/chan_iax2.c
    branches/1.2/channels/iax2-parser.c
    branches/1.2/channels/iax2-parser.h
    branches/1.2/channels/iax2.h
    branches/1.2/configs/iax.conf.sample
    branches/1.2/include/asterisk/acl.h
    branches/1.2/include/asterisk/astobj2.h
    branches/1.2/include/asterisk/utils.h
    branches/1.2/utils.c

Modified: branches/1.2/Makefile
URL: http://svn.asterisk.org/svn-view/asterisk/branches/1.2/Makefile?view=diff&rev=215958&r1=215957&r2=215958
==============================================================================
--- branches/1.2/Makefile (original)
+++ branches/1.2/Makefile Thu Sep  3 11:57:35 2009
@@ -354,7 +354,7 @@
 	astmm.o enum.o srv.o dns.o aescrypt.o aestab.o aeskey.o \
 	utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \
 	netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \
-	cryptostub.o astobj2.o
+	cryptostub.o astobj2.o sha1.o
 
 ifeq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/include/sys/poll.h),)
   OBJS+= poll.o

Modified: branches/1.2/acl.c
URL: http://svn.asterisk.org/svn-view/asterisk/branches/1.2/acl.c?view=diff&rev=215958&r1=215957&r2=215958
==============================================================================
--- branches/1.2/acl.c (original)
+++ branches/1.2/acl.c Thu Sep  3 11:57:35 2009
@@ -74,14 +74,6 @@
 AST_MUTEX_DEFINE_STATIC(routeseq_lock);
 #endif
 
-struct ast_ha {
-	/* Host access rule */
-	struct in_addr netaddr;
-	struct in_addr netmask;
-	int sense;
-	struct ast_ha *next;
-};
-
 /* Default IP - if not otherwise set, don't breathe garbage */
 static struct in_addr __ourip = { 0x00000000 };
 
@@ -102,7 +94,7 @@
 }
 
 /* Copy HA structure */
-static void ast_copy_ha(struct ast_ha *from, struct ast_ha *to)
+void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to)
 {
 	memcpy(&to->netaddr, &from->netaddr, sizeof(from->netaddr));
 	memcpy(&to->netmask, &from->netmask, sizeof(from->netmask));
@@ -141,7 +133,7 @@
 	return ret;    			/* Return start of list */
 }
 
-struct ast_ha *ast_append_ha(char *sense, char *stuff, struct ast_ha *path)
+struct ast_ha *ast_append_ha(char *sense, const char *stuff, struct ast_ha *path)
 {
 	struct ast_ha *ha = malloc(sizeof(struct ast_ha));
 	char *nm = "255.255.255.255";

Modified: branches/1.2/astobj2.c
URL: http://svn.asterisk.org/svn-view/asterisk/branches/1.2/astobj2.c?view=diff&rev=215958&r1=215957&r2=215958
==============================================================================
--- branches/1.2/astobj2.c (original)
+++ branches/1.2/astobj2.c Thu Sep  3 11:57:35 2009
@@ -413,7 +413,7 @@
 	const enum search_flags flags,
 	ao2_callback_fn cb_fn, void *arg)
 {
-	int i, last;	/* search boundaries */
+	int i, start, last;	/* search boundaries */
 	void *ret = NULL;
 
 	if (INTERNAL_OBJ(c) == NULL)	/* safety check on the argument */
@@ -443,13 +443,15 @@
 	 * (this only for the time being. We need to optimize this.)
 	 */
 	if ((flags & OBJ_POINTER))	/* we know hash can handle this case */
-		i = c->hash_fn(arg, flags & OBJ_POINTER) % c->n_buckets;
+		start = i = c->hash_fn(arg, flags & OBJ_POINTER) % c->n_buckets;
 	else			/* don't know, let's scan all buckets */
 		i = -1;		/* XXX this must be fixed later. */
 
 	/* determine the search boundaries: i..last-1 */
 	if (i < 0) {
-		i = 0;
+		start = i = 0;
+		last = c->n_buckets;
+	} else if ((flags & OBJ_CONTINUE)) {
 		last = c->n_buckets;
 	} else {
 		last = i + 1;
@@ -505,6 +507,17 @@
 			}
 		}
 		AST_LIST_TRAVERSE_SAFE_END
+
+		if (ret) {
+			/* This assumes OBJ_MULTIPLE with !OBJ_NODATA is still not implemented */
+			break;
+		}
+
+		if (i == c->n_buckets - 1 && (flags & OBJ_POINTER) && (flags & OBJ_CONTINUE)) {
+			/* Move to the beginning to ensure we check every bucket */
+			i = -1;
+			last = start;
+		}
 	}
 	ao2_unlock(c);
 	return ret;

Modified: branches/1.2/channels/chan_iax2.c
URL: http://svn.asterisk.org/svn-view/asterisk/branches/1.2/channels/chan_iax2.c?view=diff&rev=215958&r1=215957&r2=215958
==============================================================================
--- branches/1.2/channels/chan_iax2.c (original)
+++ branches/1.2/channels/chan_iax2.c Thu Sep  3 11:57:35 2009
@@ -281,6 +281,20 @@
 static int reload_config(void);
 static int iax2_reload(int fd, int argc, char *argv[]);
 
+/*!
+ * \brief Call token validation settings.
+ */
+enum calltoken_peer_enum {
+	/*! \brief Default calltoken required unless the ip is in the ignorelist */
+	CALLTOKEN_DEFAULT = 0,
+	/*! \brief Require call token validation. */
+	CALLTOKEN_YES = 1,
+	/*! \brief Require call token validation after a successful registration
+	 *         using call token validation occurs. */
+	CALLTOKEN_AUTO = 2,
+	/*! \brief Do not require call token validation. */
+	CALLTOKEN_NO = 3,
+};
 
 struct iax2_user {
 	char name[80];
@@ -303,6 +317,7 @@
 	struct iax2_context *contexts;
 	struct iax2_user *next;
 	struct ast_variable *vars;
+	enum calltoken_peer_enum calltoken_required;        /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */
 };
 
 struct iax2_peer {
@@ -348,8 +363,10 @@
 	int pokefreqnotok;				/*!< How often to check when the host has been determined to be down */
 	int historicms;					/*!< How long recent average responses took */
 	int smoothing;					/*!< Sample over how many units to determine historic ms */
+	uint16_t maxcallno;					/*!< Max call number limit for this peer.  Set on registration */
 	
 	struct ast_ha *ha;
+	enum calltoken_peer_enum calltoken_required;        /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */
 	struct iax2_peer *next;
 };
 
@@ -482,6 +499,8 @@
 	struct ast_codec_pref rprefs;
 	/*! Our call number */
 	unsigned short callno;
+	/*! Our callno_entry entry */
+	struct callno_entry *callno_entry;
 	/*! Peer callno */
 	unsigned short peercallno;
 	/*! Negotiated format, this is only used to remember what format was
@@ -616,7 +635,17 @@
 	int frames_dropped;
 	/*! received frame count: (just for stats) */
 	int frames_received;
+	/*! num bytes used for calltoken ie, even an empty ie should contain 2 */
+	unsigned char calltoken_ie_len;
 };
+
+/*! table of available call numbers */
+static struct ao2_container *callno_pool;
+
+/*! table of available trunk call numbers */
+static struct ao2_container *callno_pool_trunk;
+
+static const unsigned int CALLNO_POOL_BUCKETS = 2699;
 
 static struct ast_iax2_queue {
 	struct iax_frame *head;
@@ -624,6 +653,63 @@
 	int count;
 	ast_mutex_t lock;
 } iaxq;
+
+static int randomcalltokendata;
+
+static const time_t MAX_CALLTOKEN_DELAY = 10;
+
+#define MAX_PEER_BUCKETS 563
+
+/*! Table containing peercnt objects for every ip address consuming a callno */
+static struct ao2_container *peercnts;
+
+/*! Table containing custom callno limit rules for a range of ip addresses. */
+static struct ao2_container *callno_limits;
+
+/*! Table containing ip addresses not requiring calltoken validation */
+static struct ao2_container *calltoken_ignores;
+
+static uint16_t DEFAULT_MAXCALLNO_LIMIT = 2048;
+
+static uint16_t DEFAULT_MAXCALLNO_LIMIT_NONVAL = 8192;
+
+static uint16_t global_maxcallno;
+
+/*! Total num of call numbers allowed to be allocated without calltoken validation */
+static uint16_t global_maxcallno_nonval;
+
+static uint16_t total_nonval_callno_used = 0;
+
+/*! peer connection private, keeps track of all the call numbers
+ *  consumed by a single ip address */
+struct peercnt {
+	/*! ip address consuming call numbers */
+	unsigned long addr;
+	/*! Number of call numbers currently used by this ip address */
+	uint16_t cur;
+	/*! Max call numbers allowed for this ip address */
+	uint16_t limit;
+	/*! Specifies whether limit is set by a registration or not, if so normal
+ 	 *  limit setting rules do not apply to this address. */
+	unsigned char reg;
+};
+
+/*! used by both callno_limits and calltoken_ignores containers */
+struct addr_range {
+	/*! ip address range for custom callno limit rule */
+	struct ast_ha ha;
+	/*! callno limit for this ip address range, only used in callno_limits container */
+	uint16_t limit;
+	/*! delete me marker for reloads */
+	unsigned char delme;
+};
+
+struct callno_entry {
+	/*! callno used for this entry */
+	uint16_t callno;
+	/*! was this callno calltoken validated or not */
+	unsigned char validated;
+};
 
 static struct ast_user_list {
 	struct iax2_user *users;
@@ -673,6 +759,7 @@
 
 static void reg_source_db(struct iax2_peer *p);
 static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in *sin);
+static struct iax2_user *realtime_user(const char *username, struct sockaddr_in *sin);
 
 static void destroy_peer(struct iax2_peer *peer);
 static int ast_cli_netstats(int fd, int limit_fmt);
@@ -707,7 +794,7 @@
 	vsnprintf(buf, 1024, fmt, args);
 	va_end(args);
 
-	ast_log(LOG_ERROR, buf);
+	ast_log(LOG_ERROR, "%s", buf);
 }
 
 static void jb_warning_output(const char *fmt, ...)
@@ -719,7 +806,7 @@
 	vsnprintf(buf, 1024, fmt, args);
 	va_end(args);
 
-	ast_log(LOG_WARNING, buf);
+	ast_log(LOG_WARNING, "%s", buf);
 }
 
 static void jb_debug_output(const char *fmt, ...)
@@ -731,7 +818,7 @@
 	vsnprintf(buf, 1024, fmt, args);
 	va_end(args);
 
-	ast_verbose(buf);
+	ast_verbose("%s", buf);
 }
 #endif
 
@@ -739,7 +826,6 @@
 /* XXX We probably should use a mutex when working with this XXX */
 static struct chan_iax2_pvt *iaxs[IAX_MAX_CALLS];
 static ast_mutex_t iaxsl[IAX_MAX_CALLS];
-static struct timeval lastused[IAX_MAX_CALLS];
 
 /*!
  * \brief Another container of iax2_pvt structures
@@ -782,6 +868,9 @@
 static enum ast_bridge_result iax2_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
 static int iax2_transfer(struct ast_channel *c, const char *dest);
 static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan);
+static struct callno_entry *get_unused_callno(int trunk, int validated);
+static int replace_callno(void *obj);
+static void sched_delay_remove(struct sockaddr_in *sin, struct callno_entry *callno_entry);
 
 static const struct ast_channel_tech iax2_tech = {
 	.type = channeltype,
@@ -905,6 +994,21 @@
 		return csub;
 }
 
+static struct iax2_user *find_user(const char *name, int realtime) 
+{
+	struct iax2_user *user;
+	ast_mutex_lock(&userl.lock);
+	for(user = userl.users; user; user = user->next) {
+		if (!strcasecmp(user->name, name)) {
+			break;
+		}
+	}
+	ast_mutex_unlock(&userl.lock);
+	if(!user && realtime)
+		user = realtime_user(name, NULL);
+	return user;
+}
+
 static struct iax2_peer *find_peer(const char *name, int realtime) 
 {
 	struct iax2_peer *peer;
@@ -1036,7 +1140,6 @@
 retry:
 	ast_mutex_lock(&iaxsl[callno]);
 	pvt = iaxs[callno];
-	gettimeofday(&lastused[callno], NULL);
 
 	if (pvt)
 		owner = pvt->owner;
@@ -1095,6 +1198,9 @@
 	struct iax_frame *cur;
 
 	iax2_destroy_helper(pvt);
+
+	sched_delay_remove(&pvt->addr, pvt->callno_entry);
+	pvt->callno_entry = NULL;
 
 	if (pvt->bridgetrans)
 		ast_translator_free_path(pvt->bridgetrans);
@@ -1173,9 +1279,19 @@
 	return new;
 }
 
-#define NEW_PREVENT 	0
-#define NEW_ALLOW 	1
-#define NEW_FORCE 	2
+/* keep these defined in this order.  They are used in find_callno to
+ * determine whether or not a new call number should be allowed. */
+enum {
+	/* do not allow a new call number, only search ones in use for match */
+	NEW_PREVENT = 0,
+	/* search for match first, then allow a new one to be allocated */
+	NEW_ALLOW = 1,
+	/* do not search for match, force a new call number */
+	NEW_FORCE = 2,
+	/* do not search for match, force a new call number.  Signifies call number
+ 	 * has been calltoken validated */
+	NEW_ALLOW_CALLTOKEN_VALIDATED = 3,
+};
 
 static int match(struct sockaddr_in *sin, unsigned short callno, unsigned short dcallno, struct chan_iax2_pvt *cur, int check_dcallno)
 {
@@ -1215,7 +1331,7 @@
 {
 	int x;
 	int res= 0;
-	struct timeval now;
+	struct callno_entry *callno_entry;
 	if (iaxs[callno]->oseqno) {
 		ast_log(LOG_WARNING, "Can't make trunk once a call has started!\n");
 		return -1;
@@ -1224,46 +1340,642 @@
 		ast_log(LOG_WARNING, "Call %d is already a trunk\n", callno);
 		return -1;
 	}
-	gettimeofday(&now, NULL);
-	for (x=TRUNK_CALL_START;x<IAX_MAX_CALLS - 1; x++) {
-		ast_mutex_lock(&iaxsl[x]);
-		if (!iaxs[x] && ((now.tv_sec - lastused[x].tv_sec) > MIN_REUSE_TIME)) {
-			iaxs[x] = iaxs[callno];
-			iaxs[x]->callno = x;
-			iaxs[callno] = NULL;
-			/* Update the two timers that should have been started */
-			if (iaxs[x]->pingid > -1)
-				ast_sched_del(sched, iaxs[x]->pingid);
-			if (iaxs[x]->lagid > -1)
-				ast_sched_del(sched, iaxs[x]->lagid);
-			iaxs[x]->pingid = ast_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x);
-			iaxs[x]->lagid = ast_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x);
-			if (locked)
-				ast_mutex_unlock(&iaxsl[callno]);
-			res = x;
-			if (!locked)
-				ast_mutex_unlock(&iaxsl[x]);
-			break;
-		}
-		ast_mutex_unlock(&iaxsl[x]);
-	}
-	if (x >= IAX_MAX_CALLS - 1) {
+
+	if (!(callno_entry = get_unused_callno(1, iaxs[callno]->callno_entry->validated))) {
 		ast_log(LOG_WARNING, "Unable to trunk call: Insufficient space\n");
 		return -1;
 	}
-	ast_log(LOG_DEBUG, "Made call %d into trunk call %d\n", callno, x);
+	x = callno_entry->callno;
+	ast_mutex_lock(&iaxsl[x]);
+
+	/*!
+	 * \note We delete these before switching the slot, because if
+	 * they fire in the meantime, they will generate a warning.
+	 */
+	iaxs[x] = iaxs[callno];
+	iaxs[x]->callno = x;
+	/* since we copied over the pvt from a different callno, make sure the old entry is replaced
+	 * before assigning the new one */
+	if (iaxs[x]->callno_entry) {
+		ast_sched_add(sched, MIN_REUSE_TIME * 1000, replace_callno, iaxs[x]->callno_entry);
+	}
+	iaxs[x]->callno_entry = callno_entry;
+	iaxs[callno] = NULL;
+	/* Update the two timers that should have been started */
+	if (iaxs[x]->pingid > -1)
+		ast_sched_del(sched, iaxs[x]->pingid);
+	if (iaxs[x]->lagid > -1)
+		ast_sched_del(sched, iaxs[x]->lagid);
+	iaxs[x]->pingid = ast_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x);
+	iaxs[x]->lagid = ast_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x);
+	if (locked)
+		ast_mutex_unlock(&iaxsl[callno]);
+	res = x;
+	if (!locked)
+		ast_mutex_unlock(&iaxsl[x]);
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "Made call %d into trunk call %d\n", callno, x);
+
 	/* We move this call from a non-trunked to a trunked call */
 	update_max_trunk();
 	update_max_nontrunk();
 	return res;
 }
 
+static int addr_range_delme_cb(void *obj, void *arg, int flags)
+{
+	struct addr_range *lim = obj;
+	lim->delme = 1;
+	return 0;
+}
+
+static int addr_range_hash_cb(const void *obj, const int flags)
+{
+	const struct addr_range *lim = obj;
+	return abs((int) lim->ha.netaddr.s_addr);
+}
+
+static int addr_range_cmp_cb(void *obj, void *arg, int flags)
+{
+	struct addr_range *lim1 = obj, *lim2 = arg;
+	return ((lim1->ha.netaddr.s_addr == lim2->ha.netaddr.s_addr) &&
+		(lim1->ha.netmask.s_addr == lim2->ha.netmask.s_addr)) ?
+		CMP_MATCH | CMP_STOP : 0;
+}
+
+static int peercnt_hash_cb(const void *obj, const int flags)
+{
+	const struct peercnt *peercnt = obj;
+	return abs((int) peercnt->addr);
+}
+
+static int peercnt_cmp_cb(void *obj, void *arg, int flags)
+{
+	struct peercnt *peercnt1 = obj, *peercnt2 = arg;
+	return (peercnt1->addr == peercnt2->addr) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int addr_range_match_address_cb(void *obj, void *arg, int flags)
+{
+	struct addr_range *addr_range = obj;
+	struct sockaddr_in *sin = arg;
+
+	if ((sin->sin_addr.s_addr & addr_range->ha.netmask.s_addr) == addr_range->ha.netaddr.s_addr) {
+		return CMP_MATCH | CMP_STOP;
+	}
+	return 0;
+}
+
+/*! 
+ * \internal
+ *
+ * \brief compares sin to calltoken_ignores table to determine if validation is required.
+ */
+static int calltoken_required(struct sockaddr_in *sin, const char *name, int subclass)
+{
+	struct addr_range *addr_range;
+	struct iax2_peer *peer = NULL;
+	struct iax2_user *user = NULL;
+	/* if no username is given, check for guest accounts */
+	const char *find = ast_strlen_zero(name) ?  "guest" : name;
+	int res = 1;  /* required by default */
+	int optional = 0;
+	enum calltoken_peer_enum calltoken_required = CALLTOKEN_DEFAULT;
+	char iabuf[INET_ADDRSTRLEN];
+	/* There are only two cases in which calltoken validation is not required.
+	 * Case 1. sin falls within the list of address ranges specified in the calltoken optional table and
+	 *         the peer definition has not set the requirecalltoken option.
+	 * Case 2. Username is a valid peer/user, and that peer has requirecalltoken set either auto or no.
+	 */
+
+	/* ----- Case 1 ----- */
+	if ((addr_range = ao2_callback(calltoken_ignores, 0, addr_range_match_address_cb, sin))) {
+		ao2_ref(addr_range, -1);
+		optional = 1;
+	}
+
+	/* ----- Case 2 ----- */
+	if ((subclass == IAX_COMMAND_NEW) && (user = find_user(find, 1))) {
+		calltoken_required = user->calltoken_required;
+	} else if ((subclass != IAX_COMMAND_NEW) && (peer = find_peer(find, 1))) {
+		calltoken_required = peer->calltoken_required;
+	}
+
+	if (option_debug) {
+		ast_log(LOG_DEBUG, "Determining if address %s with username %s requires calltoken validation.  Optional = %d  calltoken_required = %d \n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), name, optional, calltoken_required);
+	}
+	if (((calltoken_required == CALLTOKEN_NO) || (calltoken_required == CALLTOKEN_AUTO)) ||
+		(optional && (calltoken_required == CALLTOKEN_DEFAULT))) {
+		res = 0;
+	}
+
+	return res;
+}
+
+/*! 
+ * \internal
+ *
+ * \brief set peercnt callno limit.
+ *
+ * \details 
+ * First looks in custom definitions. If not found, global limit
+ * is used.  Entries marked as reg already have
+ * a custom limit set by a registration and are not modified.
+ */
+static void set_peercnt_limit(struct peercnt *peercnt)
+{
+	uint16_t limit = global_maxcallno;
+	struct addr_range *addr_range;
+	struct sockaddr_in sin = {
+		.sin_addr.s_addr = peercnt->addr,
+	};
+	char iabuf[INET_ADDRSTRLEN];
+
+
+	if (peercnt->reg && peercnt->limit) {
+		return; /* this peercnt has a custom limit set by a registration */
+	}
+
+	if ((addr_range = ao2_callback(callno_limits, 0, addr_range_match_address_cb, &sin))) {
+		limit = addr_range->limit;
+		if (option_debug) {
+			ast_log(LOG_NOTICE, "custom addr_range %d found for %s\n", limit, ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr));
+		}
+		ao2_ref(addr_range, -1);
+	}
+
+	peercnt->limit = limit;
+}
+
+/*! 
+ * \internal
+ * \brief sets limits for all peercnts in table. done on reload to reflect changes in conf.
+ */
+static int set_peercnt_limit_all_cb(void *obj, void *arg, int flags)
+{
+	struct peercnt *peercnt = obj;
+
+	set_peercnt_limit(peercnt);
+	if (option_debug) {
+		ast_log(LOG_NOTICE, "Reset limits for peercnts table\n");
+	}
+	return 0;
+}
+
+/*! 
+ * \internal
+ * \brief returns match if delme is set. 
+ */
+static int prune_addr_range_cb(void *obj, void *arg, int flags)
+{
+	struct addr_range *addr_range = obj;
+
+	return addr_range->delme ? CMP_MATCH : 0;
+}
+
+/*! 
+ * \internal
+ * \brief modifies peercnt entry in peercnts table. Used to set custom limit or mark a registered ip
+ */
+static void peercnt_modify(unsigned char reg, uint16_t limit, struct sockaddr_in *sin)
+{
+	/* this function turns off and on custom callno limits set by peer registration */
+	struct peercnt *peercnt;
+	struct peercnt tmp = {
+		.addr = sin->sin_addr.s_addr,
+	};
+	char iabuf[INET_ADDRSTRLEN];
+
+	if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) {
+		peercnt->reg = reg;
+		if (limit) {
+			peercnt->limit = limit;
+		} else {
+			set_peercnt_limit(peercnt);
+		}
+		if (option_debug) {
+			ast_log(LOG_NOTICE, "peercnt entry %s modified limit:%d registered:%d", ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), peercnt->limit, peercnt->reg);
+		}
+		ao2_ref(peercnt, -1); /* decrement ref from find */
+	}
+}
+
+/*! 
+ * \internal
+ * \brief adds an ip to the peercnts table, increments connection count if it already exists
+ *
+ * \details First searches for the address in the peercnts table.  If found
+ * the current count is incremented.  If not found a new peercnt is allocated
+ * and linked into the peercnts table with a call number count of 1.
+ */
+static int peercnt_add(struct sockaddr_in *sin)
+{
+	struct peercnt *peercnt;
+	unsigned long addr = sin->sin_addr.s_addr;
+	int res = 0;
+	struct peercnt tmp = {
+		.addr = addr,
+	};
+	char iabuf[INET_ADDRSTRLEN];
+
+	/* Reasoning for peercnts container lock:  Two identical ip addresses
+	 * could be added by different threads at the "same time". Without the container
+	 * lock, both threads could alloc space for the same object and attempt
+	 * to link to table.  With the lock, one would create the object and link
+	 * to table while the other would find the already created peercnt object
+	 * rather than creating a new one. */
+	ao2_lock(peercnts);
+	if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) {
+		ao2_lock(peercnt);
+	} else if ((peercnt = ao2_alloc(sizeof(*peercnt), NULL))) {
+		ao2_lock(peercnt);
+		/* create and set defaults */
+		peercnt->addr = addr;
+		set_peercnt_limit(peercnt);
+		/* guarantees it does not go away after unlocking table
+		 * ao2_find automatically adds this */
+		ao2_link(peercnts, peercnt);
+	} else {
+		ao2_unlock(peercnts);
+		return -1;
+	}
+
+	/* check to see if the address has hit its callno limit.  If not increment cur. */
+	if (peercnt->limit > peercnt->cur) {
+		peercnt->cur++;
+		if (option_debug) {
+			ast_log(LOG_NOTICE, "ip callno count incremented to %d for %s\n", peercnt->cur, ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr));
+		}
+	} else { /* max num call numbers for this peer has been reached! */
+		ast_log(LOG_ERROR, "maxcallnumber limit of %d for %s has been reached!\n", peercnt->limit, ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr));
+		res = -1;
+	}
+
+	/* clean up locks and ref count */
+	ao2_unlock(peercnt);
+	ao2_unlock(peercnts);
+	ao2_ref(peercnt, -1); /* decrement ref from find/alloc, only the container ref remains. */
+
+	return res;
+}
+
+/*! 
+ * \internal
+ * \brief decrements a peercnts table entry
+ */
+static void peercnt_remove(struct peercnt *peercnt)
+{
+	struct sockaddr_in sin = {
+		.sin_addr.s_addr = peercnt->addr,
+	};
+	char iabuf[INET_ADDRSTRLEN];
+
+	if (peercnt) {
+		/* Container locked here since peercnt may be unlinked from list.  If left unlocked,
+		 * peercnt_add could try and grab this entry from the table and modify it at the
+		 * "same time" this thread attemps to unlink it.*/
+		ao2_lock(peercnts);
+		peercnt->cur--;
+		if (option_debug) {
+			ast_log(LOG_NOTICE, "ip callno count decremented to %d for %s\n", peercnt->cur, ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr));
+		}
+		/* if this was the last connection from the peer remove it from table */
+		if (peercnt->cur == 0) {
+			ao2_unlink(peercnts, peercnt);/* decrements ref from table, last ref is left to scheduler */
+		}
+		ao2_unlock(peercnts);
+	}
+}
+
+/*! 
+ * \internal
+ * \brief called by scheduler to decrement object
+ */
+static int peercnt_remove_cb(void *obj)
+{
+	struct peercnt *peercnt = (struct peercnt *) obj;
+
+	peercnt_remove(peercnt);
+	ao2_ref(peercnt, -1); /* decrement ref from scheduler */
+
+	return 0;
+}
+
+/*! 
+ * \internal
+ * \brief decrements peercnts connection count, finds by addr
+ */
+static int peercnt_remove_by_addr(struct sockaddr_in *sin)
+{
+	struct peercnt *peercnt;
+	struct peercnt tmp = {
+		.addr = sin->sin_addr.s_addr,
+	};
+
+	if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) {
+		peercnt_remove(peercnt);
+		ao2_ref(peercnt, -1); /* decrement ref from find */
+	}
+	return 0;
+}
+
+/*! 
+ * \internal
+ * \brief Create callno_limit entry based on configuration
+ */
+static void build_callno_limits(struct ast_variable *v)
+{
+	struct addr_range *addr_range = NULL;
+	struct addr_range tmp;
+	struct ast_ha *ha;
+	int limit;
+	int found;
+
+	for (; v; v = v->next) {
+		limit = -1;
+		found = 0;
+		ha = ast_append_ha("permit", v->name, NULL);
+
+		/* check for valid config information */
+		if (!ha) {
+			ast_log(LOG_ERROR, "Call number limit for %s could not be added, Invalid address range\n.", v->name);
+			continue;
+		} else if ((sscanf(v->value, "%d", &limit) != 1) || (limit < 0)) {
+			ast_log(LOG_ERROR, "Call number limit for %s could not be added. Invalid limit %s\n.", v->name, v->value);
+			ast_free_ha(ha);
+			continue;
+		}
+
+		ast_copy_ha(ha, &tmp.ha);
+		/* find or create the addr_range */
+		if ((addr_range = ao2_find(callno_limits, &tmp, OBJ_POINTER))) {
+			ao2_lock(addr_range);
+			found = 1;
+		} else if (!(addr_range = ao2_alloc(sizeof(*addr_range), NULL))) {
+			ast_free_ha(ha);
+			return; /* out of memory */
+		}
+
+		/* copy over config data into addr_range object */
+		ast_copy_ha(ha, &addr_range->ha); /* this is safe because only one ha is possible for each limit */
+		ast_free_ha(ha); /* cleanup the tmp ha */
+		addr_range->limit = limit;
+		addr_range->delme = 0;
+
+		/* cleanup */
+		if (found) {
+			ao2_unlock(addr_range);
+		} else {
+			ao2_link(callno_limits, addr_range);
+		}
+		ao2_ref(addr_range, -1); /* decrement ref from ao2_find and ao2_alloc, only container ref remains */
+	}
+}
+
+/*! 
+ * \internal
+ * \brief Create calltoken_ignores entry based on configuration
+ */
+static int add_calltoken_ignore(const char *addr)
+{
+	struct addr_range tmp;
+	struct addr_range *addr_range = NULL;
+	struct ast_ha *ha = NULL;
+
+	if (ast_strlen_zero(addr)) {
+		ast_log(LOG_WARNING, "invalid calltokenoptional %s\n", addr);
+		return -1;
+	}
+
+	ha = ast_append_ha("permit", addr, NULL);
+
+	/* check for valid config information */
+	if (!ha) {
+		ast_log(LOG_WARNING, "Error creating calltokenoptional entry %s\n", addr);
+		return -1;
+	}
+
+	ast_copy_ha(ha, &tmp.ha);
+	/* find or create the addr_range */
+	if ((addr_range = ao2_find(calltoken_ignores, &tmp, OBJ_POINTER))) {
+		ao2_lock(addr_range);
+		addr_range->delme = 0;
+		ao2_unlock(addr_range);
+	} else if ((addr_range = ao2_alloc(sizeof(*addr_range), NULL))) {
+		/* copy over config data into addr_range object */
+		ast_copy_ha(ha, &addr_range->ha); /* this is safe because only one ha is possible */
+		ao2_link(calltoken_ignores, addr_range);
+	} else {
+		ast_free_ha(ha);
+		return -1;
+	}
+
+	ast_free_ha(ha);
+	ao2_ref(addr_range, -1); /* decrement ref from ao2_find and ao2_alloc, only container ref remains */
+
+	return 0;
+}
+
+static int iax2_show_callnumber_usage(int fd, int argc, char *argv[])
+{
+	struct ao2_iterator i;
+	struct peercnt *peercnt;
+	struct sockaddr_in sin;
+	int found = 0;
+	char iabuf[INET_ADDRSTRLEN];
+
+	if (argc < 4 || argc > 5)
+		return RESULT_SHOWUSAGE;
+
+	ast_cli(fd, "%-15s %-12s %-12s\n", "Address", "Callno Usage", "Callno Limit");
+	i = ao2_iterator_init(peercnts, 0);
+	while ((peercnt = ao2_iterator_next(&i))) {
+		sin.sin_addr.s_addr = peercnt->addr;
+		if (argc == 5 && (!strcasecmp(argv[4], ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr)))) {
+				ast_cli(fd, "%-15s %-12d %-12d\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr), peercnt->cur, peercnt->limit);
+				found = 1;
+				break;
+		} else {
+			ast_cli(fd, "%-15s %-12d %-12d\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr), peercnt->cur, peercnt->limit);
+		}
+		ao2_ref(peercnt, -1);
+	}
+	if (argc == 4) {
+		ast_cli(fd, "\nNon-CallToken Validation Limit: %d\nNon-CallToken Validated: %d\n", global_maxcallno_nonval, total_nonval_callno_used);
+	} else if (argc == 5 && !found) {
+		ast_cli(fd, "No callnumber table entries for %s found\n", argv[4] );
+	}
+	return RESULT_SUCCESS;
+}
+
+static struct callno_entry *get_unused_callno(int trunk, int validated)
+{
+	struct callno_entry *callno_entry = NULL;
+	if ((!ao2_container_count(callno_pool) && !trunk) || (!ao2_container_count(callno_pool_trunk) && trunk)) {
+		ast_log(LOG_WARNING, "Out of CallNumbers\n");
+		/* Minor optimization for the extreme case. */
+		return NULL;
+	}
+
+	/* the callno_pool container is locked here primarily to ensure thread
+	 * safety of the total_nonval_callno_used check and increment */
+	ao2_lock(callno_pool);
+
+	/* only a certain number of nonvalidated call numbers should be allocated.
+	 * If there ever is an attack, this separates the calltoken validating
+	 * users from the non calltoken validating users. */
+	if (!validated && (total_nonval_callno_used >= global_maxcallno_nonval)) {
+		ast_log(LOG_WARNING, "NON-CallToken callnumber limit is reached. Current:%d Max:%d\n", total_nonval_callno_used, global_maxcallno_nonval);
+		ao2_unlock(callno_pool);
+		return NULL;
+	}
+
+	/* unlink the object from the container, taking over ownership
+	 * of the reference the container had to the object */
+	callno_entry = ao2_find((trunk ? callno_pool_trunk : callno_pool), NULL, OBJ_POINTER | OBJ_UNLINK | OBJ_CONTINUE);
+
+	if (callno_entry) {
+		callno_entry->validated = validated;
+		if (!validated) {
+			total_nonval_callno_used++;
+		}
+	}
+
+	ao2_unlock(callno_pool);
+	return callno_entry;
+}
+
+static int replace_callno(void *obj)
+{
+	struct callno_entry *callno_entry = (struct callno_entry *) obj;
+
+	/* the callno_pool container is locked here primarily to ensure thread
+	 * safety of the total_nonval_callno_used check and decrement */
+	ao2_lock(callno_pool);
+
+	if (!callno_entry->validated && (total_nonval_callno_used != 0)) {
+		total_nonval_callno_used--;
+	} else if (!callno_entry->validated && (total_nonval_callno_used == 0)) {
+		ast_log(LOG_ERROR, "Attempted to decrement total non calltoken validated callnumbers below zero... Callno is:%d \n", callno_entry->callno);
+	}
+
+	if (callno_entry->callno < TRUNK_CALL_START) {
+		ao2_link(callno_pool, callno_entry);
+	} else {
+		ao2_link(callno_pool_trunk, callno_entry);
+	}
+	ao2_ref(callno_entry, -1); /* only container ref remains */
+
+	ao2_unlock(callno_pool);
+	return 0;
+}
+
+static int callno_hash(const void *obj, const int flags)
+{
+	return abs(rand());
+}
+
+static int create_callno_pools(void)
+{
+	uint16_t i;
+
+	if (!(callno_pool = ao2_container_alloc(CALLNO_POOL_BUCKETS, callno_hash, NULL))) {
+		return -1;
+	}
+
+	if (!(callno_pool_trunk = ao2_container_alloc(CALLNO_POOL_BUCKETS, callno_hash, NULL))) {
+		return -1;
+	}
+
+	/* start at 2, 0 and 1 are reserved */
+	for (i = 2; i <= IAX_MAX_CALLS; i++) {
+		struct callno_entry *callno_entry;
+
+		if (!(callno_entry = ao2_alloc(sizeof(*callno_entry), NULL))) {
+			return -1;
+		}
+
+		callno_entry->callno = i;
+
+		if (i < TRUNK_CALL_START) {
+			ao2_link(callno_pool, callno_entry);
+		} else {
+			ao2_link(callno_pool_trunk, callno_entry);
+		}
+
+		ao2_ref(callno_entry, -1);
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Schedules delayed removal of iax2_pvt call number data
+ *
+ * \note After MIN_REUSE_TIME has passed for a destroyed iax2_pvt, the callno is
+ * avaliable again, and the address from the previous connection must be decremented
+ * from the peercnts table.  This function schedules these operations to take place.
+ */
+static void sched_delay_remove(struct sockaddr_in *sin, struct callno_entry *callno_entry)
+{
+	int i;
+	struct peercnt *peercnt;
+	struct peercnt tmp = {
+		.addr = sin->sin_addr.s_addr,
+	};
+	char iabuf[INET_ADDRSTRLEN];
+
+	if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) {
+		/* refcount is incremented with ao2_find.  keep that ref for the scheduler */
+		if (option_debug) {
+			ast_log(LOG_NOTICE, "schedule decrement of callno used for %s in %d seconds\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), MIN_REUSE_TIME);
+		}
+		i = ast_sched_add(sched, MIN_REUSE_TIME * 1000, peercnt_remove_cb, peercnt);
+		if (i == -1) {
+			ao2_ref(peercnt, -1);
+		}
+	}
+
+	ast_sched_add(sched, MIN_REUSE_TIME * 1000, replace_callno, callno_entry);
+}
+
+/*! 
+ * \internal
+ * \brief returns whether or not a frame is capable of starting a new IAX2 dialog. 
+ *
+ * \note For this implementation, inbound pokes should _NOT_ be capable of allocating
+ * a new callno.
+ */
+static inline int iax2_allow_new(int frametype, int subclass, int inbound)
+{
+	if (frametype != AST_FRAME_IAX) {
+		return 0;
+	}
+	switch (subclass) {
+	case IAX_COMMAND_NEW:
+	case IAX_COMMAND_REGREQ:
+	case IAX_COMMAND_FWDOWNL:
+	case IAX_COMMAND_REGREL:
+		return 1;
+	case IAX_COMMAND_POKE:
+		if (!inbound) {
+			return 1;
+		}
+		break;
+	}
+	return 0;
+}
+
 static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int lockpeer, int sockfd, int check_dcallno)
 {
 	int res = 0;
 	int x;
-	struct timeval now;
 	char iabuf[INET_ADDRSTRLEN];
+	/* this call is calltoken validated as long as it is either NEW_FORCE
+	 * or NEW_ALLOW_CALLTOKEN_VALIDATED */
+	int validated = (new > NEW_ALLOW) ? 1 : 0;
 	char host[80];
 	if (new <= NEW_ALLOW) {
  		if (callno) {
@@ -1308,8 +2020,7 @@
 		}
 	}
 	if ((res < 1) && (new >= NEW_ALLOW)) {
-		int start, found = 0;
-
+		struct callno_entry *callno_entry;
 		/* It may seem odd that we look through the peer list for a name for
 		 * this *incoming* call.  Well, it is weird.  However, users don't
 		 * have an IP address/port number that we can match against.  So,
@@ -1319,37 +2030,29 @@
 		if (!iax2_getpeername(*sin, host, sizeof(host), lockpeer))
 			snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), ntohs(sin->sin_port));
 
-		gettimeofday(&now, NULL);
-
-		start = 2 + (rand() % (TRUNK_CALL_START - 1));
-		for (x = start; 1; x++) {
-			if (x == TRUNK_CALL_START) {
-				x = 1;
-				continue;
-			}
-
-			/* Find first unused call number that hasn't been used in a while */
-			ast_mutex_lock(&iaxsl[x]);
-			if (!iaxs[x] && ((now.tv_sec - lastused[x].tv_sec) > MIN_REUSE_TIME)) {
-				found = 1;
-				break;
-			}
-			ast_mutex_unlock(&iaxsl[x]);
-
-			if (x == start - 1) {
-				break;
-			}
-		}
-		/* We've still got lock held if we found a spot */
-		if (x == start - 1 && !found) {
+
+		if (peercnt_add(sin)) {
+			/* This address has hit its callnumber limit.  When the limit
+			 * is reached, the connection is not added to the peercnts table.*/
+			return 0;
+		}
+
+		if (!(callno_entry = get_unused_callno(0, validated))) {
+			/* since we ran out of space, remove the peercnt
+			 * entry we added earlier */
+			peercnt_remove_by_addr(sin);
 			ast_log(LOG_WARNING, "No more space\n");
 			return 0;
 		}
+		x = callno_entry->callno;
+		ast_mutex_lock(&iaxsl[x]);
+
 		iaxs[x] = new_iax(sin, lockpeer, host);
 		update_max_nontrunk();
 		if (iaxs[x]) {
 			if (option_debug && iaxdebug)
 				ast_log(LOG_DEBUG, "Creating new call structure %d\n", x);
+			iaxs[x]->callno_entry = callno_entry;
 			iaxs[x]->sockfd = sockfd;
 			iaxs[x]->addr.sin_port = sin->sin_port;
 			iaxs[x]->addr.sin_family = sin->sin_family;
@@ -1369,6 +2072,7 @@
 		} else {
 			ast_log(LOG_WARNING, "Out of resources\n");
 			ast_mutex_unlock(&iaxsl[x]);
+			replace_callno(callno_entry);
 			return 0;
 		}
 		ast_mutex_unlock(&iaxsl[x]);
@@ -2112,6 +2816,8 @@
 		ast_cli(fd, "  Context      : %s\n", peer->context);
 		ast_cli(fd, "  Mailbox      : %s\n", peer->mailbox);
 		ast_cli(fd, "  Dynamic      : %s\n", ast_test_flag(peer, IAX_DYNAMIC) ? "Yes":"No");
+		ast_cli(fd, "  Callnum limit: %d\n", peer->maxcallno);
+		ast_cli(fd, "  Calltoken req: %s\n", (peer->calltoken_required == CALLTOKEN_YES) ? "Yes" : ((peer->calltoken_required == CALLTOKEN_AUTO) ? "Auto" : "No"));
 		ast_cli(fd, "  Callerid     : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>"));
 		ast_cli(fd, "  Expire       : %d\n", peer->expire);
 		ast_cli(fd, "  ACL          : %s\n", (peer->ha?"Yes":"No"));
@@ -3104,13 +3810,248 @@
 
 static int send_apathetic_reply(unsigned short callno, unsigned short dcallno,
 		struct sockaddr_in *sin, int command, int ts, unsigned char seqno,
-		int sockfd)
-{
-	struct ast_iax2_full_hdr f = { .scallno = htons(0x8000 | callno), .dcallno = htons(dcallno),
-		.ts = htonl(ts), .iseqno = seqno, .oseqno = 0, .type = AST_FRAME_IAX,
-		.csub = compress_subclass(command) };
-
-	return sendto(sockfd, &f, sizeof(f), 0, (struct sockaddr *)sin, sizeof(*sin));
+		int sockfd, struct iax_ie_data *ied)
+{
+	struct {
+		struct ast_iax2_full_hdr f;
+		struct iax_ie_data ied;
+	} data;
+	size_t size = sizeof(struct ast_iax2_full_hdr);
+
+	if (ied) {
+		size += ied->pos;
+		memcpy(&data.ied, ied->buf, ied->pos);
+	}
+
+	data.f.scallno = htons(0x8000 | callno);
+	data.f.dcallno = htons(dcallno);
+	data.f.ts = htonl(ts);
+	data.f.iseqno = seqno;
+	data.f.oseqno = 0;
+	data.f.type = AST_FRAME_IAX;
+	data.f.csub = compress_subclass(command);
+
+	return sendto(sockfd, &data, size, 0, (struct sockaddr *)sin, sizeof(*sin));
+}
+
+static void add_empty_calltoken_ie(struct chan_iax2_pvt *pvt, struct iax_ie_data *ied)
+{
+	/* first make sure their are two empty bytes left in ied->buf */
+	if (pvt && ied && (2 < ((int) sizeof(ied->buf) - ied->pos))) {
+		ied->buf[ied->pos++] = IAX_IE_CALLTOKEN;  /* type */
+		ied->buf[ied->pos++] = 0;   /* data size,  ZERO in this case */
+		pvt->calltoken_ie_len = 2;
+	}
+}
+
+static void resend_with_token(int callno, struct iax_frame *f, char *newtoken)
+{
+	struct chan_iax2_pvt *pvt = iaxs[callno];
+	int frametype = f->af.frametype;
+	int subclass = f->af.subclass;
+	struct {
+		struct ast_iax2_full_hdr fh;
+		struct iax_ie_data ied;
+	} data = {
+		.ied.buf = { 0 },
+		.ied.pos = 0,
+	};
+	/* total len - header len gives us the frame's IE len */
+	int ie_data_pos = f->datalen - sizeof(struct ast_iax2_full_hdr);
+
+	if (!pvt) {
+		return;  /* this should not be possible if called from socket_process() */
+	}
+
+	/* 
+	 * Check to make sure last frame sent is valid for call token resend
+	 * 1. Frame should _NOT_ already have a destination callno
+	 * 2. Frame must be a valid iax_frame subclass capable of starting dialog
+	 * 3. Pvt must have a calltoken_ie_len which represents the number of
+	 *    bytes at the end of the frame used for the previous calltoken ie.
+	 * 4. Pvt's calltoken_ie_len must be _LESS_ than the total IE length
+	 * 5. Total length of f->data must be _LESS_ than size of our data struct
+	 *    because f->data must be able to fit within data. 
+	 */
+	if (f->dcallno || !iax2_allow_new(frametype, subclass, 0)
+		|| !pvt->calltoken_ie_len || (pvt->calltoken_ie_len > ie_data_pos) ||
+		(f->datalen > sizeof(data))) {
+
+		return;  /* ignore resend, token was not valid for the dialog */
+	}
+
+	/* token is valid
+	 * 1. Copy frame data over
+	 * 2. Redo calltoken IE, it will always be the last ie in the frame.
+	 *    NOTE: Having the ie always be last is not protocol specified,
+	 *    it is only an implementation choice.  Since we only expect the ie to
+	 *    be last for frames we have sent, this can no way be affected by
+	 *    another end point.
+	 * 3. Remove frame from queue
+	 * 4. Free old frame
+	 * 5. Clear previous seqnos
+	 * 6. Resend with CALLTOKEN ie.
+	 */
+
+	/* ---1.--- */
+	memcpy(&data, f->data, f->datalen);
+	data.ied.pos = ie_data_pos;
+
+	/* ---2.--- */
+	/* move to the beginning of the calltoken ie so we can write over it */
+	data.ied.pos -= pvt->calltoken_ie_len;
+	iax_ie_append_str(&data.ied, IAX_IE_CALLTOKEN, newtoken);
+
+	/* make sure to update token length incase it ever has to be stripped off again */

[... 1441 lines stripped ...]



More information about the asterisk-commits mailing list