[Pkg-voip-commits] r10063 - in /asterisk/trunk/debian: changelog patches/AST-2012-014 patches/AST-2012-015 patches/fix-sip-tcp-no-FILE patches/fix-sip-tls-leak patches/series
tzafrir at alioth.debian.org
tzafrir at alioth.debian.org
Thu Jan 3 21:01:52 UTC 2013
Author: tzafrir
Date: Thu Jan 3 21:01:52 2013
New Revision: 10063
URL: http://svn.debian.org/wsvn/pkg-voip/?sc=1&rev=10063
Log:
Security fixes from Asterisk 1.8.19.1
* Patch AST-2012-014 (CVE-2012-5976) - fixes Crashes due to large stack
allocations when using TCP.
The following two fixes were also pulled in order to easily apply it:
- Patch fix-sip-tcp-no-FILE - Switch to reading with a recv loop
- Patch fix-sip-tls-leak - Memory leak in the SIP TLS code
* Patch AST-2012-015 (CVE-2012-5977) - Denial of Service Through
Exploitation of Device State Caching
Added:
asterisk/trunk/debian/patches/AST-2012-014
asterisk/trunk/debian/patches/AST-2012-015
asterisk/trunk/debian/patches/fix-sip-tcp-no-FILE
asterisk/trunk/debian/patches/fix-sip-tls-leak
Modified:
asterisk/trunk/debian/changelog
asterisk/trunk/debian/patches/series
Modified: asterisk/trunk/debian/changelog
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/changelog?rev=10063&op=diff
==============================================================================
--- asterisk/trunk/debian/changelog (original)
+++ asterisk/trunk/debian/changelog Thu Jan 3 21:01:52 2013
@@ -1,3 +1,15 @@
+asterisk (1:1.8.13.1~dfsg-2) UNRELEASED; urgency=high
+
+ * Patch AST-2012-014 (CVE-2012-5976) - fixes Crashes due to large stack
+ allocations when using TCP.
+ The following two fixes were also pulled in order to easily apply it:
+ - Patch fix-sip-tcp-no-FILE - Switch to reading with a recv loop
+ - Patch fix-sip-tls-leak - Memory leak in the SIP TLS code
+ * Patch AST-2012-015 (CVE-2012-5977) - Denial of Service Through
+ Exploitation of Device State Caching
+
+ -- Tzafrir Cohen <tzafrir at debian.org> Thu, 03 Jan 2013 22:46:21 +0200
+
asterisk (1:1.8.13.1~dfsg-1) unstable; urgency=low
* New upstream release (Closes: #680470):
Added: asterisk/trunk/debian/patches/AST-2012-014
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/patches/AST-2012-014?rev=10063&op=file
==============================================================================
--- asterisk/trunk/debian/patches/AST-2012-014 (added)
+++ asterisk/trunk/debian/patches/AST-2012-014 Thu Jan 3 21:01:52 2013
@@ -1,0 +1,298 @@
+From: Matthew Jordan <mjordan at digium.com>
+Date: Wed, 2 Jan 2013 15:16:10 +0000
+Subject: Resolve crashes due to large stack allocations when using TCP
+Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=378269
+CVE: CVE-2012-5976
+Bug: https://issues.asterisk.org/jira/browse/ASTERISK-20658
+
+Asterisk had several places where messages received over various network
+transports may be copied in a single stack allocation. In the case of TCP,
+since multiple packets in a stream may be concatenated together, this can
+lead to large allocations that overflow the stack.
+
+This patch modifies those portions of Asterisk using TCP to either
+favor heap allocations or use an upper bound to ensure that the stack will not
+overflow:
+ * For SIP, the allocation now has an upper limit
+ * For HTTP, the allocation is now a heap allocation instead of a stack
+ allocation
+ * For XMPP (in res_jabber), the allocation has been eliminated since it was
+ unnecesary.
+
+Note that the HTTP portion of this issue was independently found by Brandon
+Edwards of Exodus Intelligence.
+
+Reported by: wdoekes, Brandon Edwards
+Tested by: mmichelson, wdoekes
+See also: http://downloads.asterisk.org/pub/security/AST-2012-014.html
+
+---
+ channels/chan_sip.c | 58 +++++++++++++++++++++++++++++++-------------
+ channels/sip/include/sip.h | 1 +
+ main/http.c | 20 ++++++++++++---
+ res/res_jabber.c | 5 ++--
+ 4 files changed, 60 insertions(+), 24 deletions(-)
+
+diff --git a/channels/chan_sip.c b/channels/chan_sip.c
+index 18a0f7c..7e51ed5 100644
+--- a/channels/chan_sip.c
++++ b/channels/chan_sip.c
+@@ -2524,19 +2524,20 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ int authenticated, time_t start, struct sip_threadinfo *me)
+ {
+ int res, content_length, after_poll = 1, need_poll = 1;
++ size_t datalen = ast_str_strlen(req->data);
+ char buf[1024] = "";
+ int timeout = -1;
+-
+- /* Read in headers one line at a time */
+- while (ast_str_strlen(req->data) < 4 || strncmp(REQ_OFFSET_TO_STR(req, data->used - 4), "\r\n\r\n", 4)) {
+- if (!tcptls_session->client && !authenticated) {
+- if ((timeout = sip_check_authtimeout(start)) < 0) {
+- ast_debug(2, "SIP SSL server failed to determine authentication timeout\n");
++
++ /* Read in headers one line at a time */
++ while (datalen < 4 || strncmp(REQ_OFFSET_TO_STR(req, data->used - 4), "\r\n\r\n", 4)) {
++ if (!tcptls_session->client && !authenticated) {
++ if ((timeout = sip_check_authtimeout(start)) < 0) {
++ ast_debug(2, "SIP TLS server failed to determine authentication timeout\n");
+ return -1;
+ }
+
+ if (timeout == 0) {
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
++ ast_debug(2, "SIP TLS server timed out\n");
+ return -1;
+ }
+ } else {
+@@ -2551,11 +2552,11 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ after_poll = 1;
+ res = ast_wait_for_input(tcptls_session->fd, timeout);
+ if (res < 0) {
+- ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
++ ast_debug(2, "SIP TLS server :: ast_wait_for_input returned %d\n", res);
+ return -1;
+ } else if (res == 0) {
+ /* timeout */
+- ast_debug(2, "SIP TCP server timed out\n");
++ ast_debug(2, "SIP TLS server timed out\n");
+ return -1;
+ }
+ }
+@@ -2576,6 +2577,13 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ return -1;
+ }
+ ast_str_append(&req->data, 0, "%s", buf);
++
++ datalen = ast_str_strlen(req->data);
++ if (datalen > SIP_MAX_PACKET_SIZE) {
++ ast_log(LOG_WARNING, "Rejecting TLS packet from '%s' because way too large: %zu\n",
++ ast_sockaddr_stringify(&tcptls_session->remote_address), datalen);
++ return -1;
++ }
+ }
+ copy_request(reqcpy, req);
+ parse_request(reqcpy);
+@@ -2589,7 +2597,7 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ }
+
+ if (timeout == 0) {
+- ast_debug(2, "SIP SSL server timed out\n");
++ ast_debug(2, "SIP TLS server timed out\n");
+ return -1;
+ }
+ } else {
+@@ -2601,11 +2609,11 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ after_poll = 1;
+ res = ast_wait_for_input(tcptls_session->fd, timeout);
+ if (res < 0) {
+- ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
++ ast_debug(2, "SIP TLS server :: ast_wait_for_input returned %d\n", res);
+ return -1;
+ } else if (res == 0) {
+ /* timeout */
+- ast_debug(2, "SIP TCP server timed out\n");
++ ast_debug(2, "SIP TLS server timed out\n");
+ return -1;
+ }
+ }
+@@ -2628,6 +2636,13 @@ static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, str
+ }
+ content_length -= strlen(buf);
+ ast_str_append(&req->data, 0, "%s", buf);
++
++ datalen = ast_str_strlen(req->data);
++ if (datalen > SIP_MAX_PACKET_SIZE) {
++ ast_log(LOG_WARNING, "Rejecting TLS packet from '%s' because way too large: %zu\n",
++ ast_sockaddr_stringify(&tcptls_session->remote_address), datalen);
++ return -1;
++ }
+ }
+ }
+ /*! \todo XXX If there's no Content-Length or if the content-length and what
+@@ -2801,6 +2816,8 @@ static int sip_tcp_read(struct sip_request *req, struct ast_tcptls_session_insta
+ enum message_integrity message_integrity = MESSAGE_FRAGMENT;
+
+ while (message_integrity == MESSAGE_FRAGMENT) {
++ size_t datalen;
++
+ if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
+ char readbuf[4097];
+ int timeout;
+@@ -2840,6 +2857,13 @@ static int sip_tcp_read(struct sip_request *req, struct ast_tcptls_session_insta
+ ast_str_append(&req->data, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf));
+ ast_str_reset(tcptls_session->overflow_buf);
+ }
++
++ datalen = ast_str_strlen(req->data);
++ if (datalen > SIP_MAX_PACKET_SIZE) {
++ ast_log(LOG_WARNING, "Rejecting TCP packet from '%s' because way too large: %zu\n",
++ ast_sockaddr_stringify(&tcptls_session->remote_address), datalen);
++ return -1;
++ }
+
+ message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf);
+ }
+@@ -2911,7 +2935,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ }
+
+ me->threadid = pthread_self();
+- ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
++ ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP");
+
+ /* set up pollfd to watch for reads on both the socket and the alert_pipe */
+ fds[0].fd = tcptls_session->fd;
+@@ -2945,7 +2969,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ }
+
+ if (timeout == 0) {
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
++ ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "TLS": "TCP");
+ goto cleanup;
+ }
+ } else {
+@@ -2955,11 +2979,11 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
+ res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
+ if (res < 0) {
+- ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
++ ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "TLS": "TCP", res);
+ goto cleanup;
+ } else if (res == 0) {
+ /* timeout */
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
++ ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "TLS": "TCP");
+ goto cleanup;
+ }
+ }
+@@ -3041,7 +3065,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ }
+ }
+
+- ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
++ ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP");
+
+ cleanup:
+ if (tcptls_session && !tcptls_session->client && !authenticated) {
+diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
+index bb1588d..6040daa 100644
+--- a/channels/sip/include/sip.h
++++ b/channels/sip/include/sip.h
+@@ -96,6 +96,7 @@
+
+ #define SIP_MAX_HEADERS 64 /*!< Max amount of SIP headers to read */
+ #define SIP_MAX_LINES 256 /*!< Max amount of lines in SIP attachment (like SDP) */
++#define SIP_MAX_PACKET_SIZE 20480 /*!< Max SIP packet size */
+ #define SIP_MIN_PACKET 4096 /*!< Initialize size of memory to allocate for packets */
+ #define MAX_HISTORY_ENTRIES 50 /*!< Max entires in the history list for a sip_pvt */
+
+diff --git a/main/http.c b/main/http.c
+index a6c908d..991260f 100644
+--- a/main/http.c
++++ b/main/http.c
+@@ -622,6 +622,7 @@ struct ast_variable *ast_http_get_post_vars(
+ int content_length = 0;
+ struct ast_variable *v, *post_vars=NULL, *prev = NULL;
+ char *buf, *var, *val;
++ int res;
+
+ for (v = headers; v; v = v->next) {
+ if (!strcasecmp(v->name, "Content-Type")) {
+@@ -634,22 +635,28 @@ struct ast_variable *ast_http_get_post_vars(
+
+ for (v = headers; v; v = v->next) {
+ if (!strcasecmp(v->name, "Content-Length")) {
+- content_length = atoi(v->value) + 1;
++ content_length = atoi(v->value);
+ break;
+ }
+ }
+
+- if (!content_length) {
++ if (content_length <= 0) {
+ return NULL;
+ }
+
+- if (!(buf = alloca(content_length))) {
+- return NULL;
+- }
+- if (!fgets(buf, content_length, ser->f)) {
++ buf = ast_malloc(content_length + 1);
++ if (!buf) {
+ return NULL;
+ }
+
++ res = fread(buf, 1, content_length, ser->f);
++ if (res < content_length) {
++ /* Error, distinguishable by ferror() or feof(), but neither
++ * is good. */
++ goto done;
++ }
++ buf[content_length] = '\0';
++
+ while ((val = strsep(&buf, "&"))) {
+ var = strsep(&val, "=");
+ if (val) {
+@@ -665,6 +674,9 @@ struct ast_variable *ast_http_get_post_vars(
+ prev = v;
+ }
+ }
++
++done:
++ ast_free(buf);
+ return post_vars;
+ }
+
+diff --git a/res/res_jabber.c b/res/res_jabber.c
+index 7d4eb66..290cedc 100644
+--- a/res/res_jabber.c
++++ b/res/res_jabber.c
+@@ -766,7 +766,7 @@ static struct ast_custom_function jabberstatus_function = {
+ */
+ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen)
+ {
+- char *aux = NULL, *parse = NULL;
++ char *parse = NULL;
+ int timeout;
+ int jidlen, resourcelen;
+ struct timeval start;
+@@ -883,7 +883,7 @@ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, ch
+ continue;
+ }
+ found = 1;
+- aux = ast_strdupa(tmp->message);
++ ast_copy_string(buf, tmp->message, buflen);
+ AST_LIST_REMOVE_CURRENT(list);
+ aji_message_destroy(tmp);
+ break;
+@@ -908,7 +908,6 @@ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, ch
+ ast_log(LOG_NOTICE, "Timed out : no message received from %s\n", args.jid);
+ return -1;
+ }
+- ast_copy_string(buf, aux, buflen);
+
+ return 0;
+ }
+--
+1.7.10.4
+
Added: asterisk/trunk/debian/patches/AST-2012-015
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/patches/AST-2012-015?rev=10063&op=file
==============================================================================
--- asterisk/trunk/debian/patches/AST-2012-015 (added)
+++ asterisk/trunk/debian/patches/AST-2012-015 Thu Jan 3 21:01:52 2013
@@ -1,0 +1,1051 @@
+From: Matthew Jordan <mjordan at digium.com>
+Date: Wed, 2 Jan 2013 16:54:20 +0000
+Subject: Prevent exhaustion of system resources through exploitation of event cache
+CVE: CVE-2012-5977
+Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=378303
+Bug: https://issues.asterisk.org/jira/browse/ASTERISK-20175
+
+Asterisk maintains an internal cache for devices in the event subsystem. The
+device state cache holds the state of each device known to Asterisk, such that
+consumers of device state information can query for the last known state for
+a particular device, even if it is not part of an active call. The concept of
+a device in Asterisk can include entities that do not have a physical
+representation. One way that this occurred was when anonymous calls are allowed
+in Asterisk. A device was automatically created and stored in the cache for
+each anonymous call that occurred; this was possible in the SIP and IAX2
+channel drivers and through channel drivers that utilized the
+res_jabber/res_xmpp resource modules (Gtalk, Jingle, and Motif). These devices
+are never removed from the system, allowing anonymous calls to potentially
+exhaust a system's resources.
+
+This patch changes the event cache subsystem and device state management to
+no longer cache devices that are not associated with a physical entity.
+
+Reported by: Russell Bryant, Leif Madsen, Joshua Colp
+Tested by: kmoore
+See also: http://downloads.asterisk.org/pub/security/AST-2012-015.html
+
+---
+ apps/app_confbridge.c | 4 +--
+ apps/app_meetme.c | 16 +++++------
+ channels/chan_agent.c | 12 ++++----
+ channels/chan_dahdi.c | 7 +++--
+ channels/chan_iax2.c | 31 +++++++++++---------
+ channels/chan_local.c | 3 ++
+ channels/chan_sip.c | 18 +++++++-----
+ channels/chan_skinny.c | 16 +++++------
+ funcs/func_devstate.c | 6 ++--
+ include/asterisk/channel.h | 6 ++++
+ include/asterisk/devicestate.h | 16 +++++++++--
+ include/asterisk/event_defs.h | 8 +++++-
+ main/channel.c | 5 ++--
+ main/devicestate.c | 51 +++++++++++++++++++++------------
+ main/event.c | 1 +
+ main/features.c | 2 +-
+ res/res_calendar.c | 8 +++---
+ res/res_jabber.c | 61 ++++++++++++++++++++++++++++++----------
+ 18 files changed, 177 insertions(+), 94 deletions(-)
+
+diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
+index 18cfb74..a4eea43 100644
+--- a/apps/app_confbridge.c
++++ b/apps/app_confbridge.c
+@@ -486,7 +486,7 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct
+
+ /* Set the device state for this conference */
+ if (conference_bridge->users == 1) {
+- ast_devstate_changed(AST_DEVICE_INUSE, "confbridge:%s", conference_bridge->name);
++ ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "confbridge:%s", conference_bridge->name);
+ }
+
+ /* If the caller is a marked user or is waiting for a marked user to enter pass 'em off, otherwise pass them off to do regular joining stuff */
+@@ -568,7 +568,7 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge
+ }
+ } else {
+ /* Set device state to "not in use" */
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", conference_bridge->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "confbridge:%s", conference_bridge->name);
+
+ ao2_unlink(conference_bridges, conference_bridge);
+ }
+diff --git a/apps/app_meetme.c b/apps/app_meetme.c
+index 64e7883..1f2286e 100644
+--- a/apps/app_meetme.c
++++ b/apps/app_meetme.c
+@@ -2576,7 +2576,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
+
+ /* This device changed state now - if this is the first user */
+ if (conf->users == 1)
+- ast_devstate_changed(AST_DEVICE_INUSE, "meetme:%s", conf->confno);
++ ast_devstate_changed(AST_DEVICE_INUSE, (conf->isdynamic ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), "meetme:%s", conf->confno);
+
+ ast_mutex_unlock(&conf->playlock);
+
+@@ -3884,7 +3884,7 @@ bailoutandtrynormal:
+
+ /* Change any states */
+ if (!conf->users) {
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "meetme:%s", conf->confno);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, (conf->isdynamic ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), "meetme:%s", conf->confno);
+ }
+
+ /* Return the number of seconds the user was in the conf */
+@@ -5303,8 +5303,8 @@ static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk
+ || trunk_ref == exclude)
+ continue;
+ trunk_ref->state = state;
+- ast_devstate_changed(sla_state_to_devstate(state),
+- "SLA:%s_%s", station->name, trunk->name);
++ ast_devstate_changed(sla_state_to_devstate(state), AST_DEVSTATE_CACHABLE,
++ "SLA:%s_%s", station->name, trunk->name);
+ break;
+ }
+ }
+@@ -5802,8 +5802,8 @@ static void sla_handle_hold_event(struct sla_event *event)
+ {
+ ast_atomic_fetchadd_int((int *) &event->trunk_ref->trunk->hold_stations, 1);
+ event->trunk_ref->state = SLA_TRUNK_STATE_ONHOLD_BYME;
+- ast_devstate_changed(AST_DEVICE_ONHOLD, "SLA:%s_%s",
+- event->station->name, event->trunk_ref->trunk->name);
++ ast_devstate_changed(AST_DEVICE_ONHOLD, AST_DEVSTATE_CACHABLE, "SLA:%s_%s",
++ event->station->name, event->trunk_ref->trunk->name);
+ sla_change_trunk_state(event->trunk_ref->trunk, SLA_TRUNK_STATE_ONHOLD,
+ INACTIVE_TRUNK_REFS, event->trunk_ref);
+
+@@ -6312,8 +6312,8 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+ else {
+ trunk_ref->state = SLA_TRUNK_STATE_UP;
+- ast_devstate_changed(AST_DEVICE_INUSE,
+- "SLA:%s_%s", station->name, trunk_ref->trunk->name);
++ ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE,
++ "SLA:%s_%s", station->name, trunk_ref->trunk->name);
+ }
+ } else if (trunk_ref->state == SLA_TRUNK_STATE_RINGING) {
+ struct sla_ringing_trunk *ringing_trunk;
+diff --git a/channels/chan_agent.c b/channels/chan_agent.c
+index 06c64b2..92aa796 100644
+--- a/channels/chan_agent.c
++++ b/channels/chan_agent.c
+@@ -618,7 +618,7 @@ static struct ast_frame *agent_read(struct ast_channel *ast)
+ if (p->chan) {
+ p->chan->_bridge = NULL;
+ p->chan = NULL;
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ p->acknowledged = 0;
+ }
+ } else {
+@@ -875,7 +875,7 @@ static int agent_call(struct ast_channel *ast, char *dest, int timeout)
+ } else {
+ /* Agent hung-up */
+ p->chan = NULL;
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ }
+
+ if (!res) {
+@@ -994,7 +994,7 @@ static int agent_hangup(struct ast_channel *ast)
+ if (!p->loginstart) {
+ p->logincallerid[0] = '\0';
+ } else {
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ }
+
+ if (p->abouttograb) {
+@@ -2138,7 +2138,7 @@ static int login_exec(struct ast_channel *chan, const char *data)
+ }
+ ast_mutex_unlock(&p->lock);
+ AST_LIST_UNLOCK(&agents);
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ while (res >= 0) {
+ ast_mutex_lock(&p->lock);
+ if (p->deferlogoff && p->chan) {
+@@ -2159,7 +2159,7 @@ static int login_exec(struct ast_channel *chan, const char *data)
+ if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0) {
+ ast_debug(1, "Wrapup time for %s expired!\n", p->agent);
+ p->lastdisc = ast_tv(0, 0);
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ if (p->ackcall) {
+ check_beep(p, 0);
+ } else {
+@@ -2219,7 +2219,7 @@ static int login_exec(struct ast_channel *chan, const char *data)
+ ast_queue_log("NONE", chan->uniqueid, agent, "AGENTLOGOFF", "%s|%ld", chan->name, logintime);
+ ast_verb(2, "Agent '%s' logged out\n", p->agent);
+ /* If there is no owner, go ahead and kill it now */
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "Agent/%s", p->agent);
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "Agent/%s", p->agent);
+ if (p->dead && !p->owner) {
+ ast_mutex_destroy(&p->lock);
+ ast_cond_destroy(&p->app_complete_cond);
+diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
+index ccd8f3b..a95a8ce 100644
+--- a/channels/chan_dahdi.c
++++ b/channels/chan_dahdi.c
+@@ -3364,7 +3364,7 @@ static void dahdi_pri_update_span_devstate(struct sig_pri_span *pri)
+ }
+ if (pri->congestion_devstate != new_state) {
+ pri->congestion_devstate = new_state;
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/congestion", pri->span);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "DAHDI/I%d/congestion", pri->span);
+ }
+ #if defined(THRESHOLD_DEVSTATE_PLACEHOLDER)
+ /* Update the span threshold device state and report any change. */
+@@ -3380,7 +3380,7 @@ static void dahdi_pri_update_span_devstate(struct sig_pri_span *pri)
+ }
+ if (pri->threshold_devstate != new_state) {
+ pri->threshold_devstate = new_state;
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/threshold", pri->span);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "DAHDI/I%d/threshold", pri->span);
+ }
+ #endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */
+ }
+@@ -9804,7 +9804,8 @@ static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpb
+ if (dashptr) {
+ *dashptr = '\0';
+ }
+- ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, device_name);
++ tmp->flags |= AST_FLAG_DISABLE_DEVSTATE_CACHE;
++ ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, device_name);
+
+ for (v = i->vars ; v ; v = v->next)
+ pbx_builtin_setvar_helper(tmp, v->name, v->value);
+diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
+index d7eeb24..7a17c26 100644
+--- a/channels/chan_iax2.c
++++ b/channels/chan_iax2.c
+@@ -5729,7 +5729,7 @@ static int iax2_getpeertrunk(struct sockaddr_in sin)
+ }
+
+ /*! \brief Create new call, interface with the PBX core */
+-static struct ast_channel *ast_iax2_new(int callno, int state, format_t capability, const char *linkedid)
++static struct ast_channel *ast_iax2_new(int callno, int state, format_t capability, const char *linkedid, unsigned int cachable)
+ {
+ struct ast_channel *tmp;
+ struct chan_iax2_pvt *i;
+@@ -5798,6 +5798,10 @@ static struct ast_channel *ast_iax2_new(int callno, int state, format_t capabili
+ i->owner = tmp;
+ i->capability = capability;
+
++ if (!cachable) {
++ tmp->flags |= AST_FLAG_DISABLE_DEVSTATE_CACHE;
++ }
++
+ /* Set inherited variables */
+ if (i->vars) {
+ for (v = i->vars ; v ; v = v->next)
+@@ -8084,7 +8088,7 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
+ /* if challenge has been sent, but no challenge response if given, reject. */
+ goto return_unref;
+ }
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "IAX2/%s", p->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */
+
+ /* either Authentication has taken place, or a REGAUTH must be sent before verifying registration */
+ res = 0;
+@@ -8638,7 +8642,7 @@ static void __expire_registry(const void *data)
+ if (!ast_test_flag64(peer, IAX_TEMPONLY))
+ ast_db_del("IAX/Registry", peer->name);
+ register_peer_exten(peer, 0);
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "IAX2/%s", peer->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */
+ if (iax2_regfunk)
+ iax2_regfunk(peer->name, 0);
+
+@@ -8693,7 +8697,7 @@ static void reg_source_db(struct iax2_peer *p)
+ }
+ }
+
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "IAX2/%s", p->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */
+
+ p->expire = iax2_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, peer_ref(p));
+ if (p->expire == -1) {
+@@ -8770,14 +8774,14 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i
+ ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Registered\r\n", p->name);
+ register_peer_exten(p, 1);
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "IAX2/%s", p->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */
+ } else if (!ast_test_flag64(p, IAX_TEMPONLY)) {
+ ast_verb(3, "Unregistered IAX2 '%s' (%s)\n", p->name,
+ ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED");
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Unregistered\r\n", p->name);
+ register_peer_exten(p, 0);
+ ast_db_del("IAX/Registry", p->name);
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "IAX2/%s", p->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */
+ }
+ /* Update the host */
+ /* Verify that the host is really there */
+@@ -10291,7 +10295,8 @@ static int socket_process(struct iax2_thread *thread)
+ (f.frametype == AST_FRAME_IAX)) {
+ if (ast_test_flag64(iaxs[fr->callno], IAX_DELAYPBXSTART)) {
+ ast_clear_flag64(iaxs[fr->callno], IAX_DELAYPBXSTART);
+- if (!ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->chosenformat, NULL)) {
++ if (!ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->chosenformat, NULL,
++ ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED))) {
+ ast_variables_destroy(ies.vars);
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+@@ -10924,13 +10929,13 @@ static int socket_process(struct iax2_thread *thread)
+ if (iaxs[fr->callno]->pingtime <= peer->maxms) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now REACHABLE! Time: %d\n", peer->name, iaxs[fr->callno]->pingtime);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Reachable\r\nTime: %d\r\n", peer->name, iaxs[fr->callno]->pingtime);
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "IAX2/%s", peer->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */
+ }
+ } else if ((peer->historicms > 0) && (peer->historicms <= peer->maxms)) {
+ if (iaxs[fr->callno]->pingtime > peer->maxms) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now TOO LAGGED (%d ms)!\n", peer->name, iaxs[fr->callno]->pingtime);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Lagged\r\nTime: %d\r\n", peer->name, iaxs[fr->callno]->pingtime);
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "IAX2/%s", peer->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */
+ }
+ }
+ peer->lastms = iaxs[fr->callno]->pingtime;
+@@ -11172,7 +11177,7 @@ static int socket_process(struct iax2_thread *thread)
+ using_prefs);
+
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+- if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, format, NULL)))
++ if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, format, NULL, 1)))
+ iax2_destroy(fr->callno);
+ else if (ies.vars) {
+ struct ast_datastore *variablestore;
+@@ -11243,7 +11248,7 @@ immediatedial:
+ ast_getformatname_multiple(tmp, sizeof(tmp), iaxs[fr->callno]->peerformat));
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ send_command(iaxs[fr->callno], AST_FRAME_CONTROL, AST_CONTROL_PROGRESS, 0, NULL, 0, -1);
+- if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->peerformat, NULL)))
++ if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->peerformat, NULL, 1)))
+ iax2_destroy(fr->callno);
+ else if (ies.vars) {
+ struct ast_datastore *variablestore;
+@@ -11996,7 +12001,7 @@ static void __iax2_poke_noanswer(const void *data)
+ if (peer->lastms > -1) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Time: %d\n", peer->name, peer->lastms);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", peer->name, peer->lastms);
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "IAX2/%s", peer->name); /* Activate notification */
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */
+ }
+ if ((callno = peer->callno) > 0) {
+ ast_mutex_lock(&iaxsl[callno]);
+@@ -12164,7 +12169,7 @@ static struct ast_channel *iax2_request(const char *type, format_t format, const
+ if (cai.found)
+ ast_string_field_set(iaxs[callno], host, pds.peer);
+
+- c = ast_iax2_new(callno, AST_STATE_DOWN, cai.capability, requestor ? requestor->linkedid : NULL);
++ c = ast_iax2_new(callno, AST_STATE_DOWN, cai.capability, requestor ? requestor->linkedid : NULL, cai.found);
+
+ ast_mutex_unlock(&iaxsl[callno]);
+
+diff --git a/channels/chan_local.c b/channels/chan_local.c
+index 459bd5b..2d283eb 100644
+--- a/channels/chan_local.c
++++ b/channels/chan_local.c
+@@ -1229,6 +1229,9 @@ static struct ast_channel *local_new(struct local_pvt *p, int state, const char
+ tmp->tech_pvt = p;
+ tmp2->tech_pvt = p;
+
++ tmp->flags |= AST_FLAG_DISABLE_DEVSTATE_CACHE;
++ tmp2->flags |= AST_FLAG_DISABLE_DEVSTATE_CACHE;
++
+ p->owner = tmp;
+ p->chan = tmp2;
+ p->u_owner = ast_module_user_add(p->owner);
+diff --git a/channels/chan_sip.c b/channels/chan_sip.c
+index 7e51ed5..4687f87 100644
+--- a/channels/chan_sip.c
++++ b/channels/chan_sip.c
+@@ -6418,7 +6418,7 @@ static int update_call_counter(struct sip_pvt *fup, int event)
+ }
+
+ if (p) {
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", p->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", p->name);
+ unref_peer(p, "update_call_counter: unref_peer from call counter");
+ }
+ return 0;
+@@ -7609,6 +7609,9 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit
+ if (i->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+
++ if (!i->relatedpeer) {
++ tmp->flags |= AST_FLAG_DISABLE_DEVSTATE_CACHE;
++ }
+ /* Set channel variables for this call from configuration */
+ for (v = i->chanvars ; v ; v = v->next) {
+ char valuebuf[1024];
+@@ -14221,7 +14224,7 @@ static int expire_register(const void *data)
+
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
+ register_peer_exten(peer, FALSE); /* Remove regexten */
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name);
+
+ /* Do we need to release this peer from memory?
+ Only for realtime peers and autocreated peers
+@@ -15003,8 +15006,9 @@ static void sip_peer_hold(struct sip_pvt *p, int hold)
+ ast_atomic_fetchadd_int(&p->relatedpeer->onHold, (hold ? +1 : -1));
+
+ /* Request device state update */
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", p->relatedpeer->name);
+-
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, (p->owner->flags & AST_FLAG_DISABLE_DEVSTATE_CACHE ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE),
++ "SIP/%s", p->relatedpeer->name);
++
+ return;
+ }
+
+@@ -15422,7 +15426,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
+ }
+ }
+ if (!res) {
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name);
+ }
+ if (res < 0) {
+ switch (res) {
+@@ -21414,7 +21418,7 @@ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_req
+
+ ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n",
+ peer->name, s, pingtime, peer->maxms);
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name);
+ if (sip_cfg.peer_rtupdate) {
+ ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", str_lastms, SENTINEL);
+ }
+@@ -26870,7 +26874,7 @@ static int sip_poke_noanswer(const void *data)
+ /* Don't send a devstate change if nothing changed. */
+ if (peer->lastms > -1) {
+ peer->lastms = -1;
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name);
+ }
+
+ /* Try again quickly */
+diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
+index a92bd48..0b16a34 100644
+--- a/channels/chan_skinny.c
++++ b/channels/chan_skinny.c
+@@ -1925,7 +1925,7 @@ static int skinny_register(struct skinny_req *req, struct skinnysession *s)
+ register_exten(l);
+ /* initialize MWI on line and device */
+ mwi_event_cb(0, l);
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+ }
+ --instance;
+ }
+@@ -1963,7 +1963,7 @@ static int skinny_unregister(struct skinny_req *req, struct skinnysession *s)
+ l->instance = 0;
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: Skinny\r\nPeer: Skinny/%s@%s\r\nPeerStatus: Unregistered\r\n", l->name, d->name);
+ unregister_exten(l);
+- ast_devstate_changed(AST_DEVICE_UNAVAILABLE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+ }
+ }
+ }
+@@ -5321,7 +5321,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
+ ast_verb(1, "RECEIVED UNKNOWN STIMULUS: %d(%d/%d)\n", event, instance, callreference);
+ break;
+ }
+- ast_devstate_changed(AST_DEVICE_UNKNOWN, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+
+ return 1;
+ }
+@@ -5372,7 +5372,7 @@ static int handle_offhook_message(struct skinny_req *req, struct skinnysession *
+ transmit_ringer_mode(d, SKINNY_RING_OFF);
+ l->hookstate = SKINNY_OFFHOOK;
+
+- ast_devstate_changed(AST_DEVICE_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+
+ if (sub && sub->onhold) {
+ return 1;
+@@ -5448,7 +5448,7 @@ static int handle_onhook_message(struct skinny_req *req, struct skinnysession *s
+ return 0;
+ }
+
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+
+ if (sub->onhold) {
+ return 0;
+@@ -5834,7 +5834,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
+ return 0;
+ }
+
+- ast_devstate_changed(AST_DEVICE_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+
+ switch(event) {
+ case SOFTKEY_NONE:
+@@ -6049,7 +6049,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
+ transmit_callstate(d, l->instance, sub->callid, l->hookstate);
+ }
+
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+ if (skinnydebug)
+ ast_verb(1, "Skinny %s@%s went on hook\n", l->name, d->name);
+ if (l->transfer && sub->xferor && sub->owner->_state >= AST_STATE_RING) {
+@@ -6073,7 +6073,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
+ }
+ }
+ if ((l->hookstate == SKINNY_ONHOOK) && (AST_LIST_NEXT(sub, list) && !AST_LIST_NEXT(sub, list)->rtp)) {
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s@%s", l->name, d->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s@%s", l->name, d->name);
+ }
+ }
+ break;
+diff --git a/funcs/func_devstate.c b/funcs/func_devstate.c
+index e1f3438..6472610 100644
+--- a/funcs/func_devstate.c
++++ b/funcs/func_devstate.c
+@@ -132,7 +132,7 @@ static int devstate_write(struct ast_channel *chan, const char *cmd, char *data,
+
+ ast_db_put(astdb_family, data, value);
+
+- ast_devstate_changed(state_val, "Custom:%s", data);
++ ast_devstate_changed(state_val, AST_DEVSTATE_CACHABLE, "Custom:%s", data);
+
+ return 0;
+ }
+@@ -295,7 +295,7 @@ static char *handle_cli_devstate_change(struct ast_cli_entry *e, int cmd, struct
+
+ ast_db_put(astdb_family, dev, state);
+
+- ast_devstate_changed(state_val, "Custom:%s", dev);
++ ast_devstate_changed(state_val, AST_DEVSTATE_CACHABLE, "Custom:%s", dev);
+
+ return CLI_SUCCESS;
+ }
+@@ -341,7 +341,7 @@ static int load_module(void)
+ if (dev_name <= (const char *) 1)
+ continue;
+ ast_devstate_changed(ast_devstate_val(db_entry->data),
+- "Custom:%s\n", dev_name);
++ AST_DEVSTATE_CACHABLE, "Custom:%s\n", dev_name);
+ }
+ ast_db_freetree(db_tree);
+ db_tree = NULL;
+diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
+index d5aa046..f245b62 100644
+--- a/include/asterisk/channel.h
++++ b/include/asterisk/channel.h
+@@ -938,6 +938,12 @@ enum {
+ * some non-traditional dialplans (like AGI) to continue to function.
+ */
+ AST_FLAG_DISABLE_WORKAROUNDS = (1 << 20),
++ /*! Disable device state event caching. This allows allows channel
++ * drivers to selectively prevent device state events from being cached
++ * by certain channels such as anonymous calls which have no persistent
++ * represenatation that can be tracked.
++ */
++ AST_FLAG_DISABLE_DEVSTATE_CACHE = (1 << 21),
+ };
+
+ /*! \brief ast_bridge_config flags */
+diff --git a/include/asterisk/devicestate.h b/include/asterisk/devicestate.h
+index 66ca2bd..86740bc 100644
+--- a/include/asterisk/devicestate.h
++++ b/include/asterisk/devicestate.h
+@@ -61,6 +61,14 @@ enum ast_device_state {
+ AST_DEVICE_TOTAL, /*/ Total num of device states, used for testing */
+ };
+
++/*! \brief Device State Cachability
++ * \note This is used to define the cachability of a device state when set.
++ */
++enum ast_devstate_cache {
++ AST_DEVSTATE_NOT_CACHABLE, /*!< This device state is not cachable */
++ AST_DEVSTATE_CACHABLE, /*!< This device state is cachable */
++};
++
+ /*! \brief Devicestate provider call back */
+ typedef enum ast_device_state (*ast_devstate_prov_cb_type)(const char *data);
+
+@@ -129,6 +137,7 @@ enum ast_device_state ast_device_state(const char *device);
+ * \brief Tells Asterisk the State for Device is changed
+ *
+ * \param state the new state of the device
++ * \param cachable whether this device state is cachable
+ * \param fmt device name like a dial string with format parameters
+ *
+ * The new state of the device will be sent off to any subscribers
+@@ -138,13 +147,14 @@ enum ast_device_state ast_device_state(const char *device);
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+-int ast_devstate_changed(enum ast_device_state state, const char *fmt, ...)
+- __attribute__((format(printf, 2, 3)));
++int ast_devstate_changed(enum ast_device_state state, enum ast_devstate_cache cachable, const char *fmt, ...)
++ __attribute__((format(printf, 3, 4)));
+
+ /*!
+ * \brief Tells Asterisk the State for Device is changed
+ *
+ * \param state the new state of the device
++ * \param cachable whether this device state is cachable
+ * \param device device name like a dial string with format parameters
+ *
+ * The new state of the device will be sent off to any subscribers
+@@ -154,7 +164,7 @@ int ast_devstate_changed(enum ast_device_state state, const char *fmt, ...)
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+-int ast_devstate_changed_literal(enum ast_device_state state, const char *device);
++int ast_devstate_changed_literal(enum ast_device_state state, enum ast_devstate_cache cachable, const char *device);
+
+ /*!
+ * \brief Tells Asterisk the State for Device is changed.
+diff --git a/include/asterisk/event_defs.h b/include/asterisk/event_defs.h
+index 073d67b..c385bcf 100644
+--- a/include/asterisk/event_defs.h
++++ b/include/asterisk/event_defs.h
+@@ -283,8 +283,14 @@ enum ast_event_ie_type {
+ AST_EVENT_IE_CHALLENGE = 0x0032,
+ AST_EVENT_IE_RESPONSE = 0x0033,
+ AST_EVENT_IE_EXPECTED_RESPONSE = 0x0034,
++ /*!
++ * \brief Event non-cachability flag
++ * Used by: All events
++ * Payload type: UINT
++ */
++ AST_EVENT_IE_CACHABLE = 0x0035,
+ /*! \brief Must be the last IE value +1 */
+- AST_EVENT_IE_TOTAL = 0x0035,
++ AST_EVENT_IE_TOTAL = 0x0036,
+ };
+
+ /*!
+diff --git a/main/channel.c b/main/channel.c
+index 00f3bbb..50ed55c 100644
+--- a/main/channel.c
++++ b/main/channel.c
+@@ -299,6 +299,7 @@ static void channel_data_add_flags(struct ast_data *tree,
+ ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(chan, AST_FLAG_BRIDGE_HANGUP_RUN));
+ ast_data_add_bool(tree, "BRIDGE_HANGUP_DONT", ast_test_flag(chan, AST_FLAG_BRIDGE_HANGUP_DONT));
+ ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(chan, AST_FLAG_DISABLE_WORKAROUNDS));
++ ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(chan, AST_FLAG_DISABLE_DEVSTATE_CACHE));
+ }
+
+ #if defined(KEEP_TILL_CHANNEL_PARTY_NUMBER_INFO_NEEDED)
+@@ -2519,7 +2520,7 @@ static void ast_channel_destructor(void *obj)
+ * instance is dead, we don't know the state of all other possible
+ * instances.
+ */
+- ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, device_name);
++ ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, (chan->flags & AST_FLAG_DISABLE_DEVSTATE_CACHE ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), device_name);
+ }
+ ast_atomic_fetchadd_int(&chancount, -1);
+ }
+@@ -7058,7 +7059,7 @@ int ast_setstate(struct ast_channel *chan, enum ast_channel_state state)
+ /* We have to pass AST_DEVICE_UNKNOWN here because it is entirely possible that the channel driver
+ * for this channel is using the callback method for device state. If we pass in an actual state here
+ * we override what they are saying the state is and things go amuck. */
+- ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, name);
++ ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, (chan->flags & AST_FLAG_DISABLE_DEVSTATE_CACHE ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), name);
+
+ /* setstate used to conditionally report Newchannel; this is no more */
+ ast_manager_event(chan, EVENT_FLAG_CALL, "Newstate",
+diff --git a/main/devicestate.c b/main/devicestate.c
+index 84eedc8..aabf806 100644
+--- a/main/devicestate.c
++++ b/main/devicestate.c
+@@ -174,6 +174,7 @@ static AST_RWLIST_HEAD_STATIC(devstate_provs, devstate_prov);
+
+ struct state_change {
+ AST_LIST_ENTRY(state_change) list;
++ enum ast_devstate_cache cachable;
+ char device[1];
+ };
+
+@@ -191,6 +192,7 @@ struct devstate_change {
+ AST_LIST_ENTRY(devstate_change) entry;
+ uint32_t state;
+ struct ast_eid eid;
++ enum ast_devstate_cache cachable;
+ char device[1];
+ };
+
+@@ -424,7 +426,7 @@ static int getproviderstate(const char *provider, const char *address)
+ return res;
+ }
+
+-static void devstate_event(const char *device, enum ast_device_state state)
++static void devstate_event(const char *device, enum ast_device_state state, int cachable)
+ {
+ struct ast_event *event;
+ enum ast_event_type event_type;
+@@ -440,18 +442,23 @@ static void devstate_event(const char *device, enum ast_device_state state)
+ ast_debug(3, "device '%s' state '%d'\n", device, state);
+
+ if (!(event = ast_event_new(event_type,
+- AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device,
+- AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
+- AST_EVENT_IE_END))) {
++ AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device,
++ AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
++ AST_EVENT_IE_CACHABLE, AST_EVENT_IE_PLTYPE_UINT, cachable,
++ AST_EVENT_IE_END))) {
+ return;
+ }
+
+- ast_event_queue_and_cache(event);
++ if (cachable) {
++ ast_event_queue_and_cache(event);
++ } else {
++ ast_event_queue(event);
++ }
+ }
+
+ /*! Called by the state change thread to find out what the state is, and then
+ * to queue up the state change event */
+-static void do_state_change(const char *device)
++static void do_state_change(const char *device, int cachable)
+ {
+ enum ast_device_state state;
+
+@@ -459,10 +466,10 @@ static void do_state_change(const char *device)
+
+ ast_debug(3, "Changing state for %s - state %d (%s)\n", device, state, ast_devstate2str(state));
+
+- devstate_event(device, state);
++ devstate_event(device, state, cachable);
+ }
+
+-int ast_devstate_changed_literal(enum ast_device_state state, const char *device)
++int ast_devstate_changed_literal(enum ast_device_state state, enum ast_devstate_cache cachable, const char *device)
+ {
+ struct state_change *change;
+
+@@ -483,14 +490,15 @@ int ast_devstate_changed_literal(enum ast_device_state state, const char *device
+ */
+
+ if (state != AST_DEVICE_UNKNOWN) {
+- devstate_event(device, state);
++ devstate_event(device, state, cachable);
+ } else if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
+ /* we could not allocate a change struct, or */
+ /* there is no background thread, so process the change now */
+- do_state_change(device);
++ do_state_change(device, cachable);
+ } else {
+ /* queue the change */
+ strcpy(change->device, device);
++ change->cachable = cachable;
+ AST_LIST_LOCK(&state_changes);
+ AST_LIST_INSERT_TAIL(&state_changes, change, list);
+ ast_cond_signal(&change_pending);
+@@ -502,10 +510,10 @@ int ast_devstate_changed_literal(enum ast_device_state state, const char *device
+
+ int ast_device_state_changed_literal(const char *dev)
+ {
+- return ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, dev);
++ return ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, dev);
+ }
+
+-int ast_devstate_changed(enum ast_device_state state, const char *fmt, ...)
++int ast_devstate_changed(enum ast_device_state state, enum ast_devstate_cache cachable, const char *fmt, ...)
+ {
+ char buf[AST_MAX_EXTENSION];
+ va_list ap;
+@@ -514,7 +522,7 @@ int ast_devstate_changed(enum ast_device_state state, const char *fmt, ...)
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+- return ast_devstate_changed_literal(state, buf);
++ return ast_devstate_changed_literal(state, cachable, buf);
+ }
+
+ int ast_device_state_changed(const char *fmt, ...)
+@@ -526,7 +534,7 @@ int ast_device_state_changed(const char *fmt, ...)
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+- return ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, buf);
++ return ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, buf);
+ }
+
+ /*! \brief Go through the dev state change queue and update changes in the dev state thread */
+@@ -546,7 +554,7 @@ static void *do_devstate_changes(void *data)
+ /* Process each state change */
+ while ((current = next)) {
+ next = AST_LIST_NEXT(current, list);
+- do_state_change(current->device);
++ do_state_change(current->device, current->cachable);
+ ast_free(current);
+ }
+ }
+@@ -590,7 +598,7 @@ static void devstate_cache_cb(const struct ast_event *event, void *data)
+ collection->num_states++;
+ }
+
+-static void process_collection(const char *device, struct change_collection *collection)
++static void process_collection(const char *device, enum ast_devstate_cache cachable, struct change_collection *collection)
+ {
+ int i;
+ struct ast_devstate_aggregate agg;
+@@ -641,7 +649,11 @@ static void process_collection(const char *device, struct change_collection *col
+ return;
+ }
+
+- ast_event_queue_and_cache(event);
++ if (cachable) {
++ ast_event_queue_and_cache(event);
++ } else {
++ ast_event_queue(event);
++ }
+ }
+
+ static void handle_devstate_change(struct devstate_change *sc)
+@@ -667,7 +679,7 @@ static void handle_devstate_change(struct devstate_change *sc)
+ /* Populate the collection of device states from the cache */
+ ast_event_dump_cache(tmp_sub);
+
+- process_collection(sc->device, &collection);
++ process_collection(sc->device, sc->cachable, &collection);
+
+ ast_event_sub_destroy(tmp_sub);
+ }
+@@ -696,10 +708,12 @@ static void devstate_change_collector_cb(const struct ast_event *event, void *da
+ const char *device;
+ const struct ast_eid *eid;
+ uint32_t state;
++ enum ast_devstate_cache cachable = AST_DEVSTATE_CACHABLE;
+
+ device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE);
+ eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID);
+ state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
++ cachable = ast_event_get_ie_uint(event, AST_EVENT_IE_CACHABLE);
+
+ if (ast_strlen_zero(device) || !eid) {
+ ast_log(LOG_ERROR, "Invalid device state change event received\n");
+@@ -712,6 +726,7 @@ static void devstate_change_collector_cb(const struct ast_event *event, void *da
+ strcpy(sc->device, device);
+ sc->eid = *eid;
+ sc->state = state;
++ sc->cachable = cachable;
+
+ ast_mutex_lock(&devstate_collector.lock);
+ AST_LIST_INSERT_TAIL(&devstate_collector.devstate_change_q, sc, entry);
+diff --git a/main/event.c b/main/event.c
+index 328dc1e..85136c5 100644
+--- a/main/event.c
++++ b/main/event.c
+@@ -268,6 +268,7 @@ static const struct ie_map {
+ [AST_EVENT_IE_CHALLENGE] = { AST_EVENT_IE_PLTYPE_STR, "Challenge" },
+ [AST_EVENT_IE_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "Response" },
+ [AST_EVENT_IE_EXPECTED_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "ExpectedResponse" },
++ [AST_EVENT_IE_CACHABLE] = { AST_EVENT_IE_PLTYPE_UINT, "Cachable" },
+ };
+
+ const char *ast_event_get_type_name(const struct ast_event *event)
+diff --git a/main/features.c b/main/features.c
+index 2f08a98..00edb76 100644
+--- a/main/features.c
++++ b/main/features.c
+@@ -1094,7 +1094,7 @@ static void notify_metermaids(const char *exten, char *context, enum ast_device_
+ ast_debug(4, "Notification of state change to metermaids %s@%s\n to state '%s'",
+ exten, context, ast_devstate2str(state));
+
+- ast_devstate_changed(state, "park:%s@%s", exten, context);
++ ast_devstate_changed(state, AST_DEVSTATE_CACHABLE, "park:%s@%s", exten, context);
+ }
+
+ /*! \brief metermaids callback from devicestate.c */
+diff --git a/res/res_calendar.c b/res/res_calendar.c
+index 959c049..7c8518b 100644
+--- a/res/res_calendar.c
++++ b/res/res_calendar.c
+@@ -575,9 +575,9 @@ static struct ast_calendar_event *destroy_event(struct ast_calendar_event *event
+ * but haven't hit the end event yet, go ahead and set the devicestate to the current busy status */
+ if (event->bs_start_sched < 0 && event->bs_end_sched >= 0) {
+ if (!calendar_is_busy(event->owner)) {
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Calendar:%s", event->owner->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Calendar:%s", event->owner->name);
+ } else {
+- ast_devstate_changed(AST_DEVICE_BUSY, "Calendar:%s", event->owner->name);
++ ast_devstate_changed(AST_DEVICE_BUSY, AST_DEVSTATE_CACHABLE, "Calendar:%s", event->owner->name);
+ }
+ }
+
+@@ -818,9 +818,9 @@ static int calendar_devstate_change(const void *data)
+ /* We can have overlapping events, so ignore the event->busy_state and check busy state
+ * based on all events in the calendar */
+ if (!calendar_is_busy(event->owner)) {
+- ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Calendar:%s", event->owner->name);
++ ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Calendar:%s", event->owner->name);
+ } else {
+- ast_devstate_changed(AST_DEVICE_BUSY, "Calendar:%s", event->owner->name);
++ ast_devstate_changed(AST_DEVICE_BUSY, AST_DEVSTATE_CACHABLE, "Calendar:%s", event->owner->name);
+ }
+
+ event = ast_calendar_unref_event(event);
+diff --git a/res/res_jabber.c b/res/res_jabber.c
+index 290cedc..a00b8fe 100644
+--- a/res/res_jabber.c
++++ b/res/res_jabber.c
+@@ -349,7 +349,7 @@ static char *aji_cli_create_leafnode(struct ast_cli_entry *e, int cmd,
+ static void aji_create_affiliations(struct aji_client *client, const char *node);
+ static iks* aji_pubsub_iq_create(struct aji_client *client, const char *type);
+ static void aji_publish_device_state(struct aji_client *client, const char * device,
+- const char *device_state);
++ const char *device_state, unsigned int cachable);
+ static int aji_handle_pubsub_error(void *data, ikspak *pak);
+ static int aji_handle_pubsub_event(void *data, ikspak *pak);
+ static void aji_pubsub_subscribe(struct aji_client *client, const char *node);
+@@ -363,7 +363,7 @@ static void aji_publish_mwi(struct aji_client *client, const char *mailbox,
+ static void aji_devstate_cb(const struct ast_event *ast_event, void *data);
+ static void aji_mwi_cb(const struct ast_event *ast_event, void *data);
+ static iks* aji_build_publish_skeleton(struct aji_client *client, const char *node,
+- const char *event_type);
++ const char *event_type, unsigned int cachable);
+ /* No transports in this version */
+ /*
+ static int aji_create_transport(char *label, struct aji_client *client);
+@@ -3207,6 +3207,7 @@ static void aji_devstate_cb(const struct ast_event *ast_event, void *data)
+ {
+ const char *device;
+ const char *device_state;
++ unsigned int cachable;
+ struct aji_client *client;
+ if (ast_eid_cmp(&ast_eid_default, ast_event_get_ie_raw(ast_event, AST_EVENT_IE_EID)))
+ {
+@@ -3218,7 +3219,8 @@ static void aji_devstate_cb(const struct ast_event *ast_event, void *data)
+ client = ASTOBJ_REF((struct aji_client *) data);
+ device = ast_event_get_ie_str(ast_event, AST_EVENT_IE_DEVICE);
+ device_state = ast_devstate_str(ast_event_get_ie_uint(ast_event, AST_EVENT_IE_STATE));
+- aji_publish_device_state(client, device, device_state);
++ cachable = ast_event_get_ie_uint(ast_event, AST_EVENT_IE_CACHABLE);
++ aji_publish_device_state(client, device, device_state, cachable);
+ ASTOBJ_UNREF(client, ast_aji_client_destroy);
+ }
+
+@@ -3258,11 +3260,13 @@ static void aji_init_event_distribution(struct aji_client *client)
+ */
+ static int aji_handle_pubsub_event(void *data, ikspak *pak)
+ {
+- char *item_id, *device_state, *context;
++ char *item_id, *device_state, *context, *cachable_str;
+ int oldmsgs, newmsgs;
+ iks *item, *item_content;
+ struct ast_eid pubsub_eid;
+ struct ast_event *event;
++ unsigned int cachable = AST_DEVSTATE_CACHABLE;
++
+ item = iks_find(iks_find(iks_find(pak->x, "event"), "items"), "item");
+ if (!item) {
+ ast_log(LOG_ERROR, "Could not parse incoming PubSub event\n");
+@@ -3277,11 +3281,14 @@ static int aji_handle_pubsub_event(void *data, ikspak *pak)
+ }
+ if (!strcasecmp(iks_name(item_content), "state")) {
+ device_state = iks_find_cdata(item, "state");
++ if ((cachable_str = iks_find_cdata(item, "cachable"))) {
++ sscanf(cachable_str, "%30d", &cachable);
++ }
+ if (!(event = ast_event_new(AST_EVENT_DEVICE_STATE_CHANGE,
+- AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, item_id, AST_EVENT_IE_STATE,
+- AST_EVENT_IE_PLTYPE_UINT, ast_devstate_val(device_state), AST_EVENT_IE_EID,
+- AST_EVENT_IE_PLTYPE_RAW, &pubsub_eid, sizeof(pubsub_eid),
+- AST_EVENT_IE_END))) {
++ AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, item_id, AST_EVENT_IE_STATE,
++ AST_EVENT_IE_PLTYPE_UINT, ast_devstate_val(device_state), AST_EVENT_IE_EID,
++ AST_EVENT_IE_PLTYPE_RAW, &pubsub_eid, sizeof(pubsub_eid),
++ AST_EVENT_IE_END))) {
+ return IKS_FILTER_EAT;
+ }
+ } else if (!strcasecmp(iks_name(item_content), "mailbox")) {
+@@ -3301,7 +3308,13 @@ static int aji_handle_pubsub_event(void *data, ikspak *pak)
+ iks_name(item_content));
+ return IKS_FILTER_EAT;
+ }
+- ast_event_queue_and_cache(event);
++
++ if (cachable == AST_DEVSTATE_CACHABLE) {
++ ast_event_queue_and_cache(event);
++ } else {
++ ast_event_queue(event);
++ }
++
+ return IKS_FILTER_EAT;
+ }
+
+@@ -3376,7 +3389,7 @@ static void aji_pubsub_subscribe(struct aji_client *client, const char *node)
+ * \return iks *
+ */
+ static iks* aji_build_publish_skeleton(struct aji_client *client, const char *node,
+- const char *event_type)
++ const char *event_type, unsigned int cachable)
+ {
+ iks *request = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *publish, *item;
+@@ -3390,8 +3403,24 @@ static iks* aji_build_publish_skeleton(struct aji_client *client, const char *no
+ }
+ item = iks_insert(publish, "item");
+ iks_insert_attrib(item, "id", node);
+- return item;
+
++ if (cachable == AST_DEVSTATE_NOT_CACHABLE) {
++ iks *options, *x, *field_form_type, *field_persist;
++
++ options = iks_insert(pubsub, "publish-options");
++ x = iks_insert(options, "x");
++ iks_insert_attrib(x, "xmlns", "jabber:x:data");
++ iks_insert_attrib(x, "type", "submit");
++ field_form_type = iks_insert(x, "field");
++ iks_insert_attrib(field_form_type, "var", "FORM_TYPE");
++ iks_insert_attrib(field_form_type, "type", "hidden");
++ iks_insert_cdata(iks_insert(field_form_type, "value"), "http://jabber.org/protocol/pubsub#publish-options", 0);
++ field_persist = iks_insert(x, "field");
++ iks_insert_attrib(field_persist, "var", "pubsub#persist_items");
++ iks_insert_cdata(iks_insert(field_persist, "value"), "0", 1);
++ }
++
++ return item;
+ }
+
+ /*!
+@@ -3402,11 +3431,11 @@ static iks* aji_build_publish_skeleton(struct aji_client *client, const char *no
+ * \return void
+ */
+ static void aji_publish_device_state(struct aji_client *client, const char *device,
+- const char *device_state)
++ const char *device_state, unsigned int cachable)
+ {
+- iks *request = aji_build_publish_skeleton(client, device, "device_state");
++ iks *request = aji_build_publish_skeleton(client, device, "device_state", cachable);
+ iks *state;
+- char eid_str[20];
++ char eid_str[20], cachable_str[2];
+ if (ast_test_flag(&pubsubflags, AJI_PUBSUB_AUTOCREATE)) {
+ if (ast_test_flag(&pubsubflags, AJI_XEP0248)) {
+ aji_create_pubsub_node(client, "leaf", device, "device_state");
+@@ -3418,6 +3447,8 @@ static void aji_publish_device_state(struct aji_client *client, const char *devi
+ state = iks_insert(request, "state");
+ iks_insert_attrib(state, "xmlns", "http://asterisk.org");
+ iks_insert_attrib(state, "eid", eid_str);
++ snprintf(cachable_str, sizeof(cachable_str), "%u", cachable);
++ iks_insert_attrib(state, "cachable", cachable_str);
+ iks_insert_cdata(state, device_state, strlen(device_state));
+ ast_aji_send(client, iks_root(request));
+ iks_delete(request);
+@@ -3437,7 +3468,7 @@ static void aji_publish_mwi(struct aji_client *client, const char *mailbox,
+ char eid_str[20];
+ iks *mailbox_node, *request;
+ snprintf(full_mailbox, sizeof(full_mailbox), "%s@%s", mailbox, context);
+- request = aji_build_publish_skeleton(client, full_mailbox, "message_waiting");
++ request = aji_build_publish_skeleton(client, full_mailbox, "message_waiting", 1);
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ mailbox_node = iks_insert(request, "mailbox");
+ iks_insert_attrib(mailbox_node, "xmlns", "http://asterisk.org");
+--
+1.7.10.4
+
Added: asterisk/trunk/debian/patches/fix-sip-tcp-no-FILE
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/patches/fix-sip-tcp-no-FILE?rev=10063&op=file
==============================================================================
--- asterisk/trunk/debian/patches/fix-sip-tcp-no-FILE (added)
+++ asterisk/trunk/debian/patches/fix-sip-tcp-no-FILE Thu Jan 3 21:01:52 2013
@@ -1,0 +1,1105 @@
+From: Mark Michelson <mmichelson at digium.com>
+Date: Fri, 12 Oct 2012 15:57:40 +0000
+Subject: Do not use a FILE handle when doing SIP TCP reads.
+Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=374905
+Bug: https://issues.asterisk.org/jira/browse/ASTERISK-20212
+
+This is used to solve an issue where a poll on a file
+descriptor does not necessarily correspond to the readiness
+of a FILE handle to be read.
+
+This change makes it so that for TCP connections, we do a
+recv() on the file descriptor instead.
+
+Because TCP does not guarantee that an entire message or even
+just one single message will arrive during a read, a loop has
+been introduced to ensure that we only attempt to handle a
+single message at a time. The tcptls_session_instance structure
+has also had an overflow buffer added to it so that if more
+than one TCP message arrives in one go, there is a place to
+throw the excess.
+
+Huge thanks goes out to Walter Doekes for doing extensive review
+on this change and finding edge cases where code could fail.
+
+reported by Phil Ciccone
+
+Review: https://reviewboard.asterisk.org/r/2123
+
+---
+ channels/chan_sip.c | 968 +++++++++++++++++++++++++++++++++++++++------
+ include/asterisk/tcptls.h | 6 +
+ main/tcptls.c | 3 +
+ 3 files changed, 861 insertions(+), 116 deletions(-)
+
+diff --git a/channels/chan_sip.c b/channels/chan_sip.c
+index 8f96f7a..6c3ccfe 100644
+--- a/channels/chan_sip.c
++++ b/channels/chan_sip.c
+@@ -2500,12 +2500,363 @@ static int sip_check_authtimeout(time_t start)
+ return timeout;
+ }
+
++/*!
++ * \brief Read a SIP request or response from a TLS connection
++ *
++ * Because TLS operations are hidden from view via a FILE handle, the
++ * logic for reading data is a bit complex, and we have to make periodic
++ * checks to be sure we aren't taking too long to perform the necessary
++ * action.
++ *
++ * \todo XXX This should be altered in the future not to use a FILE pointer
++ *
++ * \param req The request structure to fill in
++ * \param tcptls_session The TLS connection on which the data is being received
++ * \param authenticated A flag indicating whether authentication has occurred yet.
++ * This is only relevant in a server role.
++ * \param start The time at which we started attempting to read data. Used in
++ * determining if there has been a timeout.
++ * \param me Thread info. Used as a means of determining if the session needs to be stoppped.
++ * \retval -1 Failed to read data
++ * \retval 0 Succeeded in reading data
++ */
++static int sip_tls_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start, struct sip_threadinfo *me)
++{
++ int res, content_length, after_poll = 1, need_poll = 1;
++ struct sip_request reqcpy = { 0, };
++ char buf[1024] = "";
++ int timeout = -1;
++
++ /* Read in headers one line at a time */
++ while (ast_str_strlen(req->data) < 4 || strncmp(REQ_OFFSET_TO_STR(req, data->used - 4), "\r\n\r\n", 4)) {
++ if (!tcptls_session->client && !authenticated) {
++ if ((timeout = sip_check_authtimeout(start)) < 0) {
++ ast_debug(2, "SIP SSL server failed to determine authentication timeout\n");
++ return -1;
++ }
++
++ if (timeout == 0) {
++ ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
++ return -1;
++ }
++ } else {
++ timeout = -1;
++ }
++
++ /* special polling behavior is required for TLS
++ * sockets because of the buffering done in the
++ * TLS layer */
++ if (need_poll) {
++ need_poll = 0;
++ after_poll = 1;
++ res = ast_wait_for_input(tcptls_session->fd, timeout);
++ if (res < 0) {
++ ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
++ return -1;
++ } else if (res == 0) {
++ /* timeout */
++ ast_debug(2, "SIP TCP server timed out\n");
++ return -1;
++ }
++ }
++
++ ast_mutex_lock(&tcptls_session->lock);
++ if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
++ ast_mutex_unlock(&tcptls_session->lock);
++ if (after_poll) {
++ return -1;
++ } else {
++ need_poll = 1;
++ continue;
++ }
++ }
++ ast_mutex_unlock(&tcptls_session->lock);
++ after_poll = 0;
++ if (me->stop) {
++ return -1;
++ }
++ ast_str_append(&req->data, 0, "%s", buf);
++ }
++ copy_request(&reqcpy, req);
++ parse_request(&reqcpy);
++ /* In order to know how much to read, we need the content-length header */
++ if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &content_length)) {
++ while (content_length > 0) {
++ size_t bytes_read;
++ if (!tcptls_session->client && !authenticated) {
++ if ((timeout = sip_check_authtimeout(start)) < 0) {
++ return -1;
++ }
++
++ if (timeout == 0) {
++ ast_debug(2, "SIP SSL server timed out\n");
++ return -1;
++ }
++ } else {
++ timeout = -1;
++ }
++
++ if (need_poll) {
++ need_poll = 0;
++ after_poll = 1;
++ res = ast_wait_for_input(tcptls_session->fd, timeout);
++ if (res < 0) {
++ ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
++ return -1;
++ } else if (res == 0) {
++ /* timeout */
++ ast_debug(2, "SIP TCP server timed out\n");
++ return -1;
++ }
++ }
++
++ ast_mutex_lock(&tcptls_session->lock);
++ if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, content_length), tcptls_session->f))) {
++ ast_mutex_unlock(&tcptls_session->lock);
++ if (after_poll) {
++ return -1;
++ } else {
++ need_poll = 1;
++ continue;
++ }
++ }
++ buf[bytes_read] = '\0';
++ ast_mutex_unlock(&tcptls_session->lock);
++ after_poll = 0;
++ if (me->stop) {
++ return -1;
++ }
++ content_length -= strlen(buf);
++ ast_str_append(&req->data, 0, "%s", buf);
++ }
++ }
++ /*! \todo XXX If there's no Content-Length or if the content-length and what
++ we receive is not the same - we should generate an error */
++ return 0;
++}
++
++/*!
++ * \brief Indication of a TCP message's integrity
++ */
++enum message_integrity {
++ /*!
++ * The message has an error in it with
++ * regards to its Content-Length header
++ */
++ MESSAGE_INVALID,
++ /*!
++ * The message is incomplete
++ */
++ MESSAGE_FRAGMENT,
++ /*!
++ * The data contains a complete message
++ * plus a fragment of another.
++ */
++ MESSAGE_FRAGMENT_COMPLETE,
++ /*!
++ * The message is complete
++ */
++ MESSAGE_COMPLETE,
++};
++
++/*!
++ * \brief
++ * Get the content length from an unparsed SIP message
++ *
++ * \param message The unparsed SIP message headers
++ * \return The value of the Content-Length header or -1 if message is invalid
++ */
++static int read_raw_content_length(const char *message)
++{
++ char *end_of_line;
++ char *content_length_str;
++ char *l_str;
++ int content_length;
++ char *msg;
++
++ if (sip_cfg.pedanticsipchecking) {
++ struct ast_str *msg_copy = ast_str_create(strlen(message));
++ if (!msg_copy) {
++ return -1;
++ }
++ ast_str_set(&msg_copy, 0, "%s", message);
++ lws2sws(msg_copy);
++ msg = ast_strdupa(ast_str_buffer(msg_copy));
++ ast_free(msg_copy);
++ } else {
++ msg = ast_strdupa(message);
++ }
++
++ /* Let's find a Content-Length header */
++ content_length_str = strcasestr(msg, "\nContent-Length:");
++ if (!content_length_str && !(l_str = strcasestr(msg, "\nl:"))) {
++ /* RFC 3261 18.3
++ * "In the case of stream-oriented transports such as TCP, the Content-
++ * Length header field indicates the size of the body. The Content-
++ * Length header field MUST be used with stream oriented transports."
++ */
++ return -1;
++ }
++ if (content_length_str) {
++ content_length_str += sizeof("\nContent-Length:");
++ } else if (l_str) {
++ content_length_str = l_str + sizeof("\nl:");
++ } else {
++ return -1;
++ }
++
++ end_of_line = strchr(content_length_str, '\n');
++
++ if (!end_of_line) {
++ return -1;
++ }
++
++ if (sscanf(content_length_str, "%30d", &content_length) == 1) {
++ return content_length;
++ }
++
++ return -1;
++}
++
++/*!
++ * \brief Check that a message received over TCP is a full message
++ *
++ * This will take the information read in and then determine if
++ * 1) The message is a full SIP request
++ * 2) The message is a partial SIP request
++ * 3) The message contains a full SIP request along with another partial request
++ * \param data The unparsed incoming SIP message.
++ * \param request The resulting request with extra fragments removed.
++ * \param overflow If the message contains more than a full request, this is the remainder of the message
++ * \return The resulting integrity of the message
++ */
++static enum message_integrity check_message_integrity(struct ast_str **request, struct ast_str **overflow)
++{
++ char *message = ast_strdupa(ast_str_buffer(*request));
++ char *body;
++ int content_length;
++ int body_len;
++ int message_len = strlen(message);
++
++ /* Important pieces to search for in a SIP request are \r\n\r\n. This
++ * marks either
++ * 1) The division between the headers and body
++ * 2) The end of the SIP request
++ */
++ body = strstr(message, "\r\n\r\n");
++ if (!body) {
++ /* This is clearly a partial message since we haven't reached an end
++ * yet.
++ */
++ return MESSAGE_FRAGMENT;
++ }
++ body += sizeof("\r\n\r\n") - 1;
++ body_len = strlen(body);
++
++ body[-1] = '\0';
++ content_length = read_raw_content_length(message);
++ body[-1] = '\n';
++
++ if (content_length < 0) {
++ return MESSAGE_INVALID;
++ } else if (content_length == 0) {
++ /* We've definitely received an entire message. We need
++ * to check if there's also a fragment of another message
++ * in addition.
++ */
++ if (body_len == 0) {
++ return MESSAGE_COMPLETE;
++ } else {
++ ast_str_truncate(*request, message_len - body_len);
++ ast_str_append(overflow, 0, "%s", body);
++ return MESSAGE_FRAGMENT_COMPLETE;
++ }
++ }
++ /* Positive content length. Let's see what sort of
++ * message body we're dealing with.
++ */
++ if (body_len < content_length) {
++ /* We don't have the full message body yet */
++ return MESSAGE_FRAGMENT;
++ } else if (body_len > content_length) {
++ /* We have the full message plus a fragment of a further
++ * message
++ */
++ ast_str_truncate(*request, message_len - (body_len - content_length));
++ ast_str_append(overflow, 0, "%s", body + content_length);
++ return MESSAGE_FRAGMENT_COMPLETE;
++ } else {
++ /* Yay! Full message with no extra content */
++ return MESSAGE_COMPLETE;
++ }
++}
++
++/*!
++ * \brief Read SIP request or response from a TCP connection
++ *
++ * \param req The request structure to be filled in
++ * \param tcptls_session The TCP connection from which to read
++ * \retval -1 Failed to read data
++ * \retval 0 Successfully read data
++ */
++static int sip_tcp_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session,
++ int authenticated, time_t start)
++{
++ enum message_integrity message_integrity = MESSAGE_FRAGMENT;
++
++ while (message_integrity == MESSAGE_FRAGMENT) {
++ if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
++ char readbuf[4097];
++ int timeout;
++ int res;
++ if (!tcptls_session->client && !authenticated) {
++ if ((timeout = sip_check_authtimeout(start)) < 0) {
++ return -1;
++ }
++
++ if (timeout == 0) {
++ ast_debug(2, "SIP TCP server timed out\n");
++ return -1;
++ }
++ } else {
++ timeout = -1;
++ }
++ res = ast_wait_for_input(tcptls_session->fd, timeout);
++ if (res < 0) {
++ ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
++ return -1;
++ } else if (res == 0) {
++ ast_debug(2, "SIP TCP server timed out\n");
++ return -1;
++ }
++
++ res = recv(tcptls_session->fd, readbuf, sizeof(readbuf) - 1, 0);
++ if (res < 0) {
++ ast_debug(2, "SIP TCP server error when receiving data\n");
++ return -1;
++ } else if (res == 0) {
++ ast_debug(2, "SIP TCP server has shut down\n");
++ return -1;
++ }
++ readbuf[res] = '\0';
++ ast_str_append(&req->data, 0, "%s", readbuf);
++ } else {
++ ast_str_append(&req->data, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf));
++ ast_str_reset(tcptls_session->overflow_buf);
++ }
++
++ message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf);
++ }
++
++ return 0;
++}
++
+ /*! \brief SIP TCP thread management function
+ This function reads from the socket, parses the packet into a request
+ */
+ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session)
+ {
+- int res, cl, timeout = -1, authenticated = 0, flags, after_poll = 0, need_poll = 1;
++ int res, timeout = -1, authenticated = 0, flags;
+ time_t start;
+ struct sip_request req = { 0, } , reqcpy = { 0, };
+ struct sip_threadinfo *me = NULL;
+@@ -2605,21 +2956,23 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ timeout = -1;
+ }
+
+- res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
+- if (res < 0) {
+- ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
+- goto cleanup;
+- } else if (res == 0) {
+- /* timeout */
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
+- goto cleanup;
++ if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
++ res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
++ if (res < 0) {
++ ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
++ goto cleanup;
++ } else if (res == 0) {
++ /* timeout */
++ ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
++ goto cleanup;
++ }
+ }
+
+- /* handle the socket event, check for both reads from the socket fd,
+- * and writes from alert_pipe fd */
+- if (fds[0].revents) { /* there is data on the socket to be read */
+- after_poll = 1;
+-
++ /*
++ * handle the socket event, check for both reads from the socket fd or TCP overflow buffer,
++ * and writes from alert_pipe fd.
++ */
++ if (fds[0].revents || (ast_str_strlen(tcptls_session->overflow_buf) > 0)) { /* there is data on the socket to be read */
+ fds[0].revents = 0;
+
+ /* clear request structure */
+@@ -2644,110 +2997,15 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ }
+ req.socket.fd = tcptls_session->fd;
+
+- /* Read in headers one line at a time */
+- while (ast_str_strlen(req.data) < 4 || strncmp(REQ_OFFSET_TO_STR(&req, data->used - 4), "\r\n\r\n", 4)) {
+- if (!tcptls_session->client && !authenticated ) {
+- if ((timeout = sip_check_authtimeout(start)) < 0) {
+- goto cleanup;
+- }
+-
+- if (timeout == 0) {
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
+- goto cleanup;
+- }
+- } else {
+- timeout = -1;
+- }
+-
+- /* special polling behavior is required for TLS
+- * sockets because of the buffering done in the
+- * TLS layer */
+- if (!tcptls_session->ssl || need_poll) {
+- need_poll = 0;
+- after_poll = 1;
+- res = ast_wait_for_input(tcptls_session->fd, timeout);
+- if (res < 0) {
+- ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
+- goto cleanup;
+- } else if (res == 0) {
+- /* timeout */
+- ast_debug(2, "SIP TCP server timed out\n");
+- goto cleanup;
+- }
+- }
+-
+- ast_mutex_lock(&tcptls_session->lock);
+- if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
+- ast_mutex_unlock(&tcptls_session->lock);
+- if (after_poll) {
+- goto cleanup;
+- } else {
+- need_poll = 1;
+- continue;
+- }
+- }
+- ast_mutex_unlock(&tcptls_session->lock);
+- after_poll = 0;
+- if (me->stop) {
+- goto cleanup;
+- }
+- ast_str_append(&req.data, 0, "%s", buf);
+- }
+- copy_request(&reqcpy, &req);
+- parse_request(&reqcpy);
+- /* In order to know how much to read, we need the content-length header */
+- if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
+- while (cl > 0) {
+- size_t bytes_read;
+- if (!tcptls_session->client && !authenticated ) {
+- if ((timeout = sip_check_authtimeout(start)) < 0) {
+- goto cleanup;
+- }
+-
+- if (timeout == 0) {
+- ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
+- goto cleanup;
+- }
+- } else {
+- timeout = -1;
+- }
+-
+- if (!tcptls_session->ssl || need_poll) {
+- need_poll = 0;
+- after_poll = 1;
+- res = ast_wait_for_input(tcptls_session->fd, timeout);
+- if (res < 0) {
+- ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
+- goto cleanup;
+- } else if (res == 0) {
+- /* timeout */
+- ast_debug(2, "SIP TCP server timed out\n");
+- goto cleanup;
+- }
+- }
++ if (tcptls_session->ssl) {
++ res = sip_tls_read(&req, tcptls_session, authenticated, start, me);
++ } else {
++ res = sip_tcp_read(&req, tcptls_session, authenticated, start);
++ }
+
+- ast_mutex_lock(&tcptls_session->lock);
+- if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
+- ast_mutex_unlock(&tcptls_session->lock);
+- if (after_poll) {
+- goto cleanup;
+- } else {
+- need_poll = 1;
+- continue;
+- }
+- }
+- buf[bytes_read] = '\0';
+- ast_mutex_unlock(&tcptls_session->lock);
+- after_poll = 0;
+- if (me->stop) {
+- goto cleanup;
+- }
+- cl -= strlen(buf);
+- ast_str_append(&req.data, 0, "%s", buf);
+- }
++ if (res < 0) {
++ goto cleanup;
+ }
+- /*! \todo XXX If there's no Content-Length or if the content-length and what
+- we receive is not the same - we should generate an error */
+
+ req.socket.tcptls_session = tcptls_session;
+ handle_request_do(&req, &tcptls_session->remote_address);
+@@ -30490,6 +30748,482 @@ AST_TEST_DEFINE(test_sip_peers_get)
+ return AST_TEST_PASS;
+ }
+
++/*!
++ * \brief Imitation TCP reception loop
++ *
++ * This imitates the logic used by SIP's TCP code. Its purpose
++ * is to either
++ * 1) Combine fragments into a single message
++ * 2) Break up combined messages into single messages
++ *
++ * \param fragments The message fragments. This simulates the data received on a TCP socket.
++ * \param num_fragments This indicates the number of fragments to receive
++ * \param overflow This is a place to stash extra data if more than one message is received
++ * in a single fragment
++ * \param[out] messages The parsed messages are placed in this array
++ * \param[out] num_messages The number of messages that were parsed
++ * \param test Used for printing messages
++ * \retval 0 Success
++ * \retval -1 Failure
++ */
++static int mock_tcp_loop(char *fragments[], size_t num_fragments,
++ struct ast_str **overflow, char **messages, int *num_messages, struct ast_test* test)
++{
++ struct ast_str *req_data;
++ int i = 0;
++ int res = 0;
++
++ req_data = ast_str_create(128);
++ ast_str_reset(*overflow);
++
++ while (i < num_fragments || ast_str_strlen(*overflow) > 0) {
++ enum message_integrity message_integrity = MESSAGE_FRAGMENT;
++ ast_str_reset(req_data);
++ while (message_integrity == MESSAGE_FRAGMENT) {
++ if (ast_str_strlen(*overflow) > 0) {
++ ast_str_append(&req_data, 0, "%s", ast_str_buffer(*overflow));
++ ast_str_reset(*overflow);
++ } else {
++ ast_str_append(&req_data, 0, "%s", fragments[i++]);
++ }
++ message_integrity = check_message_integrity(&req_data, overflow);
++ }
++ if (strcmp(ast_str_buffer(req_data), messages[*num_messages])) {
++ ast_test_status_update(test, "Mismatch in SIP messages.\n");
++ ast_test_status_update(test, "Expected message:\n%s", messages[*num_messages]);
++ ast_test_status_update(test, "Parsed message:\n%s", ast_str_buffer(req_data));
++ res = -1;
++ goto end;
++ } else {
++ ast_test_status_update(test, "Successfully read message:\n%s", ast_str_buffer(req_data));
++ }
++ (*num_messages)++;
++ }
++
++end:
++ ast_free(req_data);
++ return res;
++};
++
++AST_TEST_DEFINE(test_tcp_message_fragmentation)
++{
++ /* Normal single message in one fragment */
++ char *normal[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Single message in two fragments.
++ * Fragments combine to make "normal"
++ */
++ char *fragmented[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: ",
++
++ "70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++ /* Single message in two fragments, divided precisely at the body
++ * Fragments combine to make "normal"
++ */
++ char *fragmented_body[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n",
++
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Single message in three fragments
++ * Fragments combine to make "normal"
++ */
++ char *multi_fragment[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n",
++
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4",
++
++ " 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Two messages in a single fragment
++ * Fragments split into "multi_message_divided"
++ */
++ char *multi_message[] = {
++ "SIP/2.0 100 Trying\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n"
++ "SIP/2.0 180 Ringing\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n"
++ };
++ char *multi_message_divided[] = {
++ "SIP/2.0 100 Trying\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n",
++
++ "SIP/2.0 180 Ringing\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n"
++ };
++ /* Two messages with bodies combined into one fragment
++ * Fragments split into "multi_message_body_divided"
++ */
++ char *multi_message_body[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 2 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++ char *multi_message_body_divided[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n",
++
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 2 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Two messages that appear in two fragments. Fragment
++ * boundaries do not align with message boundaries.
++ * Fragments combine to make "multi_message_divided"
++ */
++ char *multi_message_in_fragments[] = {
++ "SIP/2.0 100 Trying\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVI",
++
++ "TE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n"
++ "SIP/2.0 180 Ringing\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: <sip:bob at example.org:5060>\r\n"
++ "Content-Length: 0\r\n"
++ "\r\n"
++ };
++
++ /* Message with compact content-length header
++ * Same as "normal" but with compact content-length header
++ */
++ char *compact[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "l: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Message with faux content-length headers
++ * Same as "normal" but with extra fake content-length headers
++ */
++ char *faux[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "DisContent-Length: 0\r\n"
++ "MalContent-Length: 60\r\n"
++ "Content-Length: 130\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Message with folded Content-Length header
++ * Message is "normal" with Content-Length spread across three lines
++ *
++ * This is the test that requires pedantic=yes in order to pass
++ */
++ char *folded[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "Content-Length: \t\r\n"
++ "\t \r\n"
++ " 130\t \r\n"
++ "\r\n"
++ "v=0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ /* Message with compact Content-length header in message and
++ * full Content-Length header in the body. Ensure that the header
++ * in the message is read and that the one in the body is ignored
++ */
++ char *cl_in_body[] = {
++ "INVITE sip:bob at example.org SIP/2.0\r\n"
++ "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
++ "From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
++ "To: <sip:bob at example.org:5060>\r\n"
++ "Call-ID: 12345\r\n"
++ "CSeq: 1 INVITE\r\n"
++ "Contact: sip:127.0.0.1:5061\r\n"
++ "Max-Forwards: 70\r\n"
++ "Content-Type: application/sdp\r\n"
++ "l: 149\r\n"
++ "\r\n"
++ "v=0\r\n"
++ "Content-Length: 0\r\n"
++ "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
++ "s=-\r\n"
++ "c=IN IP4 127.0.0.1\r\n"
++ "t=0 0\r\n"
++ "m=audio 10000 RTP/AVP 0\r\n"
++ "a=rtpmap:0 PCMU/8000\r\n"
++ };
++
++ struct ast_str *overflow;
++ struct {
++ char **fragments;
++ char **expected;
++ int num_expected;
++ const char *description;
++ } tests[] = {
++ { normal, normal, 1, "normal" },
++ { fragmented, normal, 1, "fragmented" },
++ { fragmented_body, normal, 1, "fragmented_body" },
++ { multi_fragment, normal, 1, "multi_fragment" },
++ { multi_message, multi_message_divided, 2, "multi_message" },
++ { multi_message_body, multi_message_body_divided, 2, "multi_message_body" },
++ { multi_message_in_fragments, multi_message_divided, 2, "multi_message_in_fragments" },
++ { compact, compact, 1, "compact" },
++ { faux, faux, 1, "faux" },
++ { folded, folded, 1, "folded" },
++ { cl_in_body, cl_in_body, 1, "cl_in_body" },
++ };
++ int i;
++ enum ast_test_result_state res = AST_TEST_PASS;
++
++ switch (cmd) {
++ case TEST_INIT:
++ info->name = "sip_tcp_message_fragmentation";
++ info->category = "/main/sip/transport";
++ info->summary = "SIP TCP message fragmentation test";
++ info->description =
++ "Tests reception of different TCP messages that have been fragmented or"
++ "run together. This test mimicks the code that TCP reception uses.";
++ return AST_TEST_NOT_RUN;
++ case TEST_EXECUTE:
++ break;
++ }
++ if (!sip_cfg.pedanticsipchecking) {
++ ast_log(LOG_WARNING, "Not running test. Pedantic SIP checking is not enabled, so it is guaranteed to fail\n");
++ return AST_TEST_NOT_RUN;
++ }
++
++ overflow = ast_str_create(128);
++ if (!overflow) {
++ return AST_TEST_FAIL;
++ }
++ for (i = 0; i < ARRAY_LEN(tests); ++i) {
++ int num_messages = 0;
++ if (mock_tcp_loop(tests[i].fragments, ARRAY_LEN(tests[i].fragments),
++ &overflow, tests[i].expected, &num_messages, test)) {
++ ast_test_status_update(test, "Failed to parse message '%s'\n", tests[i].description);
++ res = AST_TEST_FAIL;
++ break;
++ }
++ if (num_messages != tests[i].num_expected) {
++ ast_test_status_update(test, "Did not receive the expected number of messages. "
++ "Expected %d but received %d\n", tests[i].num_expected, num_messages);
++ res = AST_TEST_FAIL;
++ break;
++ }
++ }
++ ast_free(overflow);
++ return res;
++}
++
+ #endif
+
+ #define DATA_EXPORT_SIP_PEER(MEMBER) \
+@@ -30712,6 +31446,7 @@ static int load_module(void)
+ #ifdef TEST_FRAMEWORK
+ AST_TEST_REGISTER(test_sip_peers_get);
+ AST_TEST_REGISTER(test_sip_mwi_subscribe_parse);
++ AST_TEST_REGISTER(test_tcp_message_fragmentation);
+ #endif
+
+ /* Register AstData providers */
+@@ -30824,6 +31559,7 @@ static int unload_module(void)
+ #ifdef TEST_FRAMEWORK
+ AST_TEST_UNREGISTER(test_sip_peers_get);
+ AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
++ AST_TEST_UNREGISTER(test_tcp_message_fragmentation);
+ #endif
+ /* Unregister all the AstData providers */
+ ast_data_unregister(NULL);
+diff --git a/include/asterisk/tcptls.h b/include/asterisk/tcptls.h
+index e4894f7..7a23499 100644
+--- a/include/asterisk/tcptls.h
++++ b/include/asterisk/tcptls.h
+@@ -149,6 +149,12 @@ struct ast_tcptls_session_instance {
+ struct ast_tcptls_session_args *parent;
+ /*! XXX Why do we still use this lock when this struct is allocated as an ao2 object which has its own lock? */
+ ast_mutex_t lock;
++ /* Sometimes, when an entity reads TCP data, multiple
++ * logical messages might be read at the same time. In such
++ * a circumstance, there needs to be a place to stash the
++ * extra data.
++ */
++ struct ast_str *overflow_buf;
+ };
+
+ #if defined(HAVE_FUNOPEN)
+diff --git a/main/tcptls.c b/main/tcptls.c
+index 286985d..37a719a 100644
+--- a/main/tcptls.c
++++ b/main/tcptls.c
+@@ -141,6 +141,7 @@ HOOK_T ast_tcptls_server_write(struct ast_tcptls_session_instance *tcptls_sessio
+ static void session_instance_destructor(void *obj)
+ {
+ struct ast_tcptls_session_instance *i = obj;
++ ast_free(i->overflow_buf);
+ ast_mutex_destroy(&i->lock);
+ }
+
+@@ -292,6 +293,7 @@ void *ast_tcptls_server_root(void *data)
+ }
+
+ ast_mutex_init(&tcptls_session->lock);
++ tcptls_session->overflow_buf = ast_str_create(128);
+
+ flags = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+@@ -493,6 +495,7 @@ struct ast_tcptls_session_instance *ast_tcptls_client_create(struct ast_tcptls_s
+ goto error;
+
+ ast_mutex_init(&tcptls_session->lock);
++ tcptls_session->overflow_buf = ast_str_create(128);
+ tcptls_session->client = 1;
+ tcptls_session->fd = desc->accept_fd;
+ tcptls_session->parent = desc;
+--
+1.7.10.4
+
Added: asterisk/trunk/debian/patches/fix-sip-tls-leak
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/patches/fix-sip-tls-leak?rev=10063&op=file
==============================================================================
--- asterisk/trunk/debian/patches/fix-sip-tls-leak (added)
+++ asterisk/trunk/debian/patches/fix-sip-tls-leak Thu Jan 3 21:01:52 2013
@@ -1,0 +1,66 @@
+From: Joshua Colp <jcolp at digium.com>
+Date: Wed, 5 Dec 2012 16:48:01 +0000
+Subject: Fix a SIP request memory leak with TLS connections.
+Bug: https://issues.asterisk.org/jira/browse/ASTERISK-20763
+Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=377257
+
+During the TLS re-work in chan_sip some TLS specific code was moved
+into a separate function. This function operates on a copy of the
+incoming SIP request. This copy was never deinitialized causing a
+memory leak for each request processed.
+
+This function is now given a SIP request structure which it can use
+to copy the incoming request into. This reduces the amount of memory
+allocations done since the internal allocated components are reused
+between packets and also ensures the SIP request structure is
+deinitialized when the TLS connection is torn down.
+
+Reported by: deti
+
+---
+ channels/chan_sip.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/channels/chan_sip.c b/channels/chan_sip.c
+index 7cd117d..0a9468a 100644
+--- a/channels/chan_sip.c
++++ b/channels/chan_sip.c
+@@ -2520,10 +2520,10 @@ static int sip_check_authtimeout(time_t start)
+ * \retval -1 Failed to read data
+ * \retval 0 Succeeded in reading data
+ */
+-static int sip_tls_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start, struct sip_threadinfo *me)
++static int sip_tls_read(struct sip_request *req, struct sip_request *reqcpy, struct ast_tcptls_session_instance *tcptls_session,
++ int authenticated, time_t start, struct sip_threadinfo *me)
+ {
+ int res, content_length, after_poll = 1, need_poll = 1;
+- struct sip_request reqcpy = { 0, };
+ char buf[1024] = "";
+ int timeout = -1;
+
+@@ -2577,10 +2577,10 @@ static int sip_tls_read(struct sip_request *req, struct ast_tcptls_session_insta
+ }
+ ast_str_append(&req->data, 0, "%s", buf);
+ }
+- copy_request(&reqcpy, req);
+- parse_request(&reqcpy);
++ copy_request(reqcpy, req);
++ parse_request(reqcpy);
+ /* In order to know how much to read, we need the content-length header */
+- if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &content_length)) {
++ if (sscanf(get_header(reqcpy, "Content-Length"), "%30d", &content_length)) {
+ while (content_length > 0) {
+ size_t bytes_read;
+ if (!tcptls_session->client && !authenticated) {
+@@ -2994,7 +2994,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
+ req.socket.fd = tcptls_session->fd;
+
+ if (tcptls_session->ssl) {
+- res = sip_tls_read(&req, tcptls_session, authenticated, start, me);
++ res = sip_tls_read(&req, &reqcpy, tcptls_session, authenticated, start, me);
+ } else {
+ res = sip_tcp_read(&req, tcptls_session, authenticated, start);
+ }
+--
+1.7.10.4
+
Modified: asterisk/trunk/debian/patches/series
URL: http://svn.debian.org/wsvn/pkg-voip/asterisk/trunk/debian/patches/series?rev=10063&op=diff
==============================================================================
--- asterisk/trunk/debian/patches/series (original)
+++ asterisk/trunk/debian/patches/series Thu Jan 3 21:01:52 2013
@@ -28,3 +28,8 @@
AST-2012-012
AST-2012-013
+# Needed for AST-2012-014:
+six-sip-tcp-no-FILE
+fix-sip-tls-leak
+AST-2012-014
+AST-2012-015
More information about the Pkg-voip-commits
mailing list