ARI: Add ability to raise arbitrary User Events
[asterisk/asterisk.git] / main / pbx.c
index 006549c..faddc47 100644 (file)
@@ -50,7 +50,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/file.h"
 #include "asterisk/callerid.h"
 #include "asterisk/cdr.h"
-#include "asterisk/cel.h"
 #include "asterisk/config.h"
 #include "asterisk/term.h"
 #include "asterisk/time.h"
@@ -65,13 +64,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/app.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/presencestate.h"
-#include "asterisk/event.h"
 #include "asterisk/hashtab.h"
 #include "asterisk/module.h"
 #include "asterisk/indications.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/xmldoc.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/dial.h"
 
 /*!
  * \note I M P O R T A N T :
@@ -104,10 +104,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <para>Asterisk will wait this number of milliseconds before returning to
                                the dialplan after answering the call.</para>
                        </parameter>
-                       <parameter name="nocdr">
-                               <para>Asterisk will send an answer signal to the calling phone, but will not
-                               set the disposition or answer time in the CDR for this call.</para>
-                       </parameter>
                </syntax>
                <description>
                        <para>If the call has not been answered, this application will
@@ -235,7 +231,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </description>
                <see-also>
                        <ref type="application">Exec</ref>
+                       <ref type="application">ExecIf</ref>
                        <ref type="application">TryExec</ref>
+                       <ref type="application">GotoIfTime</ref>
                </see-also>
        </application>
        <application name="Goto" language="en_US">
@@ -279,10 +277,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="condition" required="true" />
                        <parameter name="destination" required="true" argsep=":">
                                <argument name="labeliftrue">
-                                       <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.</para>
+                                       <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
                                </argument>
                                <argument name="labeliffalse">
-                                       <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.</para>
+                                       <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
                                </argument>
                        </parameter>
                </syntax>
@@ -321,8 +321,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <argument name="timezone" required="false" />
                        </parameter>
                        <parameter name="destination" required="true" argsep=":">
-                               <argument name="labeliftrue" />
-                               <argument name="labeliffalse" />
+                               <argument name="labeliftrue">
+                                       <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+                               </argument>
+                               <argument name="labeliffalse">
+                                       <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+                               </argument>
                        </parameter>
                </syntax>
                <description>
@@ -338,6 +344,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </description>
                <see-also>
                        <ref type="application">GotoIf</ref>
+                       <ref type="application">Goto</ref>
                        <ref type="function">IFTIME</ref>
                        <ref type="function">TESTTIME</ref>
                </see-also>
@@ -457,36 +464,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <ref type="function">Exception</ref>
                </see-also>
        </application>
-       <application name="ResetCDR" language="en_US">
-               <synopsis>
-                       Resets the Call Data Record.
-               </synopsis>
-               <syntax>
-                       <parameter name="options">
-                               <optionlist>
-                                       <option name="w">
-                                               <para>Store the current CDR record before resetting it.</para>
-                                       </option>
-                                       <option name="a">
-                                               <para>Store any stacked records.</para>
-                                       </option>
-                                       <option name="v">
-                                               <para>Save CDR variables.</para>
-                                       </option>
-                                       <option name="e">
-                                               <para>Enable CDR only (negate effects of NoCDR).</para>
-                                       </option>
-                               </optionlist>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>This application causes the Call Data Record to be reset.</para>
-               </description>
-               <see-also>
-                       <ref type="application">ForkCDR</ref>
-                       <ref type="application">NoCDR</ref>
-               </see-also>
-       </application>
        <application name="Ringing" language="en_US">
                <synopsis>
                        Indicate ringing tone.
@@ -510,13 +487,58 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="string" required="true" />
                </syntax>
                <description>
+                       <para>This application will play the sounds that correspond to the letters
+                       of the given <replaceable>string</replaceable>. If the channel variable
+                       <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive),
+                       then this application will react to DTMF in the same way as
+                       <literal>Background</literal>.</para>
+               </description>
+               <see-also>
+                       <ref type="application">SayDigits</ref>
+                       <ref type="application">SayNumber</ref>
+                       <ref type="application">SayPhonetic</ref>
+                       <ref type="function">CHANNEL</ref>
+               </see-also>
+       </application>
+       <application name="SayAlphaCase" language="en_US">
+               <synopsis>
+                       Say Alpha.
+               </synopsis>
+               <syntax>
+                       <parameter name="casetype" required="true" >
+                               <enumlist>
+                                       <enum name="a">
+                                               <para>Case sensitive (all) pronunciation.
+                                               (Ex: SayAlphaCase(a,aBc); - lowercase a uppercase b lowercase c).</para>
+                                       </enum>
+                                       <enum name="l">
+                                               <para>Case sensitive (lower) pronunciation.
+                                               (Ex: SayAlphaCase(l,aBc); - lowercase a b lowercase c).</para>
+                                       </enum>
+                                       <enum name="n">
+                                               <para>Case insensitive pronunciation. Equivalent to SayAlpha.
+                                               (Ex: SayAlphaCase(n,aBc) - a b c).</para>
+                                       </enum>
+                                       <enum name="u">
+                                               <para>Case sensitive (upper) pronunciation.
+                                               (Ex: SayAlphaCase(u,aBc); - a uppercase b c).</para>
+                                       </enum>
+                               </enumlist>
+                       </parameter>
+                       <parameter name="string" required="true" />
+               </syntax>
+               <description>
                        <para>This application will play the sounds that correspond to the letters of the
-                       given <replaceable>string</replaceable>.</para>
+                       given <replaceable>string</replaceable>.  Optionally, a <replaceable>casetype</replaceable> may be
+                       specified.  This will be used for case-insensitive or case-sensitive pronunciations. If the channel
+                       variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+                       application will react to DTMF in the same way as <literal>Background</literal>.</para>
                </description>
                <see-also>
                        <ref type="application">SayDigits</ref>
                        <ref type="application">SayNumber</ref>
                        <ref type="application">SayPhonetic</ref>
+                       <ref type="application">SayAlpha</ref>
                        <ref type="function">CHANNEL</ref>
                </see-also>
        </application>
@@ -529,7 +551,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </syntax>
                <description>
                        <para>This application will play the sounds that correspond to the digits of
-                       the given number. This will use the language that is currently set for the channel.</para>
+                       the given number. This will use the language that is currently set for the channel.
+                       If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true'
+                       (case insensitive), then this application will react to DTMF in the same way as
+                       <literal>Background</literal>.</para>
                </description>
                <see-also>
                        <ref type="application">SayAlpha</ref>
@@ -547,9 +572,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="gender" />
                </syntax>
                <description>
-                       <para>This application will play the sounds that correspond to the given <replaceable>digits</replaceable>.
-                       Optionally, a <replaceable>gender</replaceable> may be specified. This will use the language that is currently
-                       set for the channel. See the CHANNEL() function for more information on setting the language for the channel.</para>
+                       <para>This application will play the sounds that correspond to the given
+                       <replaceable>digits</replaceable>. Optionally, a <replaceable>gender</replaceable> may be
+                       specified. This will use the language that is currently set for the channel. See the CHANNEL()
+                       function for more information on setting the language for the channel. If the channel variable
+                       <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+                       application will react to DTMF in the same way as <literal>Background</literal>.</para>
                </description>
                <see-also>
                        <ref type="application">SayAlpha</ref>
@@ -567,7 +595,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </syntax>
                <description>
                        <para>This application will play the sounds from the phonetic alphabet that correspond to the
-                       letters in the given <replaceable>string</replaceable>.</para>
+                       letters in the given <replaceable>string</replaceable>. If the channel variable
+                       <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+                       application will react to DTMF in the same way as <literal>Background</literal>.</para>
                </description>
                <see-also>
                        <ref type="application">SayAlpha</ref>
@@ -644,9 +674,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </syntax>
                <description>
                        <para>This application will set the channel's AMA Flags for billing purposes.</para>
+                       <warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning>
                </description>
                <see-also>
                        <ref type="function">CDR</ref>
+                       <ref type="function">CHANNEL</ref>
                </see-also>
        </application>
        <application name="Wait" language="en_US">
@@ -807,10 +839,19 @@ AST_APP_OPTIONS(waitexten_opts, {
 struct ast_context;
 struct ast_app;
 
-static struct ast_taskprocessor *extension_state_tps;
-
 AST_THREADSTORAGE(switch_data);
 AST_THREADSTORAGE(extensionstate_buf);
+/*!
+ * \brief A thread local indicating whether the current thread can run
+ * 'dangerous' dialplan functions.
+ */
+AST_THREADSTORAGE(thread_inhibit_escalations_tl);
+
+/*!
+ * \brief Set to true (non-zero) to globally allow all dangerous dialplan
+ * functions to run.
+ */
+static int live_dangerously;
 
 /*!
    \brief ast_exten: An extension
@@ -928,6 +969,8 @@ struct ast_state_cb {
        int id;
        /*! Arbitrary data passed for callbacks. */
        void *data;
+       /*! Flag if this callback is an extended callback containing detailed device status */
+       int extended;
        /*! Callback when state changes. */
        ast_state_cb_type change_cb;
        /*! Callback when destroyed so any resources given by the registerer can be freed. */
@@ -1109,18 +1152,6 @@ static const struct cfextension_states {
        { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD,  "InUse&Hold" }
 };
 
-struct presencechange {
-       char *provider;
-       int state;
-       char *subtype;
-       char *message;
-};
-
-struct statechange {
-       AST_LIST_ENTRY(statechange) entry;
-       char dev[0];
-};
-
 struct pbx_exception {
        AST_DECLARE_STRING_FIELDS(
                AST_STRING_FIELD(context);      /*!< Context associated with this exception */
@@ -1138,7 +1169,6 @@ static int pbx_builtin_background(struct ast_channel *, const char *);
 static int pbx_builtin_wait(struct ast_channel *, const char *);
 static int pbx_builtin_waitexten(struct ast_channel *, const char *);
 static int pbx_builtin_incomplete(struct ast_channel *, const char *);
-static int pbx_builtin_resetcdr(struct ast_channel *, const char *);
 static int pbx_builtin_setamaflags(struct ast_channel *, const char *);
 static int pbx_builtin_ringing(struct ast_channel *, const char *);
 static int pbx_builtin_proceeding(struct ast_channel *, const char *);
@@ -1152,6 +1182,7 @@ static int pbx_builtin_execiftime(struct ast_channel *, const char *);
 static int pbx_builtin_saynumber(struct ast_channel *, const char *);
 static int pbx_builtin_saydigits(struct ast_channel *, const char *);
 static int pbx_builtin_saycharacters(struct ast_channel *, const char *);
+static int pbx_builtin_saycharacters_case(struct ast_channel *, const char *);
 static int pbx_builtin_sayphonetic(struct ast_channel *, const char *);
 static int matchcid(const char *cidpattern, const char *callerid);
 #ifdef NEED_DEBUG
@@ -1184,6 +1215,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
        const char *registrar, int lock_context);
 static struct ast_context *find_context_locked(const char *context);
 static struct ast_context *find_context(const char *context);
+static void get_device_state_causing_channels(struct ao2_container *c);
 
 /*!
  * \internal
@@ -1225,13 +1257,22 @@ static int hashtab_compare_extens(const void *ah_a, const void *ah_b)
        }
 
        /* but if they are the same, do the cidmatch values match? */
-       if (ac->matchcid && bc->matchcid) {
-               return strcmp(ac->cidmatch,bc->cidmatch);
-       } else if (!ac->matchcid && !bc->matchcid) {
-               return 0; /* if there's no matchcid on either side, then this is a match */
-       } else {
-               return 1; /* if there's matchcid on one but not the other, they are different */
+       /* not sure which side may be using ast_ext_matchcid_types, so check both */
+       if (ac->matchcid == AST_EXT_MATCHCID_ANY || bc->matchcid == AST_EXT_MATCHCID_ANY) {
+               return 0;
+       }
+       if (ac->matchcid == AST_EXT_MATCHCID_OFF && bc->matchcid == AST_EXT_MATCHCID_OFF) {
+               return 0;
+       }
+       if (ac->matchcid != bc->matchcid) {
+               return 1;
        }
+       /* all other cases already disposed of, match now required on callerid string (cidmatch) */
+       /* although ast_add_extension2_lockopt() enforces non-zero ptr, caller may not have */
+       if (ast_strlen_zero(ac->cidmatch) && ast_strlen_zero(bc->cidmatch)) {
+               return 0;
+       }
+       return strcmp(ac->cidmatch, bc->cidmatch);
 }
 
 static int hashtab_compare_exten_numbers(const void *ah_a, const void *ah_b)
@@ -1259,7 +1300,7 @@ static unsigned int hashtab_hash_extens(const void *obj)
        const struct ast_exten *ac = obj;
        unsigned int x = ast_hashtab_hash_string(ac->exten);
        unsigned int y = 0;
-       if (ac->matchcid)
+       if (ac->matchcid == AST_EXT_MATCHCID_ON)
                y = ast_hashtab_hash_string(ac->cidmatch);
        return x+y;
 }
@@ -1285,14 +1326,19 @@ static int extenpatternmatchnew = 0;
 static char *overrideswitch = NULL;
 
 /*! \brief Subscription for device state change events */
-static struct ast_event_sub *device_state_sub;
+static struct stasis_subscription *device_state_sub;
 /*! \brief Subscription for presence state change events */
-static struct ast_event_sub *presence_state_sub;
+static struct stasis_subscription *presence_state_sub;
 
 AST_MUTEX_DEFINE_STATIC(maxcalllock);
 static int countcalls;
 static int totalcalls;
 
+/*!
+ * \brief Registered functions container.
+ *
+ * It is sorted by function name.
+ */
 static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
 
 /*! \brief Declaration of builtin applications */
@@ -1319,9 +1365,9 @@ static struct pbx_builtin {
        { "Proceeding",     pbx_builtin_proceeding },
        { "Progress",       pbx_builtin_progress },
        { "RaiseException", pbx_builtin_raise_exception },
-       { "ResetCDR",       pbx_builtin_resetcdr },
        { "Ringing",        pbx_builtin_ringing },
        { "SayAlpha",       pbx_builtin_saycharacters },
+       { "SayAlphaCase",   pbx_builtin_saycharacters_case },
        { "SayDigits",      pbx_builtin_saydigits },
        { "SayNumber",      pbx_builtin_saynumber },
        { "SayPhonetic",    pbx_builtin_sayphonetic },
@@ -1348,6 +1394,11 @@ AST_MUTEX_DEFINE_STATIC(conlock);
  */
 AST_MUTEX_DEFINE_STATIC(context_merge_lock);
 
+/*!
+ * \brief Registered applications container.
+ *
+ * It is sorted by application name.
+ */
 static AST_RWLIST_HEAD_STATIC(apps, ast_app);
 
 static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
@@ -1445,7 +1496,7 @@ int check_contexts(char *file, int line )
                                ast_copy_string(dummy_name, e1->exten, sizeof(dummy_name));
                                e2 = ast_hashtab_lookup(c1->root_table, &ex);
                                if (!e2) {
-                                       if (e1->matchcid) {
+                                       if (e1->matchcid == AST_EXT_MATCHCID_ON) {
                                                ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context records the exten %s (CID match: %s) but it is not in its root_table\n", file, line, c2->name, dummy_name, e1->cidmatch );
                                        } else {
                                                ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context records the exten %s but it is not in its root_table\n", file, line, c2->name, dummy_name );
@@ -1553,53 +1604,58 @@ int pbx_exec(struct ast_channel *c,     /*!< Channel */
        const char *saved_c_appl;
        const char *saved_c_data;
 
-       if (ast_channel_cdr(c) && !ast_check_hangup(c))
-               ast_cdr_setapp(ast_channel_cdr(c), app->name, data);
-
        /* save channel values */
        saved_c_appl= ast_channel_appl(c);
        saved_c_data= ast_channel_data(c);
 
+       ast_channel_lock(c);
        ast_channel_appl_set(c, app->name);
        ast_channel_data_set(c, data);
-       ast_cel_report_event(c, AST_CEL_APP_START, NULL, NULL, NULL);
+       ast_channel_publish_snapshot(c);
+       ast_channel_unlock(c);
 
        if (app->module)
                u = __ast_module_user_add(app->module, c);
-       if (strcasecmp(app->name, "system") && !ast_strlen_zero(data) &&
-                       strchr(data, '|') && !strchr(data, ',') && !ast_opt_dont_warn) {
-               ast_log(LOG_WARNING, "The application delimiter is now the comma, not "
-                       "the pipe.  Did you forget to convert your dialplan?  (%s(%s))\n",
-                       app->name, (char *) data);
-       }
        res = app->execute(c, S_OR(data, ""));
        if (app->module && u)
                __ast_module_user_remove(app->module, u);
-       ast_cel_report_event(c, AST_CEL_APP_END, NULL, NULL, NULL);
        /* restore channel values */
        ast_channel_appl_set(c, saved_c_appl);
        ast_channel_data_set(c, saved_c_data);
        return res;
 }
 
+static struct ast_app *pbx_findapp_nolock(const char *name)
+{
+       struct ast_app *cur;
+       int cmp;
+
+       AST_RWLIST_TRAVERSE(&apps, cur, list) {
+               cmp = strcasecmp(name, cur->name);
+               if (cmp > 0) {
+                       continue;
+               }
+               if (!cmp) {
+                       /* Found it. */
+                       break;
+               }
+               /* Not in container. */
+               cur = NULL;
+               break;
+       }
 
-/*! Go no deeper than this through includes (not counting loops) */
-#define AST_PBX_MAX_STACK      128
+       return cur;
+}
 
-/*! \brief Find application handle in linked list
- */
 struct ast_app *pbx_findapp(const char *app)
 {
-       struct ast_app *tmp;
+       struct ast_app *ret;
 
        AST_RWLIST_RDLOCK(&apps);
-       AST_RWLIST_TRAVERSE(&apps, tmp, list) {
-               if (!strcasecmp(tmp->name, app))
-                       break;
-       }
+       ret = pbx_findapp_nolock(app);
        AST_RWLIST_UNLOCK(&apps);
 
-       return tmp;
+       return ret;
 }
 
 static struct ast_switch *pbx_findswitch(const char *sw)
@@ -2411,10 +2467,122 @@ static void destroy_pattern_tree(struct match_char *pattern_tree) /* pattern tre
        ast_free(pattern_tree);
 }
 
+/*!
+ * \internal
+ * \brief Get the length of the exten string.
+ *
+ * \param str Exten to get length.
+ *
+ * \retval strlen of exten.
+ */
+static int ext_cmp_exten_strlen(const char *str)
+{
+       int len;
+
+       len = 0;
+       for (;;) {
+               /* Ignore '-' chars as eye candy fluff. */
+               while (*str == '-') {
+                       ++str;
+               }
+               if (!*str) {
+                       break;
+               }
+               ++str;
+               ++len;
+       }
+       return len;
+}
+
+/*!
+ * \internal
+ * \brief Partial comparison of non-pattern extens.
+ *
+ * \param left Exten to compare.
+ * \param right Exten to compare.  Also matches if this string ends first.
+ *
+ * \retval <0 if left < right
+ * \retval =0 if left == right
+ * \retval >0 if left > right
+ */
+static int ext_cmp_exten_partial(const char *left, const char *right)
+{
+       int cmp;
+
+       for (;;) {
+               /* Ignore '-' chars as eye candy fluff. */
+               while (*left == '-') {
+                       ++left;
+               }
+               while (*right == '-') {
+                       ++right;
+               }
+
+               if (!*right) {
+                       /*
+                        * Right ended first for partial match or both ended at the same
+                        * time for a match.
+                        */
+                       cmp = 0;
+                       break;
+               }
+
+               cmp = *left - *right;
+               if (cmp) {
+                       break;
+               }
+               ++left;
+               ++right;
+       }
+       return cmp;
+}
+
+/*!
+ * \internal
+ * \brief Comparison of non-pattern extens.
+ *
+ * \param left Exten to compare.
+ * \param right Exten to compare.
+ *
+ * \retval <0 if left < right
+ * \retval =0 if left == right
+ * \retval >0 if left > right
+ */
+static int ext_cmp_exten(const char *left, const char *right)
+{
+       int cmp;
+
+       for (;;) {
+               /* Ignore '-' chars as eye candy fluff. */
+               while (*left == '-') {
+                       ++left;
+               }
+               while (*right == '-') {
+                       ++right;
+               }
+
+               cmp = *left - *right;
+               if (cmp) {
+                       break;
+               }
+               if (!*left) {
+                       /*
+                        * Get here only if both strings ended at the same time.  cmp
+                        * would be non-zero if only one string ended.
+                        */
+                       break;
+               }
+               ++left;
+               ++right;
+       }
+       return cmp;
+}
+
 /*
  * Special characters used in patterns:
  *     '_'     underscore is the leading character of a pattern.
  *             In other position it is treated as a regular char.
+ *     '-' The '-' is a separator and ignored.  Why?  So patterns like NXX-XXX-XXXX work.
  *     .       one or more of any character. Only allowed at the end of
  *             a pattern.
  *     !       zero or more of anything. Also impacts the result of CANMATCH
@@ -2443,144 +2611,227 @@ static void destroy_pattern_tree(struct match_char *pattern_tree) /* pattern tre
  */
 
 /*!
- * \brief helper functions to sort extensions and patterns in the desired way,
+ * \brief helper functions to sort extension patterns in the desired way,
  * so that more specific patterns appear first.
  *
- * ext_cmp1 compares individual characters (or sets of), returning
+ * \details
+ * The function compares individual characters (or sets of), returning
  * an int where bits 0-7 are the ASCII code of the first char in the set,
- * while bit 8-15 are the cardinality of the set minus 1.
- * This way more specific patterns (smaller cardinality) appear first.
+ * bits 8-15 are the number of characters in the set, and bits 16-20 are
+ * for special cases.
+ * This way more specific patterns (smaller character sets) appear first.
  * Wildcards have a special value, so that we can directly compare them to
  * sets by subtracting the two values. In particular:
- *  0x000xx            one character, xx
- *  0x0yyxx            yy character set starting with xx
- *  0x10000            '.' (one or more of anything)
- *  0x20000            '!' (zero or more of anything)
- *  0x30000            NUL (end of string)
- *  0x40000            error in set.
+ *  0x001xx     one character, character set starting with xx
+ *  0x0yyxx     yy characters, character set starting with xx
+ *  0x18000     '.' (one or more of anything)
+ *  0x28000     '!' (zero or more of anything)
+ *  0x30000     NUL (end of string)
+ *  0x40000     error in set.
  * The pointer to the string is advanced according to needs.
  * NOTES:
- *     1. the empty set is equivalent to NUL.
- *     2. given that a full set has always 0 as the first element,
- *        we could encode the special cases as 0xffXX where XX
- *        is 1, 2, 3, 4 as used above.
+ *  1. the empty set is ignored.
+ *  2. given that a full set has always 0 as the first element,
+ *     we could encode the special cases as 0xffXX where XX
+ *     is 1, 2, 3, 4 as used above.
  */
-static int ext_cmp1(const char **p, unsigned char *bitwise)
+static int ext_cmp_pattern_pos(const char **p, unsigned char *bitwise)
 {
-       int c, cmin = 0xff, count = 0;
+#define BITS_PER       8       /* Number of bits per unit (byte). */
+       unsigned char c;
+       unsigned char cmin;
+       int count;
        const char *end;
 
-       /* load value and advance pointer */
-       c = *(*p)++;
+       do {
+               /* Get character and advance. (Ignore '-' chars as eye candy fluff.) */
+               do {
+                       c = *(*p)++;
+               } while (c == '-');
 
-       /* always return unless we have a set of chars */
-       switch (toupper(c)) {
-       default:        /* ordinary character */
-               bitwise[c / 8] = 1 << (c % 8);
-               return 0x0100 | (c & 0xff);
+               /* always return unless we have a set of chars */
+               switch (c) {
+               default:
+                       /* ordinary character */
+                       bitwise[c / BITS_PER] = 1 << ((BITS_PER - 1) - (c % BITS_PER));
+                       return 0x0100 | c;
 
-       case 'N':       /* 2..9 */
-               bitwise[6] = 0xfc;
-               bitwise[7] = 0x03;
-               return 0x0800 | '2';
+               case 'n':
+               case 'N':
+                       /* 2..9 */
+                       bitwise[6] = 0x3f;
+                       bitwise[7] = 0xc0;
+                       return 0x0800 | '2';
 
-       case 'X':       /* 0..9 */
-               bitwise[6] = 0xff;
-               bitwise[7] = 0x03;
-               return 0x0A00 | '0';
+               case 'x':
+               case 'X':
+                       /* 0..9 */
+                       bitwise[6] = 0xff;
+                       bitwise[7] = 0xc0;
+                       return 0x0A00 | '0';
 
-       case 'Z':       /* 1..9 */
-               bitwise[6] = 0xfe;
-               bitwise[7] = 0x03;
-               return 0x0900 | '1';
+               case 'z':
+               case 'Z':
+                       /* 1..9 */
+                       bitwise[6] = 0x7f;
+                       bitwise[7] = 0xc0;
+                       return 0x0900 | '1';
+
+               case '.':
+                       /* wildcard */
+                       return 0x18000;
+
+               case '!':
+                       /* earlymatch */
+                       return 0x28000; /* less specific than '.' */
+
+               case '\0':
+                       /* empty string */
+                       *p = NULL;
+                       return 0x30000;
+
+               case '[':
+                       /* char set */
+                       break;
+               }
+               /* locate end of set */
+               end = strchr(*p, ']');
 
-       case '.':       /* wildcard */
-               return 0x18000;
+               if (!end) {
+                       ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");
+                       return 0x40000; /* XXX make this entry go last... */
+               }
 
-       case '!':       /* earlymatch */
-               return 0x28000; /* less specific than NULL */
+               count = 0;
+               cmin = 0xFF;
+               for (; *p < end; ++*p) {
+                       unsigned char c1;       /* first char in range */
+                       unsigned char c2;       /* last char in range */
 
-       case '\0':      /* empty string */
-               *p = NULL;
-               return 0x30000;
+                       c1 = (*p)[0];
+                       if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */
+                               c2 = (*p)[2];
+                               *p += 2;    /* skip a total of 3 chars */
+                       } else {        /* individual character */
+                               c2 = c1;
+                       }
+                       if (c1 < cmin) {
+                               cmin = c1;
+                       }
+                       for (; c1 <= c2; ++c1) {
+                               unsigned char mask = 1 << ((BITS_PER - 1) - (c1 % BITS_PER));
 
-       case '[':       /* pattern */
-               break;
-       }
-       /* locate end of set */
-       end = strchr(*p, ']');
+                               /*
+                                * Note: If two character sets score the same, the one with the
+                                * lowest ASCII values will compare as coming first.  Must fill
+                                * in most significant bits for lower ASCII values to accomplish
+                                * the desired sort order.
+                                */
+                               if (!(bitwise[c1 / BITS_PER] & mask)) {
+                                       /* Add the character to the set. */
+                                       bitwise[c1 / BITS_PER] |= mask;
+                                       count += 0x100;
+                               }
+                       }
+               }
+               ++*p;
+       } while (!count);/* While the char set was empty. */
+       return count | cmin;
+}
 
-       if (end == NULL) {
-               ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");
-               return 0x40000; /* XXX make this entry go last... */
-       }
+/*!
+ * \internal
+ * \brief Comparison of exten patterns.
+ *
+ * \param left Pattern to compare.
+ * \param right Pattern to compare.
+ *
+ * \retval <0 if left < right
+ * \retval =0 if left == right
+ * \retval >0 if left > right
+ */
+static int ext_cmp_pattern(const char *left, const char *right)
+{
+       int cmp;
+       int left_pos;
+       int right_pos;
 
-       for (; *p < end  ; (*p)++) {
-               unsigned char c1, c2;   /* first-last char in range */
-               c1 = (unsigned char)((*p)[0]);
-               if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */
-                       c2 = (unsigned char)((*p)[2]);
-                       *p += 2;    /* skip a total of 3 chars */
-               } else {        /* individual character */
-                       c2 = c1;
+       for (;;) {
+               unsigned char left_bitwise[32] = { 0, };
+               unsigned char right_bitwise[32] = { 0, };
+
+               left_pos = ext_cmp_pattern_pos(&left, left_bitwise);
+               right_pos = ext_cmp_pattern_pos(&right, right_bitwise);
+               cmp = left_pos - right_pos;
+               if (!cmp) {
+                       /*
+                        * Are the character sets different, even though they score the same?
+                        *
+                        * Note: Must swap left and right to get the sense of the
+                        * comparison correct.  Otherwise, we would need to multiply by
+                        * -1 instead.
+                        */
+                       cmp = memcmp(right_bitwise, left_bitwise, ARRAY_LEN(left_bitwise));
                }
-               if (c1 < cmin) {
-                       cmin = c1;
+               if (cmp) {
+                       break;
                }
-               for (; c1 <= c2; c1++) {
-                       unsigned char mask = 1 << (c1 % 8);
-                       /*!\note If two patterns score the same, the one with the lowest
-                        * ascii values will compare as coming first. */
-                       /* Flag the character as included (used) and count it. */
-                       if (!(bitwise[ c1 / 8 ] & mask)) {
-                               bitwise[ c1 / 8 ] |= mask;
-                               count += 0x100;
-                       }
+               if (!left) {
+                       /*
+                        * Get here only if both patterns ended at the same time.  cmp
+                        * would be non-zero if only one pattern ended.
+                        */
+                       break;
                }
        }
-       (*p)++;
-       return count == 0 ? 0x30000 : (count | cmin);
+       return cmp;
 }
 
 /*!
- * \brief the full routine to compare extensions in rules.
+ * \internal
+ * \brief Comparison of dialplan extens for sorting purposes.
+ *
+ * \param left Exten/pattern to compare.
+ * \param right Exten/pattern to compare.
+ *
+ * \retval <0 if left < right
+ * \retval =0 if left == right
+ * \retval >0 if left > right
  */
-static int ext_cmp(const char *a, const char *b)
+static int ext_cmp(const char *left, const char *right)
 {
-       /* make sure non-patterns come first.
-        * If a is not a pattern, it either comes first or
-        * we do a more complex pattern comparison.
-        */
-       int ret = 0;
-
-       if (a[0] != '_')
-               return (b[0] == '_') ? -1 : strcmp(a, b);
-
-       /* Now we know a is a pattern; if b is not, a comes first */
-       if (b[0] != '_')
+       /* Make sure non-pattern extens come first. */
+       if (left[0] != '_') {
+               if (right[0] == '_') {
+                       return -1;
+               }
+               /* Compare two non-pattern extens. */
+               return ext_cmp_exten(left, right);
+       }
+       if (right[0] != '_') {
                return 1;
-
-       /* ok we need full pattern sorting routine.
-        * skip past the underscores */
-       ++a; ++b;
-       do {
-               unsigned char bitwise[2][32] = { { 0, } };
-               ret = ext_cmp1(&a, bitwise[0]) - ext_cmp1(&b, bitwise[1]);
-               if (ret == 0) {
-                       /* Are the classes different, even though they score the same? */
-                       ret = memcmp(bitwise[0], bitwise[1], 32);
-               }
-       } while (!ret && a && b);
-       if (ret == 0) {
-               return 0;
-       } else {
-               return (ret > 0) ? 1 : -1;
        }
+
+       /*
+        * OK, we need full pattern sorting routine.
+        *
+        * Skip past the underscores
+        */
+       return ext_cmp_pattern(left + 1, right + 1);
 }
 
 int ast_extension_cmp(const char *a, const char *b)
 {
-       return ext_cmp(a, b);
+       int cmp;
+
+       cmp = ext_cmp(a, b);
+       if (cmp < 0) {
+               return -1;
+       }
+       if (cmp > 0) {
+               return 1;
+       }
+       return 0;
 }
 
 /*!
@@ -2603,15 +2854,9 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
        ast_log(LOG_NOTICE,"match core: pat: '%s', dat: '%s', mode=%d\n", pattern, data, (int)mode);
 #endif
 
-       if ( (mode == E_MATCH) && (pattern[0] == '_') && (!strcasecmp(pattern,data)) ) { /* note: if this test is left out, then _x. will not match _x. !!! */
-#ifdef NEED_DEBUG_HERE
-               ast_log(LOG_NOTICE,"return (1) - pattern matches pattern\n");
-#endif
-               return 1;
-       }
-
        if (pattern[0] != '_') { /* not a pattern, try exact or partial match */
-               int ld = strlen(data), lp = strlen(pattern);
+               int lp = ext_cmp_exten_strlen(pattern);
+               int ld = ext_cmp_exten_strlen(data);
 
                if (lp < ld) {          /* pattern too short, cannot match */
 #ifdef NEED_DEBUG_HERE
@@ -2622,11 +2867,11 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
                /* depending on the mode, accept full or partial match or both */
                if (mode == E_MATCH) {
 #ifdef NEED_DEBUG_HERE
-                       ast_log(LOG_NOTICE,"return (!strcmp(%s,%s) when mode== E_MATCH)\n", pattern, data);
+                       ast_log(LOG_NOTICE,"return (!ext_cmp_exten(%s,%s) when mode== E_MATCH)\n", pattern, data);
 #endif
-                       return !strcmp(pattern, data); /* 1 on match, 0 on fail */
+                       return !ext_cmp_exten(pattern, data); /* 1 on match, 0 on fail */
                }
-               if (ld == 0 || !strncasecmp(pattern, data, ld)) { /* partial or full match */
+               if (ld == 0 || !ext_cmp_exten_partial(pattern, data)) { /* partial or full match */
 #ifdef NEED_DEBUG_HERE
                        ast_log(LOG_NOTICE,"return (mode(%d) == E_MATCHMORE ? lp(%d) > ld(%d) : 1)\n", mode, lp, ld);
 #endif
@@ -2638,26 +2883,60 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
                        return 0;
                }
        }
-       pattern++; /* skip leading _ */
+       if (mode == E_MATCH && data[0] == '_') {
+               /*
+                * XXX It is bad design that we don't know if we should be
+                * comparing data and pattern as patterns or comparing data if
+                * it conforms to pattern when the function is called.  First,
+                * assume they are both patterns.  If they don't match then try
+                * to see if data conforms to the given pattern.
+                *
+                * note: if this test is left out, then _x. will not match _x. !!!
+                */
+#ifdef NEED_DEBUG_HERE
+               ast_log(LOG_NOTICE, "Comparing as patterns first. pattern:%s data:%s\n", pattern, data);
+#endif
+               if (!ext_cmp_pattern(pattern + 1, data + 1)) {
+#ifdef NEED_DEBUG_HERE
+                       ast_log(LOG_NOTICE,"return (1) - pattern matches pattern\n");
+#endif
+                       return 1;
+               }
+       }
+
+       ++pattern; /* skip leading _ */
        /*
         * XXX below we stop at '/' which is a separator for the CID info. However we should
         * not store '/' in the pattern at all. When we insure it, we can remove the checks.
         */
-       while (*data && *pattern && *pattern != '/') {
+       for (;;) {
                const char *end;
 
-               if (*data == '-') { /* skip '-' in data (just a separator) */
-                       data++;
-                       continue;
+               /* Ignore '-' chars as eye candy fluff. */
+               while (*data == '-') {
+                       ++data;
+               }
+               while (*pattern == '-') {
+                       ++pattern;
+               }
+               if (!*data || !*pattern || *pattern == '/') {
+                       break;
                }
-               switch (toupper(*pattern)) {
+
+               switch (*pattern) {
                case '[':       /* a range */
-                       end = strchr(pattern+1, ']'); /* XXX should deal with escapes ? */
-                       if (end == NULL) {
+                       ++pattern;
+                       end = strchr(pattern, ']'); /* XXX should deal with escapes ? */
+                       if (!end) {
                                ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");
                                return 0;       /* unconditional failure */
                        }
-                       for (pattern++; pattern != end; pattern++) {
+                       if (pattern == end) {
+                               /* Ignore empty character sets. */
+                               ++pattern;
+                               continue;
+                       }
+                       for (; pattern < end; ++pattern) {
                                if (pattern+2 < end && pattern[1] == '-') { /* this is a range */
                                        if (*data >= pattern[0] && *data <= pattern[2])
                                                break;  /* match found */
@@ -2668,34 +2947,37 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
                                } else if (*data == pattern[0])
                                        break;  /* match found */
                        }
-                       if (pattern == end) {
+                       if (pattern >= end) {
 #ifdef NEED_DEBUG_HERE
-                               ast_log(LOG_NOTICE,"return (0) when pattern==end\n");
+                               ast_log(LOG_NOTICE,"return (0) when pattern>=end\n");
 #endif
                                return 0;
                        }
                        pattern = end;  /* skip and continue */
                        break;
+               case 'n':
                case 'N':
                        if (*data < '2' || *data > '9') {
 #ifdef NEED_DEBUG_HERE
-                               ast_log(LOG_NOTICE,"return (0) N is matched\n");
+                               ast_log(LOG_NOTICE,"return (0) N is not matched\n");
 #endif
                                return 0;
                        }
                        break;
+               case 'x':
                case 'X':
                        if (*data < '0' || *data > '9') {
 #ifdef NEED_DEBUG_HERE
-                               ast_log(LOG_NOTICE,"return (0) X is matched\n");
+                               ast_log(LOG_NOTICE,"return (0) X is not matched\n");
 #endif
                                return 0;
                        }
                        break;
+               case 'z':
                case 'Z':
                        if (*data < '1' || *data > '9') {
 #ifdef NEED_DEBUG_HERE
-                               ast_log(LOG_NOTICE,"return (0) Z is matched\n");
+                               ast_log(LOG_NOTICE,"return (0) Z is not matched\n");
 #endif
                                return 0;
                        }
@@ -2710,10 +2992,6 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
                        ast_log(LOG_NOTICE, "return (2) when '!' is matched\n");
 #endif
                        return 2;
-               case ' ':
-               case '-':       /* Ignore these in patterns */
-                       data--; /* compensate the final data++ */
-                       break;
                default:
                        if (*data != *pattern) {
 #ifdef NEED_DEBUG_HERE
@@ -2721,9 +2999,10 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
 #endif
                                return 0;
                        }
+                       break;
                }
-               data++;
-               pattern++;
+               ++data;
+               ++pattern;
        }
        if (*data)                      /* data longer than pattern, no match */ {
 #ifdef NEED_DEBUG_HERE
@@ -2733,7 +3012,7 @@ static int _extension_match_core(const char *pattern, const char *data, enum ext
        }
 
        /*
-        * match so far, but ran off the end of the data.
+        * match so far, but ran off the end of data.
         * Depending on what is next, determine match or not.
         */
        if (*pattern == '\0' || *pattern == '/') {      /* exact match */
@@ -3353,7 +3632,7 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru
        }
        if (s == &not_found) { /* look for more */
                if (!strcmp(var, "EPOCH")) {
-                       ast_str_set(str, maxlen, "%u", (int) time(NULL));
+                       ast_str_set(str, maxlen, "%d", (int) time(NULL));
                        s = ast_str_buffer(*str);
                } else if (!strcmp(var, "SYSTEMNAME")) {
                        s = ast_config_AST_SYSTEM_NAME;
@@ -3390,7 +3669,7 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru
                if (places[i] == &globals)
                        ast_rwlock_rdlock(&globalslock);
                AST_LIST_TRAVERSE(places[i], variables, entries) {
-                       if (!strcasecmp(ast_var_name(variables), var)) {
+                       if (!strcmp(ast_var_name(variables), var)) {
                                s = ast_var_value(variables);
                                break;
                        }
@@ -3426,7 +3705,7 @@ static void exception_store_free(void *data)
        ast_free(exception);
 }
 
-static struct ast_datastore_info exception_store_info = {
+static const struct ast_datastore_info exception_store_info = {
        .type = "EXCEPTION",
        .destroy = exception_store_free,
 };
@@ -3541,43 +3820,67 @@ static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_
        return CLI_SUCCESS;
 }
 
-static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *complete_functions(const char *word, int pos, int state)
 {
-       struct ast_custom_function *acf;
-       /* Maximum number of characters added by terminal coloring is 22 */
-       char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40];
-       char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL;
-       char stxtitle[40], *syntax = NULL, *arguments = NULL;
-       int syntax_size, description_size, synopsis_size, arguments_size, seealso_size;
+       struct ast_custom_function *cur;
        char *ret = NULL;
        int which = 0;
        int wordlen;
+       int cmp;
 
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "core show function";
-               e->usage =
-                       "Usage: core show function <function>\n"
-                       "       Describe a particular dialplan function.\n";
+       if (pos != 3) {
                return NULL;
-       case CLI_GENERATE:
-               wordlen = strlen(a->word);
-               /* case-insensitive for convenience in this 'complete' function */
-               AST_RWLIST_RDLOCK(&acf_root);
-               AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) {
-                       if (!strncasecmp(a->word, acf->name, wordlen) && ++which > a->n) {
-                               ret = ast_strdup(acf->name);
-                               break;
-                       }
-               }
-               AST_RWLIST_UNLOCK(&acf_root);
-
-               return ret;
        }
 
-       if (a->argc < 4) {
-               return CLI_SHOWUSAGE;
-       }
+       wordlen = strlen(word);
+       AST_RWLIST_RDLOCK(&acf_root);
+       AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
+               /*
+                * Do a case-insensitive search for convenience in this
+                * 'complete' function.
+                *
+                * We must search the entire container because the functions are
+                * sorted and normally found case sensitively.
+                */
+               cmp = strncasecmp(word, cur->name, wordlen);
+               if (!cmp) {
+                       /* Found match. */
+                       if (++which <= state) {
+                               /* Not enough matches. */
+                               continue;
+                       }
+                       ret = ast_strdup(cur->name);
+                       break;
+               }
+       }
+       AST_RWLIST_UNLOCK(&acf_root);
+
+       return ret;
+}
+
+static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_custom_function *acf;
+       /* Maximum number of characters added by terminal coloring is 22 */
+       char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40];
+       char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL;
+       char stxtitle[40], *syntax = NULL, *arguments = NULL;
+       int syntax_size, description_size, synopsis_size, arguments_size, seealso_size;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "core show function";
+               e->usage =
+                       "Usage: core show function <function>\n"
+                       "       Describe a particular dialplan function.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return complete_functions(a->word, a->pos, a->n);
+       }
+
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
 
        if (!(acf = ast_custom_function_find(a->argv[3]))) {
                ast_cli(a->fd, "No function by that name registered.\n");
@@ -3648,15 +3951,34 @@ static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_c
        return CLI_SUCCESS;
 }
 
-struct ast_custom_function *ast_custom_function_find(const char *name)
+static struct ast_custom_function *ast_custom_function_find_nolock(const char *name)
 {
-       struct ast_custom_function *acf = NULL;
+       struct ast_custom_function *cur;
+       int cmp;
 
-       AST_RWLIST_RDLOCK(&acf_root);
-       AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) {
-               if (!strcmp(name, acf->name))
+       AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
+               cmp = strcmp(name, cur->name);
+               if (cmp > 0) {
+                       continue;
+               }
+               if (!cmp) {
+                       /* Found it. */
                        break;
+               }
+               /* Not in container. */
+               cur = NULL;
+               break;
        }
+
+       return cur;
+}
+
+struct ast_custom_function *ast_custom_function_find(const char *name)
+{
+       struct ast_custom_function *acf;
+
+       AST_RWLIST_RDLOCK(&acf_root);
+       acf = ast_custom_function_find_nolock(name);
        AST_RWLIST_UNLOCK(&acf_root);
 
        return acf;
@@ -3684,6 +4006,28 @@ int ast_custom_function_unregister(struct ast_custom_function *acf)
        return cur ? 0 : -1;
 }
 
+/*!
+ * \brief Returns true if given custom function escalates privileges on read.
+ *
+ * \param acf Custom function to query.
+ * \return True (non-zero) if reads escalate privileges.
+ * \return False (zero) if reads just read.
+ */
+static int read_escalates(const struct ast_custom_function *acf) {
+       return acf->read_escalates;
+}
+
+/*!
+ * \brief Returns true if given custom function escalates privileges on write.
+ *
+ * \param acf Custom function to query.
+ * \return True (non-zero) if writes escalate privileges.
+ * \return False (zero) if writes just write.
+ */
+static int write_escalates(const struct ast_custom_function *acf) {
+       return acf->write_escalates;
+}
+
 /*! \internal
  *  \brief Retrieve the XML documentation of a specified ast_custom_function,
  *         and populate ast_custom_function string fields.
@@ -3740,7 +4084,6 @@ static int acf_retrieve_docs(struct ast_custom_function *acf)
 int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod)
 {
        struct ast_custom_function *cur;
-       char tmps[80];
 
        if (!acf) {
                return -1;
@@ -3757,30 +4100,55 @@ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_m
 
        AST_RWLIST_WRLOCK(&acf_root);
 
-       AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
-               if (!strcmp(acf->name, cur->name)) {
-                       ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
-                       AST_RWLIST_UNLOCK(&acf_root);
-                       return -1;
-               }
+       cur = ast_custom_function_find_nolock(acf->name);
+       if (cur) {
+               ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
+               AST_RWLIST_UNLOCK(&acf_root);
+               return -1;
        }
 
        /* Store in alphabetical order */
        AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) {
-               if (strcasecmp(acf->name, cur->name) < 0) {
+               if (strcmp(acf->name, cur->name) < 0) {
                        AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist);
                        break;
                }
        }
        AST_RWLIST_TRAVERSE_SAFE_END;
-
        if (!cur) {
                AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist);
        }
 
        AST_RWLIST_UNLOCK(&acf_root);
 
-       ast_verb(2, "Registered custom function '%s'\n", term_color(tmps, acf->name, COLOR_BRCYAN, 0, sizeof(tmps)));
+       ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name));
+
+       return 0;
+}
+
+int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
+{
+       int res;
+
+       res = __ast_custom_function_register(acf, mod);
+       if (res != 0) {
+               return -1;
+       }
+
+       switch (escalation) {
+       case AST_CFE_NONE:
+               break;
+       case AST_CFE_READ:
+               acf->read_escalates = 1;
+               break;
+       case AST_CFE_WRITE:
+               acf->write_escalates = 1;
+               break;
+       case AST_CFE_BOTH:
+               acf->read_escalates = 1;
+               acf->write_escalates = 1;
+               break;
+       }
 
        return 0;
 }
@@ -3806,6 +4174,124 @@ static char *func_args(char *function)
        return args;
 }
 
+void pbx_live_dangerously(int new_live_dangerously)
+{
+       if (new_live_dangerously && !live_dangerously) {
+               ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
+                       "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
+       }
+
+       if (!new_live_dangerously && live_dangerously) {
+               ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
+       }
+       live_dangerously = new_live_dangerously;
+}
+
+int ast_thread_inhibit_escalations(void)
+{
+       int *thread_inhibit_escalations;
+
+       thread_inhibit_escalations = ast_threadstorage_get(
+               &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
+
+       if (thread_inhibit_escalations == NULL) {
+               ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
+               return -1;
+       }
+
+       *thread_inhibit_escalations = 1;
+       return 0;
+}
+
+/*!
+ * \brief Indicates whether the current thread inhibits the execution of
+ * dangerous functions.
+ *
+ * \return True (non-zero) if dangerous function execution is inhibited.
+ * \return False (zero) if dangerous function execution is allowed.
+ */
+static int thread_inhibits_escalations(void)
+{
+       int *thread_inhibit_escalations;
+
+       thread_inhibit_escalations = ast_threadstorage_get(
+               &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
+
+       if (thread_inhibit_escalations == NULL) {
+               ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
+               /* On error, assume that we are inhibiting */
+               return 1;
+       }
+
+       return *thread_inhibit_escalations;
+}
+
+/*!
+ * \brief Determines whether execution of a custom function's read function
+ * is allowed.
+ *
+ * \param acfptr Custom function to check
+ * \return True (non-zero) if reading is allowed.
+ * \return False (zero) if reading is not allowed.
+ */
+static int is_read_allowed(struct ast_custom_function *acfptr)
+{
+       if (!acfptr) {
+               return 1;
+       }
+
+       if (!read_escalates(acfptr)) {
+               return 1;
+       }
+
+       if (!thread_inhibits_escalations()) {
+               return 1;
+       }
+
+       if (live_dangerously) {
+               /* Global setting overrides the thread's preference */
+               ast_debug(2, "Reading %s from a dangerous context\n",
+                       acfptr->name);
+               return 1;
+       }
+
+       /* We have no reason to allow this function to execute */
+       return 0;
+}
+
+/*!
+ * \brief Determines whether execution of a custom function's write function
+ * is allowed.
+ *
+ * \param acfptr Custom function to check
+ * \return True (non-zero) if writing is allowed.
+ * \return False (zero) if writing is not allowed.
+ */
+static int is_write_allowed(struct ast_custom_function *acfptr)
+{
+       if (!acfptr) {
+               return 1;
+       }
+
+       if (!write_escalates(acfptr)) {
+               return 1;
+       }
+
+       if (!thread_inhibits_escalations()) {
+               return 1;
+       }
+
+       if (live_dangerously) {
+               /* Global setting overrides the thread's preference */
+               ast_debug(2, "Writing %s from a dangerous context\n",
+                       acfptr->name);
+               return 1;
+       }
+
+       /* We have no reason to allow this function to execute */
+       return 0;
+}
+
 int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
 {
        char *copy = ast_strdupa(function);
@@ -3818,6 +4304,8 @@ int ast_func_read(struct ast_channel *chan, const char *function, char *workspac
                ast_log(LOG_ERROR, "Function %s not registered\n", copy);
        } else if (!acfptr->read && !acfptr->read2) {
                ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
+       } else if (!is_read_allowed(acfptr)) {
+               ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
        } else if (acfptr->read) {
                if (acfptr->mod) {
                        u = __ast_module_user_add(acfptr->mod, chan);
@@ -3855,6 +4343,8 @@ int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_st
                ast_log(LOG_ERROR, "Function %s not registered\n", copy);
        } else if (!acfptr->read && !acfptr->read2) {
                ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
+       } else if (!is_read_allowed(acfptr)) {
+               ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
        } else {
                if (acfptr->mod) {
                        u = __ast_module_user_add(acfptr->mod, chan);
@@ -3894,11 +4384,13 @@ int ast_func_write(struct ast_channel *chan, const char *function, const char *v
        char *args = func_args(copy);
        struct ast_custom_function *acfptr = ast_custom_function_find(copy);
 
-       if (acfptr == NULL)
+       if (acfptr == NULL) {
                ast_log(LOG_ERROR, "Function %s not registered\n", copy);
-       else if (!acfptr->write)
+       } else if (!acfptr->write) {
                ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
-       else {
+       } else if (!is_write_allowed(acfptr)) {
+               ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
+       } else {
                int res;
                struct ast_module_user *u = NULL;
                if (acfptr->mod)
@@ -3992,12 +4484,12 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
+                               size_t my_used;
+
                                if (!substr2) {
                                        substr2 = ast_str_create(16);
                                }
-
-                               ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &used);
+                               ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
                                finalvars = ast_str_buffer(substr2);
                        } else {
                                finalvars = ast_str_buffer(substr1);
@@ -4022,7 +4514,7 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
                                                ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
                                        }
                                }
-                               ast_debug(2, "Function result is '%s'\n", cp4 ? cp4 : "(null)");
+                               ast_debug(2, "Function %s result is '%s'\n", finalvars, cp4 ? cp4 : "(null)");
                        } else {
                                /* Retrieve variable value */
                                ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
@@ -4068,12 +4560,12 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
+                               size_t my_used;
+
                                if (!substr2) {
                                        substr2 = ast_str_create(16);
                                }
-
-                               ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &used);
+                               ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
                                finalvars = ast_str_buffer(substr2);
                        } else {
                                finalvars = ast_str_buffer(substr1);
@@ -4180,25 +4672,26 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
                        whereweare += (len + 3);
 
                        if (!var)
-                               var = alloca(VAR_BUF_SIZE);
+                               var = ast_alloca(VAR_BUF_SIZE);
 
                        /* Store variable name (and truncate) */
                        ast_copy_string(var, vars, len + 1);
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
-                               if (!ltmp)
-                                       ltmp = alloca(VAR_BUF_SIZE);
+                               size_t my_used;
 
-                               pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &used);
+                               if (!ltmp) {
+                                       ltmp = ast_alloca(VAR_BUF_SIZE);
+                               }
+                               pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
                                vars = ltmp;
                        } else {
                                vars = var;
                        }
 
                        if (!workspace)
-                               workspace = alloca(VAR_BUF_SIZE);
+                               workspace = ast_alloca(VAR_BUF_SIZE);
 
                        workspace[0] = '\0';
 
@@ -4221,7 +4714,7 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
                                                ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
                                        }
                                }
-                               ast_debug(2, "Function result is '%s'\n", cp4 ? cp4 : "(null)");
+                               ast_debug(2, "Function %s result is '%s'\n", vars, cp4 ? cp4 : "(null)");
                        } else {
                                /* Retrieve variable value */
                                pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp);
@@ -4269,18 +4762,19 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
                        whereweare += (len + 3);
 
                        if (!var)
-                               var = alloca(VAR_BUF_SIZE);
+                               var = ast_alloca(VAR_BUF_SIZE);
 
                        /* Store variable name (and truncate) */
                        ast_copy_string(var, vars, len + 1);
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
-                               if (!ltmp)
-                                       ltmp = alloca(VAR_BUF_SIZE);
+                               size_t my_used;
 
-                               pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &used);
+                               if (!ltmp) {
+                                       ltmp = ast_alloca(VAR_BUF_SIZE);
+                               }
+                               pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
                                vars = ltmp;
                        } else {
                                vars = var;
@@ -4311,25 +4805,6 @@ void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1,
        pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count, &used);
 }
 
-static void pbx_substitute_variables(char *passdata, int datalen, struct ast_channel *c, struct ast_exten *e)
-{
-       const char *tmp;
-
-       /* Nothing more to do */
-       if (!e->data) {
-               *passdata = '\0';
-               return;
-       }
-
-       /* No variables or expressions in e->data, so why scan it? */
-       if ((!(tmp = strchr(e->data, '$'))) || (!strstr(tmp, "${") && !strstr(tmp, "$["))) {
-               ast_copy_string(passdata, e->data, datalen);
-               return;
-       }
-
-       pbx_substitute_variables_helper(c, e->data, passdata, datalen - 1);
-}
-
 /*!
  * \brief The return value depends on the action:
  *
@@ -4354,10 +4829,10 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
 {
        struct ast_exten *e;
        struct ast_app *app;
+       char *substitute = NULL;
        int res;
        struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
        char passdata[EXT_DATA_SIZE];
-
        int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
 
        ast_rdlock_contexts();
@@ -4379,6 +4854,18 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                        if (!e->cached_app)
                                e->cached_app = pbx_findapp(e->app);
                        app = e->cached_app;
+                       if (ast_strlen_zero(e->data)) {
+                               *passdata = '\0';
+                       } else {
+                               const char *tmp;
+                               if ((!(tmp = strchr(e->data, '$'))) || (!strstr(tmp, "${") && !strstr(tmp, "$["))) {
+                                       /* no variables to substitute, copy on through */
+                                       ast_copy_string(passdata, e->data, sizeof(passdata));
+                               } else {
+                                       /* save e->data on stack for later processing after lock released */
+                                       substitute = ast_strdupa(e->data);
+                               }
+                       }
                        ast_unlock_contexts();
                        if (!app) {
                                ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
@@ -4389,29 +4876,18 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                        if (ast_channel_exten(c) != exten)
                                ast_channel_exten_set(c, exten);
                        ast_channel_priority_set(c, priority);
-                       pbx_substitute_variables(passdata, sizeof(passdata), c, e);
-#ifdef CHANNEL_TRACE
-                       ast_channel_trace_update(c);
-#endif
+                       if (substitute) {
+                               pbx_substitute_variables_helper(c, substitute, passdata, sizeof(passdata)-1);
+                       }
                        ast_debug(1, "Launching '%s'\n", app->name);
-                       {
-                               char tmp[80], tmp2[80], tmp3[EXT_DATA_SIZE];
-                               ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\") %s\n",
+                       if (VERBOSITY_ATLEAST(3)) {
+                               ast_verb(3, "Executing [%s@%s:%d] " COLORIZE_FMT "(\"" COLORIZE_FMT "\", \"" COLORIZE_FMT "\") %s\n",
                                        exten, context, priority,
-                                       term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)),
-                                       term_color(tmp2, ast_channel_name(c), COLOR_BRMAGENTA, 0, sizeof(tmp2)),
-                                       term_color(tmp3, passdata, COLOR_BRMAGENTA, 0, sizeof(tmp3)),
+                                       COLORIZE(COLOR_BRCYAN, 0, app->name),
+                                       COLORIZE(COLOR_BRMAGENTA, 0, ast_channel_name(c)),
+                                       COLORIZE(COLOR_BRMAGENTA, 0, passdata),
                                        "in new stack");
                        }
-                       manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
-                                       "Channel: %s\r\n"
-                                       "Context: %s\r\n"
-                                       "Extension: %s\r\n"
-                                       "Priority: %d\r\n"
-                                       "Application: %s\r\n"
-                                       "AppData: %s\r\n"
-                                       "Uniqueid: %s\r\n",
-                                       ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), app->name, passdata, ast_channel_uniqueid(c));
                        return pbx_exec(c, app, passdata);      /* 0 on success, -1 on failure */
                }
        } else if (q.swo) {     /* not found here, but in another switch */
@@ -4534,7 +5010,19 @@ static char *parse_hint_device(struct ast_str *hint_args)
        return ast_str_buffer(hint_args);
 }
 
-static int ast_extension_state3(struct ast_str *hint_app)
+static void device_state_info_dt(void *obj)
+{
+       struct ast_device_state_info *info = obj;
+
+       ao2_cleanup(info->causing_channel);
+}
+
+static struct ao2_container *alloc_device_state_info(void)
+{
+       return ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL);
+}
+
+static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info)
 {
        char *cur;
        char *rest;
@@ -4545,14 +5033,28 @@ static int ast_extension_state3(struct ast_str *hint_app)
 
        ast_devstate_aggregate_init(&agg);
        while ((cur = strsep(&rest, "&"))) {
-               ast_devstate_aggregate_add(&agg, ast_device_state(cur));
+               enum ast_device_state state = ast_device_state(cur);
+
+               ast_devstate_aggregate_add(&agg, state);
+               if (device_state_info) {
+                       struct ast_device_state_info *obj;
+
+                       obj = ao2_alloc_options(sizeof(*obj) + strlen(cur), device_state_info_dt, AO2_ALLOC_OPT_LOCK_NOLOCK);
+                       /* if failed we cannot add this device */
+                       if (obj) {
+                               obj->device_state = state;
+                               strcpy(obj->device_name, cur);
+                               ao2_link(device_state_info, obj);
+                               ao2_ref(obj, -1);
+                       }
+               }
        }
 
        return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
 }
 
 /*! \brief Check state of extension by using hints */
-static int ast_extension_state2(struct ast_exten *e)
+static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info)
 {
        struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
 
@@ -4561,7 +5063,7 @@ static int ast_extension_state2(struct ast_exten *e)
        }
 
        ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
-       return ast_extension_state3(hint_app);
+       return ast_extension_state3(hint_app, device_state_info);
 }
 
 /*! \brief Return extension_state as string */
@@ -4576,8 +5078,12 @@ const char *ast_extension_state2str(int extension_state)
        return "Unknown";
 }
 
-/*! \brief Check extension state for an extension by using hint */
-int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+/*!
+ * \internal
+ * \brief Check extension state for an extension by using hint
+ */
+static int internal_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+       struct ao2_container *device_state_info)
 {
        struct ast_exten *e;
 
@@ -4596,7 +5102,38 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char *
                }
        }
 
-       return ast_extension_state2(e);  /* Check all devices in the hint */
+       return ast_extension_state2(e, device_state_info);  /* Check all devices in the hint */
+}
+
+/*! \brief Check extension state for an extension by using hint */
+int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+{
+       return internal_extension_state_extended(c, context, exten, NULL);
+}
+
+/*! \brief Check extended extension state for an extension by using hint */
+int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+       struct ao2_container **device_state_info)
+{
+       struct ao2_container *container = NULL;
+       int ret;
+
+       if (device_state_info) {
+               container = alloc_device_state_info();
+       }
+
+       ret = internal_extension_state_extended(c, context, exten, container);
+       if (ret < 0 && container) {
+               ao2_ref(container, -1);
+               container = NULL;
+       }
+
+       if (device_state_info) {
+               get_device_state_causing_channels(container);
+               *device_state_info = container;
+       }
+
+       return ret;
 }
 
 static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
@@ -4652,7 +5189,8 @@ static int execute_state_callback(ast_state_cb_type cb,
        const char *exten,
        void *data,
        enum ast_state_cb_update_reason reason,
-       struct ast_hint *hint)
+       struct ast_hint *hint,
+       struct ao2_container *device_state_info)
 {
        int res = 0;
        struct ast_state_cb_info info = { 0, };
@@ -4663,6 +5201,7 @@ static int execute_state_callback(ast_state_cb_type cb,
        if (hint) {
                ao2_lock(hint);
                info.exten_state = hint->laststate;
+               info.device_state_info = device_state_info;
                info.presence_state = hint->last_presence_state;
                if (!(ast_strlen_zero(hint->last_presence_subtype))) {
                        info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
@@ -4685,149 +5224,121 @@ static int execute_state_callback(ast_state_cb_type cb,
        return res;
 }
 
-static int handle_presencechange(void *datap)
+/*!
+ * /internal
+ * /brief Identify a channel for every device which is supposedly responsible for the device state.
+ *
+ * Especially when the device is ringing, the oldest ringing channel is chosen.
+ * For all other cases the first encountered channel in the specific state is chosen.
+ */
+static void get_device_state_causing_channels(struct ao2_container *c)
 {
-       struct ast_hint *hint;
-       struct ast_str *hint_app = NULL;
-       struct presencechange *pc = datap;
-       struct ao2_iterator i;
-       struct ao2_iterator cb_iter;
-       char context_name[AST_MAX_CONTEXT];
-       char exten_name[AST_MAX_EXTENSION];
-       int res = -1;
+       struct ao2_iterator iter;
+       struct ast_device_state_info *info;
+       struct ast_channel *chan;
 
-       hint_app = ast_str_create(1024);
-       if (!hint_app) {
-               goto presencechange_cleanup;
+       if (!c || !ao2_container_count(c)) {
+               return;
        }
-
-       ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
-       i = ao2_iterator_init(hints, 0);
-       for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
-               struct ast_state_cb *state_cb;
-               const char *app;
-               char *parse;
-
-               ao2_lock(hint);
-
-               if (!hint->exten) {
-                       /* The extension has already been destroyed */
-                       ao2_unlock(hint);
-                       continue;
-               }
-
-               /* Does this hint monitor the device that changed state? */
-               app = ast_get_extension_app(hint->exten);
-               if (ast_strlen_zero(app)) {
-                       /* The hint does not monitor presence at all. */
-                       ao2_unlock(hint);
-                       continue;
-               }
-
-               ast_str_set(&hint_app, 0, "%s", app);
-               parse = parse_hint_presence(hint_app);
-               if (ast_strlen_zero(parse)) {
-                       ao2_unlock(hint);
-                       continue;
-               }
-               if (strcasecmp(parse, pc->provider)) {
-                       /* The hint does not monitor the presence provider. */
-                       ao2_unlock(hint);
-                       continue;
-               }
-
-               /*
-                * Save off strings in case the hint extension gets destroyed
-                * while we are notifying the watchers.
-                */
-               ast_copy_string(context_name,
-                       ast_get_context_name(ast_get_extension_context(hint->exten)),
-                       sizeof(context_name));
-               ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
-                       sizeof(exten_name));
-               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
-
-               /* Check to see if update is necessary */
-               if ((hint->last_presence_state == pc->state) &&
-                       ((hint->last_presence_subtype && pc->subtype && !strcmp(hint->last_presence_subtype, pc->subtype)) || (!hint->last_presence_subtype && !pc->subtype)) &&
-                       ((hint->last_presence_message && pc->message && !strcmp(hint->last_presence_message, pc->message)) || (!hint->last_presence_message && !pc->message))) {
-
-                       /* this update is the same as the last, do nothing */
-                       ao2_unlock(hint);
+       iter = ao2_iterator_init(c, 0);
+       for (; (info = ao2_iterator_next(&iter)); ao2_ref(info, -1)) {
+               enum ast_channel_state search_state = 0; /* prevent false uninit warning */
+               char match[AST_CHANNEL_NAME];
+               struct ast_channel_iterator *chan_iter;
+               struct timeval chantime = {0, }; /* prevent false uninit warning */
+
+               switch (info->device_state) {
+               case AST_DEVICE_RINGING:
+               case AST_DEVICE_RINGINUSE:
+                       /* find ringing channel */
+                       search_state = AST_STATE_RINGING;
+                       break;
+               case AST_DEVICE_BUSY:
+                       /* find busy channel */
+                       search_state = AST_STATE_BUSY;
+                       break;
+               case AST_DEVICE_ONHOLD:
+               case AST_DEVICE_INUSE:
+                       /* find up channel */
+                       search_state = AST_STATE_UP;
+                       break;
+               case AST_DEVICE_UNKNOWN:
+               case AST_DEVICE_NOT_INUSE:
+               case AST_DEVICE_INVALID:
+               case AST_DEVICE_UNAVAILABLE:
+               case AST_DEVICE_TOTAL /* not a state */:
+                       /* no channels are of interest */
                        continue;
                }
 
-               /* update new values */
-               ast_free(hint->last_presence_subtype);
-               ast_free(hint->last_presence_message);
-               hint->last_presence_state = pc->state;
-               hint->last_presence_subtype = pc->subtype ? ast_strdup(pc->subtype) : NULL;
-               hint->last_presence_message = pc->message ? ast_strdup(pc->message) : NULL;
-
-               ao2_unlock(hint);
-
-               /* For general callbacks */
-               cb_iter = ao2_iterator_init(statecbs, 0);
-               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-                       execute_state_callback(state_cb->change_cb,
-                               context_name,
-                               exten_name,
-                               state_cb->data,
-                               AST_HINT_UPDATE_PRESENCE,
-                               hint);
-               }
-               ao2_iterator_destroy(&cb_iter);
-
-               /* For extension callbacks */
-               cb_iter = ao2_iterator_init(hint->callbacks, 0);
-               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-                       execute_state_callback(state_cb->change_cb,
-                               context_name,
-                               exten_name,
-                               state_cb->data,
-                               AST_HINT_UPDATE_PRESENCE,
-                               hint);
+               /* iterate over all channels of the device */
+               snprintf(match, sizeof(match), "%s-", info->device_name);
+               chan_iter = ast_channel_iterator_by_name_new(match, strlen(match));
+               for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
+                       ast_channel_lock(chan);
+                       /* this channel's state doesn't match */
+                       if (search_state != ast_channel_state(chan)) {
+                               ast_channel_unlock(chan);
+                               continue;
+                       }
+                       /* any non-ringing channel will fit */
+                       if (search_state != AST_STATE_RINGING) {
+                               ast_channel_unlock(chan);
+                               info->causing_channel = chan; /* is kept ref'd! */
+                               break;
+                       }
+                       /* but we need the oldest ringing channel of the device to match with undirected pickup */
+                       if (!info->causing_channel) {
+                               chantime = ast_channel_creationtime(chan);
+                               ast_channel_ref(chan); /* must ref it! */
+                               info->causing_channel = chan;
+                       } else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) {
+                               chantime = ast_channel_creationtime(chan);
+                               ast_channel_unref(info->causing_channel);
+                               ast_channel_ref(chan); /* must ref it! */
+                               info->causing_channel = chan;
+                       }
+                       ast_channel_unlock(chan);
                }
-               ao2_iterator_destroy(&cb_iter);
+               ast_channel_iterator_destroy(chan_iter);
        }
-       ao2_iterator_destroy(&i);
-       ast_mutex_unlock(&context_merge_lock);
-
-       res = 0;
-
-presencechange_cleanup:
-       ast_free(hint_app);
-       ao2_ref(pc, -1);
-
-       return res;
+       ao2_iterator_destroy(&iter);
 }
 
-static int handle_statechange(void *datap)
+static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
 {
+       struct ast_device_state_message *dev_state;
        struct ast_hint *hint;
        struct ast_str *hint_app;
        struct ast_hintdevice *device;
        struct ast_hintdevice *cmpdevice;
-       struct statechange *sc = datap;
        struct ao2_iterator *dev_iter;
        struct ao2_iterator cb_iter;
        char context_name[AST_MAX_CONTEXT];
        char exten_name[AST_MAX_EXTENSION];
 
+       if (ast_device_state_message_type() != stasis_message_type(msg)) {
+               return;
+       }
+
+       dev_state = stasis_message_data(msg);
+       if (dev_state->eid) {
+               /* ignore non-aggregate states */
+               return;
+       }
+
        if (ao2_container_count(hintdevices) == 0) {
                /* There are no hints monitoring devices. */
-               ast_free(sc);
-               return 0;
+               return;
        }
 
        hint_app = ast_str_create(1024);
        if (!hint_app) {
-               ast_free(sc);
-               return -1;
+               return;
        }
 
-       cmpdevice = alloca(sizeof(*cmpdevice) + strlen(sc->dev));
-       strcpy(cmpdevice->hintdevice, sc->dev);
+       cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(dev_state->device));
+       strcpy(cmpdevice->hintdevice, dev_state->device);
 
        ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
        dev_iter = ao2_t_callback(hintdevices,
@@ -4838,13 +5349,15 @@ static int handle_statechange(void *datap)
        if (!dev_iter) {
                ast_mutex_unlock(&context_merge_lock);
                ast_free(hint_app);
-               ast_free(sc);
-               return -1;
+               return;
        }
 
        for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
                struct ast_state_cb *state_cb;
                int state;
+               int same_state;
+               struct ao2_container *device_state_info;
+               int first_extended_cb_call = 1;
 
                if (!device->hint) {
                        /* Should never happen. */
@@ -4878,8 +5391,13 @@ static int handle_statechange(void *datap)
                 * device state or notifying the watchers without causing a
                 * deadlock.  (conlock, hints, and hint)
                 */
-               state = ast_extension_state3(hint_app);
-               if (state == hint->laststate) {
+               /* Make a container so state3 can fill it if we wish.
+                * If that failed we simply do not provide the extended state info.
+                */
+               device_state_info = alloc_device_state_info();
+               state = ast_extension_state3(hint_app, device_state_info);
+               if ((same_state = state == hint->laststate) && (~state & AST_EXTENSION_RINGING)) {
+                       ao2_cleanup(device_state_info);
                        continue;
                }
 
@@ -4888,34 +5406,47 @@ static int handle_statechange(void *datap)
 
                /* For general callbacks */
                cb_iter = ao2_iterator_init(statecbs, 0);
-               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+               for (; !same_state && (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
                        execute_state_callback(state_cb->change_cb,
                                context_name,
                                exten_name,
                                state_cb->data,
                                AST_HINT_UPDATE_DEVICE,
-                               hint);
+                               hint,
+                               NULL);
                }
                ao2_iterator_destroy(&cb_iter);
 
                /* For extension callbacks */
+               /* extended callbacks are called when the state changed or when AST_STATE_RINGING is
+                * included. Normal callbacks are only called when the state changed.
+                */
                cb_iter = ao2_iterator_init(hint->callbacks, 0);
                for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-                       execute_state_callback(state_cb->change_cb,
-                               context_name,
-                               exten_name,
-                               state_cb->data,
-                               AST_HINT_UPDATE_DEVICE,
-                               hint);
+                       if (state_cb->extended && first_extended_cb_call) {
+                               /* Fill detailed device_state_info now that we know it is used by extd. callback */
+                               first_extended_cb_call = 0;
+                               get_device_state_causing_channels(device_state_info);
+                       }
+                       if (state_cb->extended || !same_state) {
+                               execute_state_callback(state_cb->change_cb,
+                                       context_name,
+                                       exten_name,
+                                       state_cb->data,
+                                       AST_HINT_UPDATE_DEVICE,
+                                       hint,
+                                       state_cb->extended ? device_state_info : NULL);
+                       }
                }
                ao2_iterator_destroy(&cb_iter);
+
+               ao2_cleanup(device_state_info);
        }
        ast_mutex_unlock(&context_merge_lock);
 
        ao2_iterator_destroy(dev_iter);
        ast_free(hint_app);
-       ast_free(sc);
-       return 0;
+       return;
 }
 
 /*!
@@ -4935,9 +5466,12 @@ static void destroy_state_cb(void *doomed)
        }
 }
 
-/*! \brief Add watcher for extension states with destructor */
-int ast_extension_state_add_destroy(const char *context, const char *exten,
-       ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+/*!
+ * \internal
+ * \brief Add watcher for extension states with destructor
+ */
+static int extension_state_add_destroy(const char *context, const char *exten,
+       ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
 {
        struct ast_hint *hint;
        struct ast_state_cb *state_cb;
@@ -4961,6 +5495,7 @@ int ast_extension_state_add_destroy(const char *context, const char *exten,
                state_cb->change_cb = change_cb;
                state_cb->destroy_cb = destroy_cb;
                state_cb->data = data;
+               state_cb->extended = extended;
                ao2_link(statecbs, state_cb);
 
                ao2_ref(state_cb, -1);
@@ -5013,6 +5548,7 @@ int ast_extension_state_add_destroy(const char *context, const char *exten,
        state_cb->change_cb = change_cb;        /* Pointer to callback routine */
        state_cb->destroy_cb = destroy_cb;
        state_cb->data = data;          /* Data for the callback */
+       state_cb->extended = extended;
        ao2_link(hint->callbacks, state_cb);
 
        ao2_ref(state_cb, -1);
@@ -5022,11 +5558,32 @@ int ast_extension_state_add_destroy(const char *context, const char *exten,
        return id;
 }
 
+/*! \brief Add watcher for extension states with destructor */
+int ast_extension_state_add_destroy(const char *context, const char *exten,
+       ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+       return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
+}
+
 /*! \brief Add watcher for extension states */
 int ast_extension_state_add(const char *context, const char *exten,
        ast_state_cb_type change_cb, void *data)
 {
-       return ast_extension_state_add_destroy(context, exten, change_cb, NULL, data);
+       return extension_state_add_destroy(context, exten, change_cb, NULL, data, 0);
+}
+
+/*! \brief Add watcher for extended extension states with destructor */
+int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
+       ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+       return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
+}
+
+/*! \brief Add watcher for extended extension states */
+int ast_extension_state_add_extended(const char *context, const char *exten,
+       ast_state_cb_type change_cb, void *data)
+{
+       return extension_state_add_destroy(context, exten, change_cb, NULL, data, 1);
 }
 
 /*! \brief Find Hint by callback id */
@@ -5120,7 +5677,8 @@ static void destroy_hint(void *obj)
                                exten_name,
                                state_cb->data,
                                AST_HINT_UPDATE_DEVICE,
-                               hint);
+                               hint,
+                               NULL);
                        ao2_ref(state_cb, -1);
                }
                ao2_ref(hint->callbacks, -1);
@@ -5194,7 +5752,7 @@ static int ast_add_hint(struct ast_exten *e)
                return -1;
        }
        hint_new->exten = e;
-       hint_new->laststate = ast_extension_state2(e);
+       hint_new->laststate = ast_extension_state2(e, NULL);
        if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
                hint_new->last_presence_state = presence_state;
                hint_new->last_presence_subtype = subtype;
@@ -5342,6 +5900,306 @@ int ast_spawn_extension(struct ast_channel *c, const char *context, const char *
        return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_SPAWN, found, combined_find_spawn);
 }
 
+void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
+{
+       int autoloopflag;
+       int found;
+       int spawn_error;
+
+       ast_channel_lock(chan);
+
+       /*
+        * Make sure that the channel is marked as hungup since we are
+        * going to run the h exten on it.
+        */
+       ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
+
+       /* Set h exten location */
+       if (context != ast_channel_context(chan)) {
+               ast_channel_context_set(chan, context);
+       }
+       ast_channel_exten_set(chan, "h");
+       ast_channel_priority_set(chan, 1);
+
+       /* Save autoloop flag */
+       autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
+       ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
+       ast_channel_unlock(chan);
+
+       for (;;) {
+               spawn_error = ast_spawn_extension(chan, ast_channel_context(chan),
+                       ast_channel_exten(chan), ast_channel_priority(chan),
+                       S_COR(ast_channel_caller(chan)->id.number.valid,
+                               ast_channel_caller(chan)->id.number.str, NULL), &found, 1);
+
+               ast_channel_lock(chan);
+               if (spawn_error) {
+                       /* The code after the loop needs the channel locked. */
+                       break;
+               }
+               ast_channel_priority_set(chan, ast_channel_priority(chan) + 1);
+               ast_channel_unlock(chan);
+       }
+       if (found && spawn_error) {
+               /* Something bad happened, or a hangup has been requested. */
+               ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n",
+                       ast_channel_context(chan), ast_channel_exten(chan),
+                       ast_channel_priority(chan), ast_channel_name(chan));
+               ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n",
+                       ast_channel_context(chan), ast_channel_exten(chan),
+                       ast_channel_priority(chan), ast_channel_name(chan));
+       }
+
+       /* An "h" exten has been run, so indicate that one has been run. */
+       ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN);
+
+       /* Restore autoloop flag */
+       ast_set2_flag(ast_channel_flags(chan), autoloopflag, AST_FLAG_IN_AUTOLOOP);
+       ast_channel_unlock(chan);
+}
+
+/*!
+ * \internal
+ * \brief Publish a hangup handler related message to \ref stasis
+ */
+static void publish_hangup_handler_message(const char *action, struct ast_channel *chan, const char *handler)
+{
+       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+
+       blob = ast_json_pack("{s: s, s: s}",
+                       "type", action,
+                       "handler", S_OR(handler, ""));
+       if (!blob) {
+               return;
+       }
+
+       ast_channel_publish_blob(chan, ast_channel_hangup_handler_type(), blob);
+}
+
+int ast_pbx_hangup_handler_run(struct ast_channel *chan)
+{
+       struct ast_hangup_handler_list *handlers;
+       struct ast_hangup_handler *h_handler;
+
+       ast_channel_lock(chan);
+       handlers = ast_channel_hangup_handlers(chan);
+       if (AST_LIST_EMPTY(handlers)) {
+               ast_channel_unlock(chan);
+               return 0;
+       }
+
+       /*
+        * Make sure that the channel is marked as hungup since we are
+        * going to run the hangup handlers on it.
+        */
+       ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
+
+       for (;;) {
+               handlers = ast_channel_hangup_handlers(chan);
+               h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
+               if (!h_handler) {
+                       break;
+               }
+
+               publish_hangup_handler_message("run", chan, h_handler->args);
+               ast_channel_unlock(chan);
+
+               ast_app_exec_sub(NULL, chan, h_handler->args, 1);
+               ast_free(h_handler);
+
+               ast_channel_lock(chan);
+       }
+       ast_channel_unlock(chan);
+       return 1;
+}
+
+void ast_pbx_hangup_handler_init(struct ast_channel *chan)
+{
+       struct ast_hangup_handler_list *handlers;
+
+       handlers = ast_channel_hangup_handlers(chan);
+       AST_LIST_HEAD_INIT_NOLOCK(handlers);
+}
+
+void ast_pbx_hangup_handler_destroy(struct ast_channel *chan)
+{
+       struct ast_hangup_handler_list *handlers;
+       struct ast_hangup_handler *h_handler;
+
+       ast_channel_lock(chan);
+
+       /* Get rid of each of the hangup handlers on the channel */
+       handlers = ast_channel_hangup_handlers(chan);
+       while ((h_handler = AST_LIST_REMOVE_HEAD(handlers, node))) {
+               ast_free(h_handler);
+       }
+
+       ast_channel_unlock(chan);
+}
+
+int ast_pbx_hangup_handler_pop(struct ast_channel *chan)
+{
+       struct ast_hangup_handler_list *handlers;
+       struct ast_hangup_handler *h_handler;
+
+       ast_channel_lock(chan);
+       handlers = ast_channel_hangup_handlers(chan);
+       h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
+       if (h_handler) {
+               publish_hangup_handler_message("pop", chan, h_handler->args);
+       }
+       ast_channel_unlock(chan);
+       if (h_handler) {
+               ast_free(h_handler);
+               return 1;
+       }
+       return 0;
+}
+
+void ast_pbx_hangup_handler_push(struct ast_channel *chan, const char *handler)
+{
+       struct ast_hangup_handler_list *handlers;
+       struct ast_hangup_handler *h_handler;
+       const char *expanded_handler;
+
+       if (ast_strlen_zero(handler)) {
+               return;
+       }
+
+       expanded_handler = ast_app_expand_sub_args(chan, handler);
+       if (!expanded_handler) {
+               return;
+       }
+       h_handler = ast_malloc(sizeof(*h_handler) + 1 + strlen(expanded_handler));
+       if (!h_handler) {
+               ast_free((char *) expanded_handler);
+               return;
+       }
+       strcpy(h_handler->args, expanded_handler);/* Safe */
+       ast_free((char *) expanded_handler);
+
+       ast_channel_lock(chan);
+
+       handlers = ast_channel_hangup_handlers(chan);
+       AST_LIST_INSERT_HEAD(handlers, h_handler, node);
+       publish_hangup_handler_message("push", chan, h_handler->args);
+       ast_channel_unlock(chan);
+}
+
+#define HANDLER_FORMAT "%-30s %s\n"
+
+/*!
+ * \internal
+ * \brief CLI output the hangup handler headers.
+ * \since 11.0
+ *
+ * \param fd CLI file descriptor to use.
+ *
+ * \return Nothing
+ */
+static void ast_pbx_hangup_handler_headers(int fd)
+{
+       ast_cli(fd, HANDLER_FORMAT, "Channel", "Handler");
+}
+
+/*!
+ * \internal
+ * \brief CLI output the channel hangup handlers.
+ * \since 11.0
+ *
+ * \param fd CLI file descriptor to use.
+ * \param chan Channel to show hangup handlers.
+ *
+ * \return Nothing
+ */
+static void ast_pbx_hangup_handler_show(int fd, struct ast_channel *chan)
+{
+       struct ast_hangup_handler_list *handlers;
+       struct ast_hangup_handler *h_handler;
+       int first = 1;
+
+       ast_channel_lock(chan);
+       handlers = ast_channel_hangup_handlers(chan);
+       AST_LIST_TRAVERSE(handlers, h_handler, node) {
+               ast_cli(fd, HANDLER_FORMAT, first ? ast_channel_name(chan) : "", h_handler->args);
+               first = 0;
+       }
+       ast_channel_unlock(chan);
+}
+
+/*
+ * \brief 'show hanguphandlers <channel>' CLI command implementation function...
+ */
+static char *handle_show_hangup_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_channel *chan;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "core show hanguphandlers";
+               e->usage =
+                       "Usage: core show hanguphandlers <channel>\n"
+                       "       Show hangup handlers of a specified channel.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+       }
+
+       if (a->argc < 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       chan = ast_channel_get_by_name(a->argv[3]);
+       if (!chan) {
+               ast_cli(a->fd, "Channel does not exist.\n");
+               return CLI_FAILURE;
+       }
+
+       ast_pbx_hangup_handler_headers(a->fd);
+       ast_pbx_hangup_handler_show(a->fd, chan);
+
+       ast_channel_unref(chan);
+
+       return CLI_SUCCESS;
+}
+
+/*
+ * \brief 'show hanguphandlers all' CLI command implementation function...
+ */
+static char *handle_show_hangup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_channel_iterator *iter;
+       struct ast_channel *chan;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "core show hanguphandlers all";
+               e->usage =
+                       "Usage: core show hanguphandlers all\n"
+                       "       Show hangup handlers for all channels.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+       }
+
+       if (a->argc < 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       iter = ast_channel_iterator_all_new();
+       if (!iter) {
+               return CLI_FAILURE;
+       }
+
+       ast_pbx_hangup_handler_headers(a->fd);
+       for (; (chan = ast_channel_iterator_next(iter)); ast_channel_unref(chan)) {
+               ast_pbx_hangup_handler_show(a->fd, chan);
+       }
+       ast_channel_iterator_destroy(iter);
+
+       return CLI_SUCCESS;
+}
+
 /*! helper function to set extension and priority */
 static void set_ext_pri(struct ast_channel *c, const char *exten, int pri)
 {
@@ -5402,7 +6260,7 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                ast_free(ast_channel_pbx(c));
        }
        if (!(pbx = ast_calloc(1, sizeof(*pbx)))) {
-               return -1;
+               return AST_PBX_FAILED;
        }
 
        callid = ast_read_threadstorage_callid();
@@ -5415,7 +6273,9 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                if (!callid) {
                        callid = ast_create_callid();
                        if (callid) {
+                               ast_channel_lock(c);
                                ast_channel_callid_set(c, callid);
+                               ast_channel_unlock(c);
                        }
                }
                ast_callid_threadassoc_add(callid);
@@ -5443,12 +6303,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                set_ext_pri(c, "s", 1);
        }
 
-       ast_channel_lock(c);
-       if (ast_channel_cdr(c)) {
-               /* allow CDR variables that have been collected after channel was created to be visible during call */
-               ast_cdr_update(c);
-       }
-       ast_channel_unlock(c);
        for (;;) {
                char dst_exten[256];    /* buffer to accumulate digits */
                int pos = 0;            /* XXX should check bounds */
@@ -5456,6 +6310,9 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                int invalid = 0;
                int timeout = 0;
 
+               /* No digits pressed yet */
+               dst_exten[pos] = '\0';
+
                /* loop on priorities in this context/exten */
                while (!(res = ast_spawn_extension(c, ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
                        S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL),
@@ -5555,11 +6412,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                                        }
                                        /* Call timed out with no special extension to jump to. */
                                }
-                               ast_channel_lock(c);
-                               if (ast_channel_cdr(c)) {
-                                       ast_cdr_update(c);
-                               }
-                               ast_channel_unlock(c);
                                error = 1;
                                break;
                        }
@@ -5665,12 +6517,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                                        }
                                }
                        }
-                       ast_channel_lock(c);
-                       if (ast_channel_cdr(c)) {
-                               ast_verb(2, "CDR updated on %s\n",ast_channel_name(c));
-                               ast_cdr_update(c);
-                       }
-                       ast_channel_unlock(c);
                }
        }
 
@@ -5680,27 +6526,15 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
 
        if (!args || !args->no_hangup_chan) {
                ast_softhangup(c, AST_SOFTHANGUP_APPUNLOAD);
-       }
-
-       if ((!args || !args->no_hangup_chan)
-               && !ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN)
-               && ast_exists_extension(c, ast_channel_context(c), "h", 1,
-                       S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-               set_ext_pri(c, "h", 1);
-               if (ast_channel_cdr(c) && ast_opt_end_cdr_before_h_exten) {
-                       ast_cdr_end(ast_channel_cdr(c));
-               }
-               while ((res = ast_spawn_extension(c, ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
-                       S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL),
-                       &found, 1)) == 0) {
-                       ast_channel_priority_set(c, ast_channel_priority(c) + 1);
-               }
-               if (found && res) {
-                       /* Something bad happened, or a hangup has been requested. */
-                       ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
-                       ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+               if (!ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN)
+                       && ast_exists_extension(c, ast_channel_context(c), "h", 1,
+                               S_COR(ast_channel_caller(c)->id.number.valid,
+                                       ast_channel_caller(c)->id.number.str, NULL))) {
+                       ast_pbx_h_exten_run(c, ast_channel_context(c));
                }
+               ast_pbx_hangup_handler_run(c);
        }
+
        ast_set2_flag(ast_channel_flags(c), autoloopflag, AST_FLAG_IN_AUTOLOOP);
        ast_clear_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN); /* from one round to the next, make sure this gets cleared */
        pbx_destroy(ast_channel_pbx(c));
@@ -5710,7 +6544,7 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
                ast_hangup(c);
        }
 
-       return 0;
+       return AST_PBX_SUCCESS;
 }
 
 /*!
@@ -5728,16 +6562,16 @@ static int increase_call_count(const struct ast_channel *c)
 #endif
 
        ast_mutex_lock(&maxcalllock);
-       if (option_maxcalls) {
-               if (countcalls >= option_maxcalls) {
-                       ast_log(LOG_WARNING, "Maximum call limit of %d calls exceeded by '%s'!\n", option_maxcalls, ast_channel_name(c));
+       if (ast_option_maxcalls) {
+               if (countcalls >= ast_option_maxcalls) {
+                       ast_log(LOG_WARNING, "Maximum call limit of %d calls exceeded by '%s'!\n", ast_option_maxcalls, ast_channel_name(c));
                        failed = -1;
                }
        }
-       if (option_maxload) {
+       if (ast_option_maxload) {
                getloadavg(&curloadavg, 1);
-               if (curloadavg >= option_maxload) {
-                       ast_log(LOG_WARNING, "Maximum loadavg limit of %f load exceeded by '%s' (currently %f)!\n", option_maxload, ast_channel_name(c), curloadavg);
+               if (curloadavg >= ast_option_maxload) {
+                       ast_log(LOG_WARNING, "Maximum loadavg limit of %f load exceeded by '%s' (currently %f)!\n", ast_option_maxload, ast_channel_name(c), curloadavg);
                        failed = -1;
                }
        }
@@ -6044,7 +6878,7 @@ int ast_context_remove_switch2(struct ast_context *con, const char *sw, const ch
 /*! \note This function will lock conlock. */
 int ast_context_remove_extension(const char *context, const char *extension, int priority, const char *registrar)
 {
-       return ast_context_remove_extension_callerid(context, extension, priority, NULL, 0, registrar);
+       return ast_context_remove_extension_callerid(context, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar);
 }
 
 int ast_context_remove_extension_callerid(const char *context, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar)
@@ -6074,7 +6908,7 @@ int ast_context_remove_extension_callerid(const char *context, const char *exten
  */
 int ast_context_remove_extension2(struct ast_context *con, const char *extension, int priority, const char *registrar, int already_locked)
 {
-       return ast_context_remove_extension_callerid2(con, extension, priority, NULL, 0, registrar, already_locked);
+       return ast_context_remove_extension_callerid2(con, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar, already_locked);
 }
 
 int ast_context_remove_extension_callerid2(struct ast_context *con, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar, int already_locked)
@@ -6090,10 +6924,6 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
        if (!already_locked)
                ast_wrlock_context(con);
 
-       /* Handle this is in the new world */
-
-       /* FIXME For backwards compatibility, if callerid==NULL, then remove ALL
-        * peers, not just those matching the callerid. */
 #ifdef NEED_DEBUG
        ast_verb(3,"Removing %s/%s/%d%s%s from trees, registrar=%s\n", con->name, extension, priority, matchcallerid ? "/" : "", matchcallerid ? callerid : "", registrar);
 #endif
@@ -6102,7 +6932,7 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
 #endif
        /* find this particular extension */
        ex.exten = dummy_name;
-       ex.matchcid = matchcallerid && !ast_strlen_zero(callerid); /* don't say match if there's no callerid */
+       ex.matchcid = matchcallerid;
        ex.cidmatch = callerid;
        ast_copy_string(dummy_name, extension, sizeof(dummy_name));
        exten = ast_hashtab_lookup(con->root_table, &ex);
@@ -6125,7 +6955,6 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
                        ex.priority = priority;
                        exten2 = ast_hashtab_lookup(exten->peer_table, &ex);
                        if (exten2) {
-
                                if (exten2->label) { /* if this exten has a label, remove that, too */
                                        exten3 = ast_hashtab_remove_this_object(exten->peer_label_table,exten2);
                                        if (!exten3)
@@ -6186,10 +7015,11 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
 
        /* scan the priority list to remove extension with exten->priority == priority */
        for (peer = exten, next_peer = exten->peer ? exten->peer : exten->next;
-                peer && !strcmp(peer->exten, extension) && (!matchcallerid || (!ast_strlen_zero(callerid) && !ast_strlen_zero(peer->cidmatch) && !strcmp(peer->cidmatch,callerid)) || (ast_strlen_zero(callerid) && ast_strlen_zero(peer->cidmatch)));
+                peer && !strcmp(peer->exten, extension) &&
+                       (!callerid || (!matchcallerid && !peer->matchcid) || (matchcallerid && peer->matchcid && !strcmp(peer->cidmatch, callerid))) ;
                        peer = next_peer, next_peer = next_peer ? (next_peer->peer ? next_peer->peer : next_peer->next) : NULL) {
+
                if ((priority == 0 || peer->priority == priority) &&
-                               (!callerid || !matchcallerid || (matchcallerid && !strcmp(peer->cidmatch, callerid))) &&
                                (!registrar || !strcmp(peer->registrar, registrar) )) {
                        found = 1;
 
@@ -6220,6 +7050,7 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
                                previous_peer->peer = peer->peer;
                        }
 
+
                        /* now, free whole priority extension */
                        destroy_exten(peer);
                } else {
@@ -6236,6 +7067,7 @@ int ast_context_remove_extension_callerid2(struct ast_context *con, const char *
  * \note This function locks contexts list by &conlist, searches for the right context
  * structure, and locks the macrolock mutex in that context.
  * macrolock is used to limit a macro to be executed by one call at a time.
+ * \param context The context
  */
 int ast_context_lockmacro(const char *context)
 {
@@ -6257,6 +7089,7 @@ int ast_context_lockmacro(const char *context)
  * \note This function locks contexts list by &conlist, searches for the right context
  * structure, and unlocks the macrolock mutex in that context.
  * macrolock is used to limit a macro to be executed by one call at a time.
+ * \param context The context
  */
 int ast_context_unlockmacro(const char *context)
 {
@@ -6277,21 +7110,19 @@ int ast_context_unlockmacro(const char *context)
 /*! \brief Dynamically register a new dial plan application */
 int ast_register_application2(const char *app, int (*execute)(struct ast_channel *, const char *), const char *synopsis, const char *description, void *mod)
 {
-       struct ast_app *tmp, *cur = NULL;
-       char tmps[80];
-       int length, res;
+       struct ast_app *tmp;
+       struct ast_app *cur;
+       int length;
 #ifdef AST_XML_DOCS
        char *tmpxml;
 #endif
 
        AST_RWLIST_WRLOCK(&apps);
-       AST_RWLIST_TRAVERSE(&apps, tmp, list) {
-               if (!(res = strcasecmp(app, tmp->name))) {
-                       ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
-                       AST_RWLIST_UNLOCK(&apps);
-                       return -1;
-               } else if (res < 0)
-                       break;
+       cur = pbx_findapp_nolock(app);
+       if (cur) {
+               ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
+               AST_RWLIST_UNLOCK(&apps);
+               return -1;
        }
 
        length = sizeof(*tmp) + strlen(app) + 1;
@@ -6359,7 +7190,7 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel
        if (!cur)
                AST_RWLIST_INSERT_TAIL(&apps, tmp, list);
 
-       ast_verb(2, "Registered application '%s'\n", term_color(tmps, tmp->name, COLOR_BRCYAN, 0, sizeof(tmps)));
+       ast_verb(2, "Registered application '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, tmp->name));
 
        AST_RWLIST_UNLOCK(&apps);
 
@@ -6401,74 +7232,67 @@ void ast_unregister_switch(struct ast_switch *sw)
 
 static void print_app_docs(struct ast_app *aa, int fd)
 {
-       /* Maximum number of characters added by terminal coloring is 22 */
-       char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], stxtitle[40], argtitle[40];
-       char seealsotitle[40];
-       char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *syntax = NULL, *arguments = NULL;
-       char *seealso = NULL;
-       int syntax_size, synopsis_size, description_size, arguments_size, seealso_size;
-
-       snprintf(info, sizeof(info), "\n  -= Info about application '%s' =- \n\n", aa->name);
-       term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle));
-
-       term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
-       term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40);
-       term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
-       term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
-       term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40);
-
 #ifdef AST_XML_DOCS
+       char *synopsis = NULL, *description = NULL, *arguments = NULL, *seealso = NULL;
        if (aa->docsrc == AST_XML_DOC) {
+               synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
                description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1);
                arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1);
-               synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
                seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1);
-
                if (!synopsis || !description || !arguments || !seealso) {
-                       goto return_cleanup;
-               }
+                       goto free_docs;
+               }
+               ast_cli(fd, "\n"
+                       "%s  -= Info about application '%s' =- %s\n\n"
+                       COLORIZE_FMT "\n"
+                       "%s\n\n"
+                       COLORIZE_FMT "\n"
+                       "%s\n\n"
+                       COLORIZE_FMT "\n"
+                       "%s%s%s\n\n"
+                       COLORIZE_FMT "\n"
+                       "%s\n\n"
+                       COLORIZE_FMT "\n"
+                       "%s\n",
+                       ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"), synopsis,
+                       COLORIZE(COLOR_MAGENTA, 0, "[Description]"), description,
+                       COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
+                               ast_term_color(COLOR_CYAN, 0), S_OR(aa->syntax, "Not available"), ast_term_reset(),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"), arguments,
+                       COLORIZE(COLOR_MAGENTA, 0, "[See Also]"), seealso);
+free_docs:
+               ast_free(synopsis);
+               ast_free(description);
+               ast_free(arguments);
+               ast_free(seealso);
        } else
 #endif
        {
-               synopsis_size = strlen(S_OR(aa->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-               synopsis = ast_malloc(synopsis_size);
-
-               description_size = strlen(S_OR(aa->description, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-               description = ast_malloc(description_size);
-
-               arguments_size = strlen(S_OR(aa->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-               arguments = ast_malloc(arguments_size);
-
-               seealso_size = strlen(S_OR(aa->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-               seealso = ast_malloc(seealso_size);
-
-               if (!synopsis || !description || !arguments || !seealso) {
-                       goto return_cleanup;
-               }
-
-               term_color(synopsis, S_OR(aa->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size);
-               term_color(description, S_OR(aa->description, "Not available"), COLOR_CYAN, 0, description_size);
-               term_color(arguments, S_OR(aa->arguments, "Not available"), COLOR_CYAN, 0, arguments_size);
-               term_color(seealso, S_OR(aa->seealso, "Not available"), COLOR_CYAN, 0, seealso_size);
-       }
-
-       /* Handle the syntax the same for both XML and raw docs */
-       syntax_size = strlen(S_OR(aa->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-       if (!(syntax = ast_malloc(syntax_size))) {
-               goto return_cleanup;
+               ast_cli(fd, "\n"
+                       "%s  -= Info about application '%s' =- %s\n\n"
+                       COLORIZE_FMT "\n"
+                       COLORIZE_FMT "\n\n"
+                       COLORIZE_FMT "\n"
+                       COLORIZE_FMT "\n\n"
+                       COLORIZE_FMT "\n"
+                       COLORIZE_FMT "\n\n"
+                       COLORIZE_FMT "\n"
+                       COLORIZE_FMT "\n\n"
+                       COLORIZE_FMT "\n"
+                       COLORIZE_FMT "\n",
+                       ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"),
+                       COLORIZE(COLOR_CYAN, 0, S_OR(aa->synopsis, "Not available")),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Description]"),
+                       COLORIZE(COLOR_CYAN, 0, S_OR(aa->description, "Not available")),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
+                       COLORIZE(COLOR_CYAN, 0, S_OR(aa->syntax, "Not available")),
+                       COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"),
+                       COLORIZE(COLOR_CYAN, 0, S_OR(aa->arguments, "Not available")),
+                       COLORIZE(COLOR_MAGENTA, 0, "[See Also]"),
+                       COLORIZE(COLOR_CYAN, 0, S_OR(aa->seealso, "Not available")));
        }
-       term_color(syntax, S_OR(aa->syntax, "Not available"), COLOR_CYAN, 0, syntax_size);
-
-       ast_cli(fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n",
-                       infotitle, syntitle, synopsis, destitle, description,
-                       stxtitle, syntax, argtitle, arguments, seealsotitle, seealso);
-
-return_cleanup:
-       ast_free(description);
-       ast_free(arguments);
-       ast_free(synopsis);
-       ast_free(seealso);
-       ast_free(syntax);
 }
 
 /*
@@ -6699,6 +7523,81 @@ static char *handle_show_switches(struct ast_cli_entry *e, int cmd, struct ast_c
        return CLI_SUCCESS;
 }
 
+#if 0
+/* This code can be used to test if the system survives running out of memory.
+ * It might be an idea to put this in only if ENABLE_AUTODESTRUCT_TESTS is enabled.
+ *
+ * If you want to test this, these Linux sysctl flags might be appropriate:
+ *   vm.overcommit_memory = 2
+ *   vm.swappiness = 0
+ *
+ * <@Corydon76-home> I envision 'core eat disk space' and 'core eat file descriptors' now
+ * <@mjordan> egads
+ * <@mjordan> it's literally the 'big red' auto-destruct button
+ * <@mjordan> if you were wondering who even builds such a thing.... well, now you know
+ * ...
+ * <@Corydon76-home> What about if they lived only if you defined TEST_FRAMEWORK?  Shouldn't have those on production machines
+ * <@mjordan> I think accompanied with an update to one of our README files that "no, really, TEST_FRAMEWORK isn't for you", I'd be fine
+ */
+static char *handle_eat_memory(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       void **blocks;
+       int blocks_pos = 0;
+       const int blocks_max = 50000;
+       long long int allocated = 0;
+       int sizes[] = {
+               100 * 1024 * 1024,
+               100 * 1024,
+               2 * 1024,
+               400,
+               0
+       };
+       int i;
+
+       switch (cmd) {
+       case CLI_INIT:
+               /* To do: add method to free memory again? 5 minutes? */
+               e->command = "core eat memory";
+               e->usage =
+                       "Usage: core eat memory\n"
+                       "       Eats all available memory so you can test if the system survives\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       blocks = ast_malloc(sizeof(void*) * blocks_max);
+       if (!blocks) {
+               ast_log(LOG_ERROR, "Already out of mem?\n");
+               return CLI_SUCCESS;
+       }
+
+       for (i = 0; sizes[i]; ++i) {
+               int alloc_size = sizes[i];
+               ast_log(LOG_WARNING, "Allocating %d sized blocks (got %d blocks already)\n", alloc_size, blocks_pos);
+               while (1) {
+                       void *block;
+                       if (blocks_pos >= blocks_max) {
+                               ast_log(LOG_ERROR, "Memory buffer too small? Run me again :)\n");
+                               break;
+                       }
+
+                       block = ast_malloc(alloc_size);
+                       if (!block) {
+                               break;
+                       }
+
+                       blocks[blocks_pos++] = block;
+                       allocated += alloc_size;
+               }
+       }
+
+       /* No freeing of the mem! */
+       ast_log(LOG_WARNING, "Allocated %lld bytes total!\n", allocated);
+       return CLI_SUCCESS;
+}
+#endif
+
 static char *handle_show_applications(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_app *aa;
@@ -6899,7 +7798,7 @@ static int show_dialplan_helper(int fd, const char *context, const char *exten,
                        dpc->total_prio++;
 
                        /* write extension name and first peer */
-                       if (e->matchcid)
+                       if (e->matchcid == AST_EXT_MATCHCID_ON)
                                snprintf(buf, sizeof(buf), "'%s' (CID match '%s') => ", ast_get_extension_name(e), e->cidmatch);
                        else
                                snprintf(buf, sizeof(buf), "'%s' =>", ast_get_extension_name(e));
@@ -7209,6 +8108,7 @@ static int manager_show_dialplan_helper(struct mansession *s, const struct messa
                        continue;       /* not the name we want */
 
                dpc->context_existence = 1;
+               dpc->total_context++;
 
                ast_debug(3, "manager_show_dialplan: Found Context: %s \n", ast_get_context_name(c));
 
@@ -7232,8 +8132,6 @@ static int manager_show_dialplan_helper(struct mansession *s, const struct messa
 
                        dpc->extension_existence = 1;
 
-                       /* may we print context info? */
-                       dpc->total_context++;
                        dpc->total_exten++;
 
                        p = NULL;               /* walk next extension peers */
@@ -7335,17 +8233,17 @@ static int manager_show_dialplan(struct mansession *s, const struct message *m)
 
        manager_show_dialplan_helper(s, m, idtext, context, exten, &counters, NULL);
 
-       if (context && !counters.context_existence) {
+       if (!ast_strlen_zero(context) && !counters.context_existence) {
                char errorbuf[BUFSIZ];
 
                snprintf(errorbuf, sizeof(errorbuf), "Did not find context %s", context);
                astman_send_error(s, m, errorbuf);
                return 0;
        }
-       if (exten && !counters.extension_existence) {
+       if (!ast_strlen_zero(exten) && !counters.extension_existence) {
                char errorbuf[BUFSIZ];
 
-               if (context)
+               if (!ast_strlen_zero(context))
                        snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s@%s", exten, context);
                else
                        snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s in any context", exten);
@@ -7353,6 +8251,10 @@ static int manager_show_dialplan(struct mansession *s, const struct message *m)
                return 0;
        }
 
+       if (!counters.total_items) {
+               manager_dpsendack(s, m);
+       }
+
        astman_append(s, "Event: ShowDialPlanComplete\r\n"
                "EventList: Complete\r\n"
                "ListItems: %d\r\n"
@@ -7427,8 +8329,9 @@ static char *handle_show_device2extenstate(struct ast_cli_entry *e, int cmd, str
 /*! \brief CLI support for listing chanvar's variables in a parseable way */
 static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       struct ast_channel *chan = NULL;
-       struct ast_str *vars = ast_str_alloca(BUFSIZ * 4); /* XXX large because we might have lots of channel vars */
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       struct ast_channel_snapshot *snapshot;
+       struct ast_var_t *var;
 
        switch (cmd) {
        case CLI_INIT:
@@ -7444,19 +8347,16 @@ static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cl
        if (a->argc != e->args + 1)
                return CLI_SHOWUSAGE;
 
-       if (!(chan = ast_channel_get_by_name(a->argv[e->args]))) {
+       if (!(msg = stasis_cache_get(ast_channel_cache_by_name(), ast_channel_snapshot_type(), a->argv[3]))) {
                ast_cli(a->fd, "Channel '%s' not found\n", a->argv[e->args]);
                return CLI_FAILURE;
        }
+       snapshot = stasis_message_data(msg);
 
-       pbx_builtin_serialize_variables(chan, &vars);
-
-       if (ast_str_strlen(vars)) {
-               ast_cli(a->fd, "\nVariables for channel %s:\n%s\n", a->argv[e->args], ast_str_buffer(vars));
+       AST_LIST_TRAVERSE(snapshot->channel_vars, var, entries) {
+               ast_cli(a->fd, "%s=%s\n", ast_var_name(var), ast_var_value(var));
        }
 
-       chan = ast_channel_unref(chan);
-
        return CLI_SUCCESS;
 }
 
@@ -7579,6 +8479,9 @@ static char *handle_unset_extenpatternmatchnew(struct ast_cli_entry *e, int cmd,
  * CLI entries for upper commands ...
  */
 static struct ast_cli_entry pbx_cli[] = {
+#if 0
+       AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"),
+#endif
        AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
        AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"),
        AST_CLI_DEFINE(handle_show_switches, "Show alternative switches"),
@@ -7590,6 +8493,8 @@ static struct ast_cli_entry pbx_cli[] = {
 #endif
        AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"),
        AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"),
+       AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"),
+       AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"),
        AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
        AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"),
        AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"),
@@ -7620,23 +8525,32 @@ static void unreference_cached_app(struct ast_app *app)
 
 int ast_unregister_application(const char *app)
 {
-       struct ast_app *tmp;
+       struct ast_app *cur;
+       int cmp;
 
        AST_RWLIST_WRLOCK(&apps);
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, tmp, list) {
-               if (!strcasecmp(app, tmp->name)) {
-                       unreference_cached_app(tmp);
+       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
+               cmp = strcasecmp(app, cur->name);
+               if (cmp > 0) {
+                       continue;
+               }
+               if (!cmp) {
+                       /* Found it. */
+                       unreference_cached_app(cur);
                        AST_RWLIST_REMOVE_CURRENT(list);
-                       ast_verb(2, "Unregistered application '%s'\n", tmp->name);
-                       ast_string_field_free_memory(tmp);
-                       ast_free(tmp);
+                       ast_verb(2, "Unregistered application '%s'\n", cur->name);
+                       ast_string_field_free_memory(cur);
+                       ast_free(cur);
                        break;
                }
+               /* Not in container. */
+               cur = NULL;
+               break;
        }
        AST_RWLIST_TRAVERSE_SAFE_END;
        AST_RWLIST_UNLOCK(&apps);
 
-       return tmp ? 0 : -1;
+       return cur ? 0 : -1;
 }
 
 struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
@@ -7884,6 +8798,16 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
        begintime = ast_tvnow();
        ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */
        ast_wrlock_contexts();
+
+       if (!contexts_table) {
+               /* Well, that's odd. There are no contexts. */
+               contexts_table = exttable;
+               contexts = *extcontexts;
+               ast_unlock_contexts();
+               ast_mutex_unlock(&context_merge_lock);
+               return;
+       }
+
        iter = ast_hashtab_start_traversal(contexts_table);
        while ((tmp = ast_hashtab_next(iter))) {
                context_merge(extcontexts, exttable, tmp, registrar);
@@ -8003,7 +8927,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
 
        /*
         * Notify watchers of all removed hints with the same lock
-        * environment as handle_statechange().
+        * environment as device_state_cb().
         */
        while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
                /* this hint has been removed, notify the watchers */
@@ -8013,6 +8937,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
                                saved_hint->exten,
                                thiscb->data,
                                AST_HINT_UPDATE_DEVICE,
+                               NULL,
                                NULL);
                        /* Ref that we added when putting into saved_hint->callbacks */
                        ao2_ref(thiscb, -1);
@@ -8235,12 +9160,19 @@ static const char * const months[] =
        "dec",
        NULL,
 };
-
+/*! /brief Build timing
+ *
+ * /param i info
+ * /param info_in
+ *
+ */
 int ast_build_timing(struct ast_timing *i, const char *info_in)
 {
        char *info;
        int j, num_fields, last_sep = -1;
 
+       i->timezone = NULL;
+
        /* Check for empty just in case */
        if (ast_strlen_zero(info_in)) {
                return 0;
@@ -8260,8 +9192,6 @@ int ast_build_timing(struct ast_timing *i, const char *info_in)
        /* save the timezone, if it is specified */
        if (num_fields == 5) {
                i->timezone = ast_strdup(info + last_sep + 1);
-       } else {
-               i->timezone = NULL;
        }
 
        /* Assume everything except time */
@@ -8656,84 +9586,35 @@ int ast_explicit_goto(struct ast_channel *chan, const char *context, const char
 
 int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
 {
-       int res = 0;
-       struct ast_channel *tmpchan;
-       struct {
-               char *accountcode;
-               char *exten;
-               char *context;
-               char *linkedid;
-               char *name;
-               struct ast_cdr *cdr;
-               int amaflags;
-               int state;
-               struct ast_format readformat;
-               struct ast_format writeformat;
-       } tmpvars = { 0, };
+       struct ast_channel *newchan;
 
        ast_channel_lock(chan);
-       if (ast_channel_pbx(chan)) { /* This channel is currently in the PBX */
-               ast_explicit_goto(chan, context, exten, priority + 1);
+       /* Channels in a bridge or running a PBX can be sent directly to the specified destination */
+       if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
+               if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+                       priority += 1;
+               }
+               ast_explicit_goto(chan, context, exten, priority);
                ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
                ast_channel_unlock(chan);
-               return res;
+               return 0;
        }
-
-       /* In order to do it when the channel doesn't really exist within
-        * the PBX, we have to make a new channel, masquerade, and start the PBX
-        * at the new location */
-       tmpvars.accountcode = ast_strdupa(ast_channel_accountcode(chan));
-       tmpvars.exten = ast_strdupa(ast_channel_exten(chan));
-       tmpvars.context = ast_strdupa(ast_channel_context(chan));
-       tmpvars.linkedid = ast_strdupa(ast_channel_linkedid(chan));
-       tmpvars.name = ast_strdupa(ast_channel_name(chan));
-       tmpvars.amaflags = ast_channel_amaflags(chan);
-       tmpvars.state = ast_channel_state(chan);
-       ast_format_copy(&tmpvars.writeformat, ast_channel_writeformat(chan));
-       ast_format_copy(&tmpvars.readformat, ast_channel_readformat(chan));
-       tmpvars.cdr = ast_channel_cdr(chan) ? ast_cdr_dup(ast_channel_cdr(chan)) : NULL;
-
        ast_channel_unlock(chan);
 
-       /* Do not hold any channel locks while calling channel_alloc() since the function
-        * locks the channel container when linking the new channel in. */
-       if (!(tmpchan = ast_channel_alloc(0, tmpvars.state, 0, 0, tmpvars.accountcode, tmpvars.exten, tmpvars.context, tmpvars.linkedid, tmpvars.amaflags, "AsyncGoto/%s", tmpvars.name))) {
-               ast_cdr_discard(tmpvars.cdr);
+       /* Otherwise, we need to gain control of the channel first */
+       newchan = ast_channel_yank(chan);
+       if (!newchan) {
+               ast_log(LOG_WARNING, "Unable to gain control of channel %s\n", ast_channel_name(chan));
                return -1;
        }
-
-       /* copy the cdr info over */
-       if (tmpvars.cdr) {
-               ast_cdr_discard(ast_channel_cdr(tmpchan));
-               ast_channel_cdr_set(tmpchan, tmpvars.cdr);
-               tmpvars.cdr = NULL;
-       }
-
-       /* Make formats okay */
-       ast_format_copy(ast_channel_readformat(tmpchan), &tmpvars.readformat);
-       ast_format_copy(ast_channel_writeformat(tmpchan), &tmpvars.writeformat);
-
-       /* Setup proper location. Never hold another channel lock while calling this function. */
-       ast_explicit_goto(tmpchan, S_OR(context, tmpvars.context), S_OR(exten, tmpvars.exten), priority);
-
-       /* Masquerade into tmp channel */
-       if (ast_channel_masquerade(tmpchan, chan)) {
-               /* Failed to set up the masquerade.  It's probably chan_local
-                * in the middle of optimizing itself out.  Sad. :( */
-               ast_hangup(tmpchan);
-               tmpchan = NULL;
-               res = -1;
-       } else {
-               ast_do_masquerade(tmpchan);
-               /* Start the PBX going on our stolen channel */
-               if (ast_pbx_start(tmpchan)) {
-                       ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmpchan));
-                       ast_hangup(tmpchan);
-                       res = -1;
-               }
+       ast_explicit_goto(newchan, context, exten, priority);
+       if (ast_pbx_start(newchan)) {
+               ast_hangup(newchan);
+               ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(newchan));
+               return -1;
        }
 
-       return res;
+       return 0;
 }
 
 int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority)
@@ -8788,8 +9669,15 @@ static int add_priority(struct ast_context *con, struct ast_exten *tmp,
 
        for (ep = NULL; e ; ep = e, e = e->peer) {
                if (e->label && tmp->label && e->priority != tmp->priority && !strcmp(e->label, tmp->label)) {
-                       ast_log(LOG_WARNING, "Extension '%s', priority %d in '%s', label '%s' already in use at "
-                                       "priority %d\n", tmp->exten, tmp->priority, con->name, tmp->label, e->priority);
+                       if (strcmp(e->exten, tmp->exten)) {
+                               ast_log(LOG_WARNING,
+                                       "Extension '%s' priority %d in '%s', label '%s' already in use at aliased extension '%s' priority %d\n",
+                                       tmp->exten, tmp->priority, con->name, tmp->label, e->exten, e->priority);
+                       } else {
+                               ast_log(LOG_WARNING,
+                                       "Extension '%s' priority %d in '%s', label '%s' already in use at priority %d\n",
+                                       tmp->exten, tmp->priority, con->name, tmp->label, e->priority);
+                       }
                        repeated_label = 1;
                }
                if (e->priority >= tmp->priority) {
@@ -8814,7 +9702,15 @@ static int add_priority(struct ast_context *con, struct ast_exten *tmp,
                /* Can't have something exactly the same.  Is this a
                   replacement?  If so, replace, otherwise, bonk. */
                if (!replace) {
-                       ast_log(LOG_WARNING, "Unable to register extension '%s', priority %d in '%s', already in use\n", tmp->exten, tmp->priority, con->name);
+                       if (strcmp(e->exten, tmp->exten)) {
+                               ast_log(LOG_WARNING,
+                                       "Unable to register extension '%s' priority %d in '%s', already in use by aliased extension '%s'\n",
+                                       tmp->exten, tmp->priority, con->name, e->exten);
+                       } else {
+                               ast_log(LOG_WARNING,
+                                       "Unable to register extension '%s' priority %d in '%s', already in use\n",
+                                       tmp->exten, tmp->priority, con->name);
+                       }
                        if (tmp->datad) {
                                tmp->datad(tmp->data);
                                /* if you free this, null it out */
@@ -8969,6 +9865,16 @@ int ast_add_extension2(struct ast_context *con,
                application, data, datad, registrar, 1);
 }
 
+int ast_add_extension2_nolock(struct ast_context *con,
+       int replace, const char *extension, int priority, const char *label, const char *callerid,
+       const char *application, void *data, void (*datad)(void *),
+       const char *registrar)
+{
+       return ast_add_extension2_lockopt(con, replace, extension, priority, label, callerid,
+               application, data, datad, registrar, 0);
+}
+
+
 /*!
  * \brief Same as ast_add_extension2() but controls the context locking.
  *
@@ -9002,7 +9908,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
        }
 
        /* If we are adding a hint evalulate in variables and global variables */
-       if (priority == PRIORITY_HINT && strstr(application, "${") && !strstr(extension, "_")) {
+       if (priority == PRIORITY_HINT && strstr(application, "${") && extension[0] != '_') {
                struct ast_channel *c = ast_dummy_channel_alloc();
 
                if (c) {
@@ -9048,10 +9954,10 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
        /* Blank callerid and NULL callerid are two SEPARATE things.  Do NOT confuse the two!!! */
        if (callerid) {
                p += ext_strncpy(p, callerid, strlen(callerid) + 1) + 1;
-               tmp->matchcid = 1;
+               tmp->matchcid = AST_EXT_MATCHCID_ON;
        } else {
                *p++ = '\0';
-               tmp->matchcid = 0;
+               tmp->matchcid = AST_EXT_MATCHCID_OFF;
        }
        tmp->app = p;
        strcpy(p, application);
@@ -9068,7 +9974,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
                                                                an extension, and the trie exists, then we need to incrementally add this pattern to it. */
                ast_copy_string(dummy_name, extension, sizeof(dummy_name));
                dummy_exten.exten = dummy_name;
-               dummy_exten.matchcid = 0;
+               dummy_exten.matchcid = AST_EXT_MATCHCID_OFF;
                dummy_exten.cidmatch = 0;
                tmp2 = ast_hashtab_lookup(con->root_table, &dummy_exten);
                if (!tmp2) {
@@ -9081,11 +9987,11 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
        for (e = con->root; e; el = e, e = e->next) {   /* scan the extension list */
                res = ext_cmp(e->exten, tmp->exten);
                if (res == 0) { /* extension match, now look at cidmatch */
-                       if (!e->matchcid && !tmp->matchcid)
+                       if (e->matchcid == AST_EXT_MATCHCID_OFF && tmp->matchcid == AST_EXT_MATCHCID_OFF)
                                res = 0;
-                       else if (tmp->matchcid && !e->matchcid)
+                       else if (tmp->matchcid == AST_EXT_MATCHCID_ON && e->matchcid == AST_EXT_MATCHCID_OFF)
                                res = 1;
-                       else if (e->matchcid && !tmp->matchcid)
+                       else if (e->matchcid == AST_EXT_MATCHCID_ON && tmp->matchcid == AST_EXT_MATCHCID_OFF)
                                res = -1;
                        else
                                res = ext_cmp(e->cidmatch, tmp->cidmatch);
@@ -9162,7 +10068,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
                }
        }
        if (option_debug) {
-               if (tmp->matchcid) {
+               if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
                        ast_debug(1, "Added extension '%s' priority %d (CID match '%s') to %s (%p)\n",
                                          tmp->exten, tmp->priority, tmp->cidmatch, con->name, con);
                } else {
@@ -9171,7 +10077,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
                }
        }
 
-       if (tmp->matchcid) {
+       if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
                ast_verb(3, "Added extension '%s' priority %d (CID match '%s') to %s\n",
                                 tmp->exten, tmp->priority, tmp->cidmatch, con->name);
        } else {
@@ -9182,461 +10088,405 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
        return 0;
 }
 
-struct async_stat {
-       pthread_t p;
-       struct ast_channel *chan;
+/*! \brief Structure which contains information about an outgoing dial */
+struct pbx_outgoing {
+       /*! \brief Dialing structure being used */
+       struct ast_dial *dial;
+       /*! \brief Condition for synchronous dialing */
+       ast_cond_t cond;
+       /*! \brief Application to execute */
+       char app[AST_MAX_APP];
+       /*! \brief Application data to pass to application */
+       char *appdata;
+       /*! \brief Dialplan context */
        char context[AST_MAX_CONTEXT];
+       /*! \brief Dialplan extension */
        char exten[AST_MAX_EXTENSION];
+       /*! \brief Dialplan priority */
        int priority;
-       int timeout;
-       char app[AST_MAX_EXTENSION];
-       char appdata[1024];
+       /*! \brief Result of the dial operation when dialed is set */
+       int dial_res;
+       /*! \brief Set when dialing is completed */
+       unsigned int dialed:1;
+       /*! \brief Set when execution is completed */
+       unsigned int executed:1;
 };
 
-static void *async_wait(void *data)
+/*! \brief Destructor for outgoing structure */
+static void pbx_outgoing_destroy(void *obj)
 {
-       struct async_stat *as = data;
-       struct ast_channel *chan = as->chan;
-       int timeout = as->timeout;
-       int res;
-       struct ast_frame *f;
-       struct ast_app *app;
+       struct pbx_outgoing *outgoing = obj;
 
-       if (chan) {
-               struct ast_callid *callid = ast_channel_callid(chan);
-               if (callid) {
-                       ast_callid_threadassoc_add(callid);
-                       ast_callid_unref(callid);
-               }
+       if (outgoing->dial) {
+               ast_dial_destroy(outgoing->dial);
        }
 
-       while (timeout && (ast_channel_state(chan) != AST_STATE_UP)) {
-               res = ast_waitfor(chan, timeout);
-               if (res < 1)
-                       break;
-               if (timeout > -1)
-                       timeout = res;
-               f = ast_read(chan);
-               if (!f)
-                       break;
-               if (f->frametype == AST_FRAME_CONTROL) {
-                       if ((f->subclass.integer == AST_CONTROL_BUSY)  ||
-                           (f->subclass.integer == AST_CONTROL_CONGESTION) ) {
-                               ast_frfree(f);
-                               break;
-                       }
-               }
-               ast_frfree(f);
+       ast_cond_destroy(&outgoing->cond);
+
+       ast_free(outgoing->appdata);
+}
+
+/*! \brief Internal function which dials an outgoing leg and sends it to a provided extension or application */
+static void *pbx_outgoing_exec(void *data)
+{
+       RAII_VAR(struct pbx_outgoing *, outgoing, data, ao2_cleanup);
+       enum ast_dial_result res;
+
+       /* Notify anyone interested that dialing is complete */
+       res = ast_dial_run(outgoing->dial, NULL, 0);
+       ao2_lock(outgoing);
+       outgoing->dial_res = res;
+       outgoing->dialed = 1;
+       ast_cond_signal(&outgoing->cond);
+       ao2_unlock(outgoing);
+
+       /* If the outgoing leg was not answered we can immediately return and go no further */
+       if (res != AST_DIAL_RESULT_ANSWERED) {
+               return NULL;
        }
-       if (ast_channel_state(chan) == AST_STATE_UP) {
-               if (!ast_strlen_zero(as->app)) {
-                       app = pbx_findapp(as->app);
-                       if (app) {
-                               ast_verb(3, "Launching %s(%s) on %s\n", as->app, as->appdata, ast_channel_name(chan));
-                               pbx_exec(chan, app, as->appdata);
-                       } else
-                               ast_log(LOG_WARNING, "No such application '%s'\n", as->app);
+
+       if (!ast_strlen_zero(outgoing->app)) {
+               struct ast_app *app = pbx_findapp(outgoing->app);
+
+               if (app) {
+                       ast_verb(4, "Launching %s(%s) on %s\n", outgoing->app, outgoing->appdata,
+                               ast_channel_name(ast_dial_answered(outgoing->dial)));
+                       pbx_exec(ast_dial_answered(outgoing->dial), app, outgoing->appdata);
                } else {
-                       if (!ast_strlen_zero(as->context))
-                               ast_channel_context_set(chan, as->context);
-                       if (!ast_strlen_zero(as->exten))
-                               ast_channel_exten_set(chan, as->exten);
-                       if (as->priority > 0)
-                               ast_channel_priority_set(chan, as->priority);
-                       /* Run the PBX */
-                       if (ast_pbx_run(chan)) {
-                               ast_log(LOG_ERROR, "Failed to start PBX on %s\n", ast_channel_name(chan));
-                       } else {
-                               /* PBX will have taken care of this */
-                               chan = NULL;
-                       }
+                       ast_log(LOG_WARNING, "No such application '%s'\n", outgoing->app);
+               }
+       } else {
+               struct ast_channel *answered = ast_dial_answered(outgoing->dial);
+
+               if (!ast_strlen_zero(outgoing->context)) {
+                       ast_channel_context_set(answered, outgoing->context);
+               }
+
+               if (!ast_strlen_zero(outgoing->exten)) {
+                       ast_channel_exten_set(answered, outgoing->exten);
+               }
+
+               if (outgoing->priority > 0) {
+                       ast_channel_priority_set(answered, outgoing->priority);
+               }
+
+               if (ast_pbx_run(answered)) {
+                       ast_log(LOG_ERROR, "Failed to start PBX on %s\n", ast_channel_name(answered));
+               } else {
+                       /* PBX will have taken care of hanging up, so we steal the answered channel so dial doesn't do it */
+                       ast_dial_answered_steal(outgoing->dial);
                }
        }
-       ast_free(as);
-       if (chan)
-               ast_hangup(chan);
+
+       /* Notify anyone else again that may be interested that execution is complete */
+       ao2_lock(outgoing);
+       outgoing->executed = 1;
+       ast_cond_signal(&outgoing->cond);
+       ao2_unlock(outgoing);
+
        return NULL;
 }
 
-/*!
- * \brief Function to post an empty cdr after a spool call fails.
- * \note This function posts an empty cdr for a failed spool call
-*/
-static int ast_pbx_outgoing_cdr_failed(void)
+/*! \brief Internal dialing state callback which causes early media to trigger an answer */
+static void pbx_outgoing_state_callback(struct ast_dial *dial)
 {
-       /* allocate a channel */
-       struct ast_channel *chan = ast_dummy_channel_alloc();
+       struct ast_channel *channel;
 
-       if (!chan)
-               return -1;  /* failure */
+       if (ast_dial_state(dial) != AST_DIAL_RESULT_PROGRESS) {
+               return;
+       }
 
-       ast_channel_cdr_set(chan, ast_cdr_alloc());
-       if (!ast_channel_cdr(chan)) {
-               /* allocation of the cdr failed */
-               chan = ast_channel_unref(chan);   /* free the channel */
-               return -1;                /* return failure */
+       if (!(channel = ast_dial_get_channel(dial, 0))) {
+               return;
        }
 
-       /* allocation of the cdr was successful */
-       ast_cdr_init(ast_channel_cdr(chan), chan);  /* initialize our channel's cdr */
-       ast_cdr_start(ast_channel_cdr(chan));       /* record the start and stop time */
-       ast_cdr_end(ast_channel_cdr(chan));
-       ast_cdr_failed(ast_channel_cdr(chan));      /* set the status to failed */
-       ast_cdr_detach(ast_channel_cdr(chan));      /* post and free the record */
-       ast_channel_cdr_set(chan, NULL);
-       chan = ast_channel_unref(chan);         /* free the channel */
+       ast_verb(4, "Treating progress as answer on '%s' due to early media option\n",
+               ast_channel_name(channel));
 
-       return 0;  /* success */
+       ast_queue_control(channel, AST_CONTROL_ANSWER);
 }
 
-int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *context, const char *exten, int priority, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **channel)
+/*!
+ * \brief Attempt to convert disconnect cause to old originate reason.
+ *
+ * \todo XXX The old originate reasons need to be trashed and replaced
+ * with normal disconnect cause codes if the call was not answered.
+ * The internal consumers of the reason values would also need to be
+ * updated: app_originate, call files, and AMI OriginateResponse.
+ */
+static enum ast_control_frame_type pbx_dial_reason(enum ast_dial_result dial_result, int cause)
 {
-       struct ast_channel *chan;
-       struct async_stat *as;
-       struct ast_callid *callid;
-       int callid_created = 0;
-       int res = -1, cdr_res = -1;
-       struct outgoing_helper oh;
+       enum ast_control_frame_type pbx_reason;
 
-       callid_created = ast_callid_threadstorage_auto(&callid);
-
-       if (synchronous) {
-               oh.context = context;
-               oh.exten = exten;
-               oh.priority = priority;
-               oh.cid_num = cid_num;
-               oh.cid_name = cid_name;
-               oh.account = account;
-               oh.vars = vars;
-               oh.parent_channel = NULL;
-
-               chan = __ast_request_and_dial(type, cap, NULL, addr, timeout, reason, cid_num, cid_name, &oh);
-               if (channel) {
-                       *channel = chan;
-                       if (chan)
-                               ast_channel_lock(chan);
+       if (dial_result == AST_DIAL_RESULT_ANSWERED) {
+               /* Remote end answered. */
+               pbx_reason = AST_CONTROL_ANSWER;
+       } else if (dial_result == AST_DIAL_RESULT_HANGUP) {
+               /* Caller hungup */
+               pbx_reason = AST_CONTROL_HANGUP;
+       } else {
+               switch (cause) {
+               case AST_CAUSE_USER_BUSY:
+                       pbx_reason = AST_CONTROL_BUSY;
+                       break;
+               case AST_CAUSE_CALL_REJECTED:
+               case AST_CAUSE_NETWORK_OUT_OF_ORDER:
+               case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
+               case AST_CAUSE_NORMAL_TEMPORARY_FAILURE:
+               case AST_CAUSE_SWITCH_CONGESTION:
+               case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+                       pbx_reason = AST_CONTROL_CONGESTION;
+                       break;
+               case AST_CAUSE_ANSWERED_ELSEWHERE:
+               case AST_CAUSE_NO_ANSWER:
+                       /* Remote end was ringing (but isn't anymore) */
+                       pbx_reason = AST_CONTROL_RINGING;
+                       break;
+               case AST_CAUSE_UNALLOCATED:
+               default:
+                       /* Call Failure (not BUSY, and not NO_ANSWER, maybe Circuit busy or down?) */
+                       pbx_reason = 0;
+                       break;
                }
-               if (chan) {
-                       /* Bind the callid to the channel if it doesn't already have one on creation */
-                       struct ast_callid *channel_callid = ast_channel_callid(chan);
-                       if (channel_callid) {
-                               ast_callid_unref(channel_callid);
-                       } else {
-                               if (callid) {
-                                       ast_channel_callid_set(chan, callid);
-                               }
-                       }
-
-                       if (ast_channel_state(chan) == AST_STATE_UP) {
-                                       res = 0;
-                               ast_verb(4, "Channel %s was answered.\n", ast_channel_name(chan));
-
-                               if (synchronous > 1) {
-                                       if (channel)
-                                               ast_channel_unlock(chan);
-                                       if (ast_pbx_run(chan)) {
-                                               ast_log(LOG_ERROR, "Unable to run PBX on %s\n", ast_channel_name(chan));
-                                               if (channel)
-                                                       *channel = NULL;
-                                               ast_hangup(chan);
-                                               chan = NULL;
-                                               res = -1;
-                                       }
-                               } else {
-                                       if (ast_pbx_start(chan)) {
-                                               ast_log(LOG_ERROR, "Unable to start PBX on %s\n", ast_channel_name(chan));
-                                               if (channel) {
-                                                       *channel = NULL;
-                                                       ast_channel_unlock(chan);
-                                               }
-                                               ast_hangup(chan);
-                                               res = -1;
-                                       }
-                                       chan = NULL;
-                               }
-                       } else {
-                               ast_verb(4, "Channel %s was never answered.\n", ast_channel_name(chan));
+       }
 
-                               if (ast_channel_cdr(chan)) { /* update the cdr */
-                                       /* here we update the status of the call, which sould be busy.
-                                        * if that fails then we set the status to failed */
-                                       if (ast_cdr_disposition(ast_channel_cdr(chan), ast_channel_hangupcause(chan)))
-                                               ast_cdr_failed(ast_channel_cdr(chan));
-                               }
+       return pbx_reason;
+}
 
-                               if (channel) {
-                                       *channel = NULL;
-                                       ast_channel_unlock(chan);
-                               }
-                               ast_hangup(chan);
-                               chan = NULL;
-                       }
-               }
+static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap,
+       const char *addr, int timeout, const char *context, const char *exten, int priority,
+       const char *app, const char *appdata, int *reason, int synchronous,
+       const char *cid_num, const char *cid_name, struct ast_variable *vars,
+       const char *account, struct ast_channel **locked_channel, int early_media,
+       const struct ast_assigned_ids *assignedids)
+{
+       RAII_VAR(struct pbx_outgoing *, outgoing, NULL, ao2_cleanup);
+       struct ast_channel *dialed;
+       pthread_t thread;
 
-               if (res < 0) { /* the call failed for some reason */
-                       if (*reason == 0) { /* if the call failed (not busy or no answer)
-                                           * update the cdr with the failed message */
-                               cdr_res = ast_pbx_outgoing_cdr_failed();
-                               if (cdr_res != 0) {
-                                       res = cdr_res;
-                                       goto outgoing_exten_cleanup;
-                               }
-                       }
+       outgoing = ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy);
+       if (!outgoing) {
+               return -1;
+       }
+       ast_cond_init(&outgoing->cond, NULL);
 
-                       /* create a fake channel and execute the "failed" extension (if it exists) within the requested context */
-                       /* check if "failed" exists */
-                       if (ast_exists_extension(chan, context, "failed", 1, NULL)) {
-                               chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", NULL, 0, "OutgoingSpoolFailed");
-                               if (chan) {
-                                       char failed_reason[4] = "";
-                                       if (!ast_strlen_zero(context))
-                                               ast_channel_context_set(chan, context);
-                                       set_ext_pri(chan, "failed", 1);
-                                       ast_set_variables(chan, vars);
-                                       snprintf(failed_reason, sizeof(failed_reason), "%d", *reason);
-                                       pbx_builtin_setvar_helper(chan, "REASON", failed_reason);
-                                       if (account)
-                                               ast_cdr_setaccount(chan, account);
-                                       if (ast_pbx_run(chan)) {
-                                               ast_log(LOG_ERROR, "Unable to run PBX on %s\n", ast_channel_name(chan));
-                                               ast_hangup(chan);
-                                       }
-                                       chan = NULL;
-                               }
-                       }
-               }
+       if (!ast_strlen_zero(app)) {
+               ast_copy_string(outgoing->app, app, sizeof(outgoing->app));
+               outgoing->appdata = ast_strdup(appdata);
        } else {
-               struct ast_callid *channel_callid;
-               if (!(as = ast_calloc(1, sizeof(*as)))) {
-                       res = -1;
-                       goto outgoing_exten_cleanup;
-               }
-               chan = ast_request_and_dial(type, cap, NULL, addr, timeout, reason, cid_num, cid_name);
-               if (channel) {
-                       *channel = chan;
-                       if (chan)
-                               ast_channel_lock(chan);
-               }
-               if (!chan) {
-                       ast_free(as);
-                       res = -1;
-                       goto outgoing_exten_cleanup;
-               }
+               ast_copy_string(outgoing->context, context, sizeof(outgoing->context));
+               ast_copy_string(outgoing->exten, exten, sizeof(outgoing->exten));
+               outgoing->priority = priority;
+       }
 
-               /* Bind the newly created callid to the channel if it doesn't already have one on creation. */
-               channel_callid = ast_channel_callid(chan);
-               if (channel_callid) {
-                       ast_callid_unref(channel_callid);
-               } else {
-                       if (callid) {
-                               ast_channel_callid_set(chan, callid);
-                       }
-               }
-
-               as->chan = chan;
-               ast_copy_string(as->context, context, sizeof(as->context));
-               set_ext_pri(as->chan,  exten, priority);
-               as->timeout = timeout;
-               ast_set_variables(chan, vars);
-               if (account)
-                       ast_cdr_setaccount(chan, account);
-               if (ast_pthread_create_detached(&as->p, NULL, async_wait, as)) {
-                       ast_log(LOG_WARNING, "Failed to start async wait\n");
-                       ast_free(as);
-                       if (channel) {
-                               *channel = NULL;
-                               ast_channel_unlock(chan);
-                       }
-                       ast_hangup(chan);
-                       res = -1;
-                       goto outgoing_exten_cleanup;
+       if (!(outgoing->dial = ast_dial_create())) {
+               return -1;
+       }
+
+       if (ast_dial_append(outgoing->dial, type, addr, assignedids)) {
+               return -1;
+       }
+
+       ast_dial_set_global_timeout(outgoing->dial, timeout);
+
+       if (ast_dial_prerun(outgoing->dial, NULL, cap)) {
+               if (synchronous && reason) {
+                       *reason = pbx_dial_reason(AST_DIAL_RESULT_FAILED,
+                               ast_dial_reason(outgoing->dial, 0));
                }
-               res = 0;
+               return -1;
        }
 
-outgoing_exten_cleanup:
-       ast_callid_threadstorage_auto_clean(callid, callid_created);
-       ast_variables_destroy(vars);
-       return res;
-}
+       dialed = ast_dial_get_channel(outgoing->dial, 0);
+       if (!dialed) {
+               return -1;
+       }
 
-struct app_tmp {
-       struct ast_channel *chan;
-       pthread_t t;
-       AST_DECLARE_STRING_FIELDS (
-               AST_STRING_FIELD(app);
-               AST_STRING_FIELD(data);
-       );
-};
+       ast_channel_lock(dialed);
+       if (vars) {
+               ast_set_variables(dialed, vars);
+       }
+       if (account) {
+               ast_channel_accountcode_set(dialed, account);
+       }
+       ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
+       ast_channel_unlock(dialed);
 
-/*! \brief run the application and free the descriptor once done */
-static void *ast_pbx_run_app(void *data)
-{
-       struct app_tmp *tmp = data;
-       struct ast_app *app;
-       app = pbx_findapp(tmp->app);
-       if (app) {
-               ast_verb(4, "Launching %s(%s) on %s\n", tmp->app, tmp->data, ast_channel_name(tmp->chan));
-               pbx_exec(tmp->chan, app, tmp->data);
-       } else
-               ast_log(LOG_WARNING, "No such application '%s'\n", tmp->app);
-       ast_hangup(tmp->chan);
-       ast_string_field_free_memory(tmp);
-       ast_free(tmp);
-       return NULL;
-}
+       if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
+               struct ast_party_connected_line connected;
 
-int ast_pbx_outgoing_app(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *app, const char *appdata, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel)
-{
-       struct ast_channel *chan;
-       struct app_tmp *tmp;
-       struct ast_callid *callid;
-       int callid_created;
-       int res = -1, cdr_res = -1;
-       struct outgoing_helper oh;
+               /*
+                * It seems strange to set the CallerID on an outgoing call leg
+                * to whom we are calling, but this function's callers are doing
+                * various Originate methods.  This call leg goes to the local
+                * user.  Once the called party answers, the dialplan needs to
+                * be able to access the CallerID from the CALLERID function as
+                * if the called party had placed this call.
+                */
+               ast_set_callerid(dialed, cid_num, cid_name, cid_num);
 
-       /* Start by checking for a callid in threadstorage, and if none is found, bind one. */
-       callid_created = ast_callid_threadstorage_auto(&callid);
+               ast_party_connected_line_set_init(&connected, ast_channel_connected(dialed));
+               if (!ast_strlen_zero(cid_num)) {
+                       connected.id.number.valid = 1;
+                       connected.id.number.str = (char *) cid_num;
+                       connected.id.number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+               }
+               if (!ast_strlen_zero(cid_name)) {
+                       connected.id.name.valid = 1;
+                       connected.id.name.str = (char *) cid_name;
+                       connected.id.name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+               }
+               ast_channel_set_connected_line(dialed, &connected, NULL);
+       }
 
-       memset(&oh, 0, sizeof(oh));
-       oh.vars = vars;
-       oh.account = account;
+       if (early_media) {
+               ast_dial_set_state_callback(outgoing->dial, pbx_outgoing_state_callback);
+       }
 
-       if (locked_channel)
-               *locked_channel = NULL;
-       if (ast_strlen_zero(app)) {
-               res = -1;
-               goto outgoing_app_cleanup;
+       if (locked_channel) {
+               /*
+                * Keep a dialed channel ref since the caller wants
+                * the channel returned.  We must get the ref before
+                * spawning off pbx_outgoing_exec().
+                */
+               ast_channel_ref(dialed);
+               if (!synchronous) {
+                       /*
+                        * Lock it now to hold off pbx_outgoing_exec() in case the
+                        * calling function needs the channel state/snapshot before
+                        * dialing actually happens.
+                        */
+                       ast_channel_lock(dialed);
+               }
        }
-       if (synchronous) {
-               chan = __ast_request_and_dial(type, cap, NULL, addr, timeout, reason, cid_num, cid_name, &oh);
-               if (chan) {
-                       /* Bind the newly created callid to the channel if it doesn't already have one on creation */
-                       struct ast_callid *channel_callid = ast_channel_callid(chan);
-                       if (channel_callid) {
-                               ast_callid_unref(channel_callid);
-                       } else {
-                               if (callid) {
-                                       ast_channel_callid_set(chan, callid);
-                               }
-                       }
 
-                       ast_set_variables(chan, vars);
-                       if (account)
-                               ast_cdr_setaccount(chan, account);
-                       if (ast_channel_state(chan) == AST_STATE_UP) {
-                               res = 0;
-                               ast_verb(4, "Channel %s was answered.\n", ast_channel_name(chan));
-                               tmp = ast_calloc(1, sizeof(*tmp));
-                               if (!tmp || ast_string_field_init(tmp, 252)) {
-                                       if (tmp) {
-                                               ast_free(tmp);
-                                       }
-                                       res = -1;
-                               } else {
-                                       ast_string_field_set(tmp, app, app);
-                                       ast_string_field_set(tmp, data, appdata);
-                                       tmp->chan = chan;
-                                       if (synchronous > 1) {
-                                               if (locked_channel)
-                                                       ast_channel_unlock(chan);
-                                               ast_pbx_run_app(tmp);
-                                       } else {
-                                               if (locked_channel)
-                                                       ast_channel_lock(chan);
-                                               if (ast_pthread_create_detached(&tmp->t, NULL, ast_pbx_run_app, tmp)) {
-                                                       ast_log(LOG_WARNING, "Unable to spawn execute thread on %s: %s\n", ast_channel_name(chan), strerror(errno));
-                                                       ast_string_field_free_memory(tmp);
-                                                       ast_free(tmp);
-                                                       if (locked_channel)
-                                                               ast_channel_unlock(chan);
-                                                       ast_hangup(chan);
-                                                       res = -1;
-                                               } else {
-                                                       if (locked_channel)
-                                                               *locked_channel = chan;
-                                               }
-                                       }
-                               }
-                       } else {
-                               ast_verb(4, "Channel %s was never answered.\n", ast_channel_name(chan));
-                               if (ast_channel_cdr(chan)) { /* update the cdr */
-                                       /* here we update the status of the call, which sould be busy.
-                                        * if that fails then we set the status to failed */
-                                       if (ast_cdr_disposition(ast_channel_cdr(chan), ast_channel_hangupcause(chan)))
-                                               ast_cdr_failed(ast_channel_cdr(chan));
-                               }
-                               ast_hangup(chan);
+       ao2_ref(outgoing, +1);
+       if (ast_pthread_create_detached(&thread, NULL, pbx_outgoing_exec, outgoing)) {
+               ast_log(LOG_WARNING, "Unable to spawn dialing thread for '%s/%s'\n", type, addr);
+               ao2_ref(outgoing, -1);
+               if (locked_channel) {
+                       if (!synchronous) {
+                               ast_channel_unlock(dialed);
                        }
+                       ast_channel_unref(dialed);
                }
+               return -1;
+       }
 
-               if (res < 0) { /* the call failed for some reason */
-                       if (*reason == 0) { /* if the call failed (not busy or no answer)
-                                           * update the cdr with the failed message */
-                               cdr_res = ast_pbx_outgoing_cdr_failed();
-                               if (cdr_res != 0) {
-                                       res = cdr_res;
-                                       goto outgoing_app_cleanup;
-                               }
+       if (synchronous) {
+               ao2_lock(outgoing);
+               /* Wait for dialing to complete */
+               while (!outgoing->dialed) {
+                       ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
+               }
+               if (1 < synchronous
+                       && outgoing->dial_res == AST_DIAL_RESULT_ANSWERED) {
+                       /* Wait for execution to complete */
+                       while (!outgoing->executed) {
+                               ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
                        }
                }
+               ao2_unlock(outgoing);
 
-       } else {
-               struct async_stat *as;
-               struct ast_callid *channel_callid;
-               if (!(as = ast_calloc(1, sizeof(*as)))) {
-                       res = -1;
-                       goto outgoing_app_cleanup;
-               }
-               chan = __ast_request_and_dial(type, cap, NULL, addr, timeout, reason, cid_num, cid_name, &oh);
-               if (!chan) {
-                       ast_free(as);
-                       res = -1;
-                       goto outgoing_app_cleanup;
+               /* Determine the outcome of the dialing attempt up to it being answered. */
+               if (reason) {
+                       *reason = pbx_dial_reason(outgoing->dial_res,
+                               ast_dial_reason(outgoing->dial, 0));
                }
 
-               /* Bind the newly created callid to the channel if it doesn't already have one on creation. */
-               channel_callid = ast_channel_callid(chan);
-               if (channel_callid) {
-                       ast_callid_unref(channel_callid);
-               } else {
-                       if (callid) {
-                               ast_channel_callid_set(chan, callid);
+               if (outgoing->dial_res != AST_DIAL_RESULT_ANSWERED) {
+                       /* The dial operation failed. */
+                       if (locked_channel) {
+                               ast_channel_unref(dialed);
                        }
+                       return -1;
                }
+               if (locked_channel) {
+                       ast_channel_lock(dialed);
+               }
+       }
 
-               as->chan = chan;
-               ast_copy_string(as->app, app, sizeof(as->app));
-               if (appdata)
-                       ast_copy_string(as->appdata,  appdata, sizeof(as->appdata));
-               as->timeout = timeout;
-               ast_set_variables(chan, vars);
-               if (account)
-                       ast_cdr_setaccount(chan, account);
-               /* Start a new thread, and get something handling this channel. */
-               if (locked_channel)
-                       ast_channel_lock(chan);
-               if (ast_pthread_create_detached(&as->p, NULL, async_wait, as)) {
-                       ast_log(LOG_WARNING, "Failed to start async wait\n");
-                       ast_free(as);
-                       if (locked_channel)
-                               ast_channel_unlock(chan);
-                       ast_hangup(chan);
-                       res = -1;
-                       goto outgoing_app_cleanup;
-               } else {
-                       if (locked_channel)
-                               *locked_channel = chan;
+       if (locked_channel) {
+               *locked_channel = dialed;
+       }
+       return 0;
+}
+
+int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr,
+       int timeout, const char *context, const char *exten, int priority, int *reason,
+       int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars,
+       const char *account, struct ast_channel **locked_channel, int early_media,
+       const struct ast_assigned_ids *assignedids)
+{
+       int res;
+       int my_reason;
+
+       if (!reason) {
+               reason = &my_reason;
+       }
+       *reason = 0;
+       if (locked_channel) {
+               *locked_channel = NULL;
+       }
+
+       res = pbx_outgoing_attempt(type, cap, addr, timeout, context, exten, priority,
+               NULL, NULL, reason, synchronous, cid_num, cid_name, vars, account, locked_channel,
+               early_media, assignedids);
+
+       if (res < 0 /* Call failed to get connected for some reason. */
+               && 1 < synchronous
+               && ast_exists_extension(NULL, context, "failed", 1, NULL)) {
+               struct ast_channel *failed;
+
+               /* We do not have to worry about a locked_channel if dialing failed. */
+               ast_assert(!locked_channel || !*locked_channel);
+
+               /*!
+                * \todo XXX Not good.  The channel name is not unique if more than
+                * one originate fails at a time.
+                */
+               failed = ast_channel_alloc(0, AST_STATE_DOWN, cid_num, cid_name, account,
+                       "failed", context, NULL, NULL, 0, "OutgoingSpoolFailed");
+               if (failed) {
+                       char failed_reason[12];
+
+                       ast_set_variables(failed, vars);
+                       snprintf(failed_reason, sizeof(failed_reason), "%d", *reason);
+                       pbx_builtin_setvar_helper(failed, "REASON", failed_reason);
+                       ast_channel_unlock(failed);
+
+                       if (ast_pbx_run(failed)) {
+                               ast_log(LOG_ERROR, "Unable to run PBX on '%s'\n",
+                                       ast_channel_name(failed));
+                               ast_hangup(failed);
+                       }
                }
-               res = 0;
        }
 
-outgoing_app_cleanup:
-       ast_callid_threadstorage_auto_clean(callid, callid_created);
-       ast_variables_destroy(vars);
        return res;
 }
 
+int ast_pbx_outgoing_app(const char *type, struct ast_format_cap *cap, const char *addr,
+       int timeout, const char *app, const char *appdata, int *reason, int synchronous,
+       const char *cid_num, const char *cid_name, struct ast_variable *vars,
+       const char *account, struct ast_channel **locked_channel,
+       const struct ast_assigned_ids *assignedids)
+{
+       if (reason) {
+               *reason = 0;
+       }
+       if (locked_channel) {
+               *locked_channel = NULL;
+       }
+       if (ast_strlen_zero(app)) {
+               return -1;
+       }
+
+       return pbx_outgoing_attempt(type, cap, addr, timeout, NULL, NULL, 0, app, appdata,
+               reason, synchronous, cid_num, cid_name, vars, account, locked_channel, 0,
+               assignedids);
+}
+
 /* this is the guts of destroying a context --
    freeing up the structure, traversing and destroying the
    extensions, switches, ignorepats, includes, etc. etc. */
@@ -9684,6 +10534,7 @@ static void __ast_internal_context_destroy( struct ast_context *con)
        }
        tmp->root = NULL;
        ast_rwlock_destroy(&tmp->lock);
+       ast_mutex_destroy(&tmp->macrolock);
        ast_free(tmp);
 }
 
@@ -9778,12 +10629,11 @@ void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *context
                                                }
                                                ast_verb(3, "Remove %s/%s/%d, registrar=%s; con=%s(%p); con->root=%p\n",
                                                                 tmp->name, prio_item->exten, prio_item->priority, registrar, con? con->name : "<nil>", con, con? con->root_table: NULL);
-                                               /* set matchcid to 1 to insure we get a direct match, and NULL registrar to make sure no wildcarding is done */
                                                ast_copy_string(extension, prio_item->exten, sizeof(extension));
                                                if (prio_item->cidmatch) {
                                                        ast_copy_string(cidmatch, prio_item->cidmatch, sizeof(cidmatch));
                                                }
-                                               end_traversal &= ast_context_remove_extension_callerid2(tmp, extension, prio_item->priority, prio_item->cidmatch ? cidmatch : NULL, 1, NULL, 1);
+                                               end_traversal &= ast_context_remove_extension_callerid2(tmp, extension, prio_item->priority, cidmatch, prio_item->matchcid, NULL, 1);
                                        }
                                        /* Explanation:
                                         * ast_context_remove_extension_callerid2 will destroy the extension that it comes across. This
@@ -9910,10 +10760,12 @@ static int pbx_builtin_busy(struct ast_channel *chan, const char *data)
        ast_indicate(chan, AST_CONTROL_BUSY);
        /* Don't change state of an UP channel, just indicate
           busy in audio */
+       ast_channel_lock(chan);
        if (ast_channel_state(chan) != AST_STATE_UP) {
+               ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY);
                ast_setstate(chan, AST_STATE_BUSY);
-               ast_cdr_busy(ast_channel_cdr(chan));
        }
+       ast_channel_unlock(chan);
        wait_for_hangup(chan, data);
        return -1;
 }
@@ -9926,10 +10778,12 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
        ast_indicate(chan, AST_CONTROL_CONGESTION);
        /* Don't change state of an UP channel, just indicate
           congestion in audio */
+       ast_channel_lock(chan);
        if (ast_channel_state(chan) != AST_STATE_UP) {
+               ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION);
                ast_setstate(chan, AST_STATE_BUSY);
-               ast_cdr_congestion(ast_channel_cdr(chan));
        }
+       ast_channel_unlock(chan);
        wait_for_hangup(chan, data);
        return -1;
 }
@@ -9940,7 +10794,6 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
 static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
 {
        int delay = 0;
-       int answer_cdr = 1;
        char *parse;
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(delay);
@@ -9948,7 +10801,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
        );
 
        if (ast_strlen_zero(data)) {
-               return __ast_answer(chan, 0, 1);
+               return __ast_answer(chan, 0);
        }
 
        parse = ast_strdupa(data);
@@ -9963,10 +10816,10 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
        }
 
        if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
-               answer_cdr = 0;
+               ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n");
        }
 
-       return __ast_answer(chan, delay, answer_cdr);
+       return __ast_answer(chan, delay);
 }
 
 static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
@@ -9983,7 +10836,7 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
        if (ast_check_hangup(chan)) {
                return -1;
        } else if (ast_channel_state(chan) != AST_STATE_UP && answer) {
-               __ast_answer(chan, 0, 1);
+               __ast_answer(chan, 0);
        }
 
        ast_indicate(chan, AST_CONTROL_INCOMPLETE);
@@ -9991,39 +10844,30 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
        return AST_PBX_INCOMPLETE;
 }
 
-AST_APP_OPTIONS(resetcdr_opts, {
-       AST_APP_OPTION('w', AST_CDR_FLAG_POSTED),
-       AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED),
-       AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
-       AST_APP_OPTION('e', AST_CDR_FLAG_POST_ENABLE),
-});
-
 /*!
  * \ingroup applications
  */
-static int pbx_builtin_resetcdr(struct ast_channel *chan, const char *data)
+static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
 {
-       char *args;
-       struct ast_flags flags = { 0 };
+       ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n");
 
-       if (!ast_strlen_zero(data)) {
-               args = ast_strdupa(data);
-               ast_app_parse_options(resetcdr_opts, &flags, NULL, args);
+       if (ast_strlen_zero(data)) {
+               ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n");
+               return 0;
        }
-
-       ast_cdr_reset(ast_channel_cdr(chan), &flags);
-
-       return 0;
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
-{
        /* Copy the AMA Flags as specified */
        ast_channel_lock(chan);
-       ast_cdr_setamaflags(chan, data ? data : "");
+       if (isdigit(data[0])) {
+               int amaflags;
+               if (sscanf(data, "%30d", &amaflags) != 1) {
+                       ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan));
+                       ast_channel_unlock(chan);
+                       return 0;
+               }
+               ast_channel_amaflags_set(chan, amaflags);
+       } else {
+               ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data));
+       }
        ast_channel_unlock(chan);
        return 0;
 }
@@ -10512,10 +11356,9 @@ void pbx_builtin_pushvar_helper(struct ast_channel *chan, const char *name, cons
                headp = &globals;
        }
 
-       if (value) {
+       if (value && (newvariable = ast_var_assign(name, value))) {
                if (headp == &globals)
                        ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
-               newvariable = ast_var_assign(name, value);
                AST_LIST_INSERT_HEAD(headp, newvariable, entries);
        }
 
@@ -10562,18 +11405,15 @@ int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const
        }
        AST_LIST_TRAVERSE_SAFE_END;
 
-       if (value) {
+       if (value && (newvariable = ast_var_assign(name, value))) {
                if (headp == &globals)
                        ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
-               newvariable = ast_var_assign(name, value);
                AST_LIST_INSERT_HEAD(headp, newvariable, entries);
-               manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
-                       "Channel: %s\r\n"
-                       "Variable: %s\r\n"
-                       "Value: %s\r\n"
-                       "Uniqueid: %s\r\n",
-                       chan ? ast_channel_name(chan) : "none", name, value,
-                       chan ? ast_channel_uniqueid(chan) : "none");
+               ast_channel_publish_varset(chan, name, value);
+
+               if (headp != &globals) {
+                       ast_channel_publish_snapshot(chan);
+               }
        }
 
        if (chan)
@@ -10673,11 +11513,9 @@ int pbx_builtin_importvar(struct ast_channel *chan, const char *data)
        if (channel && value && name) { /*! \todo XXX should do !ast_strlen_zero(..) of the args ? */
                struct ast_channel *chan2 = ast_channel_get_by_name(channel);
                if (chan2) {
-                       char *s = alloca(strlen(value) + 4);
-                       if (s) {
-                               sprintf(s, "${%s}", value);
-                               pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1);
-                       }
+                       char *s = ast_alloca(strlen(value) + 4);
+                       sprintf(s, "${%s}", value);
+                       pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1);
                        chan2 = ast_channel_unref(chan2);
                }
                pbx_builtin_setvar_helper(chan, name, tmp);
@@ -10741,7 +11579,18 @@ static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data)
 {
        char tmp[256];
        char *number = tmp;
+       int number_val;
        char *options;
+       int res;
+       int interrupt = 0;
+       const char *interrupt_string;
+
+       ast_channel_lock(chan);
+       interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+       if (ast_true(interrupt_string)) {
+               interrupt = 1;
+       }
+       ast_channel_unlock(chan);
 
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n");
@@ -10749,6 +11598,12 @@ static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data)
        }
        ast_copy_string(tmp, data, sizeof(tmp));
        strsep(&number, ",");
+
+       if (sscanf(tmp, "%d", &number_val) != 1) {
+               ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp);
+               return 0;
+       }
+
        options = strsep(&number, ",");
        if (options) {
                if ( strcasecmp(options, "f") && strcasecmp(options, "m") &&
@@ -10758,102 +11613,235 @@ static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data)
                }
        }
 
-       if (ast_say_number(chan, atoi(tmp), "", ast_channel_language(chan), options)) {
+       res = ast_say_number(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options);
+
+       if (res < 0) {
                ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp);
        }
 
-       return 0;
+       return interrupt ? res : 0;
 }
 
 static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data)
 {
        int res = 0;
+       int interrupt = 0;
+       const char *interrupt_string;
+
+       ast_channel_lock(chan);
+       interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+       if (ast_true(interrupt_string)) {
+               interrupt = 1;
+       }
+       ast_channel_unlock(chan);
+
+       if (data) {
+               res = ast_say_digit_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+       }
 
-       if (data)
-               res = ast_say_digit_str(chan, data, "", ast_channel_language(chan));
        return res;
 }
 
-static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data)
+static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data)
 {
        int res = 0;
+       int sensitivity = 0;
+       char *parse;
+       int interrupt = 0;
+       const char *interrupt_string;
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(options);
+               AST_APP_ARG(characters);
+       );
+
+       ast_channel_lock(chan);
+       interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+       if (ast_true(interrupt_string)) {
+               interrupt = 1;
+       }
+       ast_channel_unlock(chan);
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "SayAlphaCase requires two arguments (options, characters)\n");
+               return 0;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (!args.options || strlen(args.options) != 1) {
+               ast_log(LOG_WARNING, "SayAlphaCase options are mutually exclusive and required\n");
+               return 0;
+       }
+
+       switch (args.options[0]) {
+       case 'a':
+               sensitivity = AST_SAY_CASE_ALL;
+               break;
+       case 'l':
+               sensitivity = AST_SAY_CASE_LOWER;
+               break;
+       case 'n':
+               sensitivity = AST_SAY_CASE_NONE;
+               break;
+       case 'u':
+               sensitivity = AST_SAY_CASE_UPPER;
+               break;
+       default:
+               ast_log(LOG_WARNING, "Invalid option: '%s'\n", args.options);
+               return 0;
+       }
+
+       res = ast_say_character_str(chan, args.characters, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), sensitivity);
 
-       if (data)
-               res = ast_say_character_str(chan, data, "", ast_channel_language(chan));
        return res;
 }
 
-static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
+static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data)
 {
        int res = 0;
+       int interrupt = 0;
+       const char *interrupt_string;
+
+       ast_channel_lock(chan);
+       interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+       if (ast_true(interrupt_string)) {
+               interrupt = 1;
+       }
+       ast_channel_unlock(chan);
+
+       if (data) {
+               res = ast_say_character_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), AST_SAY_CASE_NONE);
+       }
 
-       if (data)
-               res = ast_say_phonetic_str(chan, data, "", ast_channel_language(chan));
        return res;
 }
 
-static void presencechange_destroy(void *data)
+static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
 {
-       struct presencechange *pc = data;
-       ast_free(pc->provider);
-       ast_free(pc->subtype);
-       ast_free(pc->message);
+       int res = 0;
+       int interrupt = 0;
+       const char *interrupt_string;
+
+       ast_channel_lock(chan);
+       interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+       if (ast_true(interrupt_string)) {
+               interrupt = 1;
+       }
+       ast_channel_unlock(chan);
+
+       if (data)
+               res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+       return res;
 }
 
-static void presence_state_cb(const struct ast_event *event, void *unused)
+static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
 {
-       struct presencechange *pc;
-       const char *tmp;
+       struct ast_presence_state_message *presence_state = stasis_message_data(msg);
+       struct ast_hint *hint;
+       struct ast_str *hint_app = NULL;
+       struct ao2_iterator hint_iter;
+       struct ao2_iterator cb_iter;
+       char context_name[AST_MAX_CONTEXT];
+       char exten_name[AST_MAX_EXTENSION];
 
-       if (!(pc = ao2_alloc(sizeof(*pc), presencechange_destroy))) {
+       if (stasis_message_type(msg) != ast_presence_state_message_type()) {
                return;
        }
 
-       tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER);
-       if (ast_strlen_zero(tmp)) {
-               ast_log(LOG_ERROR, "Received invalid event that had no presence provider IE\n");
-               ao2_ref(pc, -1);
+       hint_app = ast_str_create(1024);
+       if (!hint_app) {
                return;
        }
-       pc->provider = ast_strdup(tmp);
 
-       pc->state = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
-       if (pc->state < 0) {
-               ao2_ref(pc, -1);
-               return;
-       }
+       ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
+       hint_iter = ao2_iterator_init(hints, 0);
+       for (; (hint = ao2_iterator_next(&hint_iter)); ao2_cleanup(hint)) {
+               struct ast_state_cb *state_cb;
+               const char *app;
+               char *parse;
+               SCOPED_AO2LOCK(lock, hint);
 
-       if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE))) {
-               pc->subtype = ast_strdup(tmp);
-       }
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       continue;
+               }
 
-       if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE))) {
-               pc->message = ast_strdup(tmp);
-       }
+               /* Does this hint monitor the device that changed state? */
+               app = ast_get_extension_app(hint->exten);
+               if (ast_strlen_zero(app)) {
+                       /* The hint does not monitor presence at all. */
+                       continue;
+               }
 
-       /* The task processor thread is taking our reference to the presencechange object. */
-       if (ast_taskprocessor_push(extension_state_tps, handle_presencechange, pc) < 0) {
-               ao2_ref(pc, -1);
-       }
-}
+               ast_str_set(&hint_app, 0, "%s", app);
+               parse = parse_hint_presence(hint_app);
+               if (ast_strlen_zero(parse)) {
+                       continue;
+               }
+               if (strcasecmp(parse, presence_state->provider)) {
+                       /* The hint does not monitor the presence provider. */
+                       continue;
+               }
 
-static void device_state_cb(const struct ast_event *event, void *unused)
-{
-       const char *device;
-       struct statechange *sc;
+               /*
+                * Save off strings in case the hint extension gets destroyed
+                * while we are notifying the watchers.
+                */
+               ast_copy_string(context_name,
+                       ast_get_context_name(ast_get_extension_context(hint->exten)),
+                       sizeof(context_name));
+               ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+                       sizeof(exten_name));
+               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
 
-       device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE);
-       if (ast_strlen_zero(device)) {
-               ast_log(LOG_ERROR, "Received invalid event that had no device IE\n");
-               return;
-       }
+               /* Check to see if update is necessary */
+               if ((hint->last_presence_state == presence_state->state) &&
+                       ((hint->last_presence_subtype && presence_state->subtype && !strcmp(hint->last_presence_subtype, presence_state->subtype)) || (!hint->last_presence_subtype && !presence_state->subtype)) &&
+                       ((hint->last_presence_message && presence_state->message && !strcmp(hint->last_presence_message, presence_state->message)) || (!hint->last_presence_message && !presence_state->message))) {
 
-       if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device) + 1)))
-               return;
-       strcpy(sc->dev, device);
-       if (ast_taskprocessor_push(extension_state_tps, handle_statechange, sc) < 0) {
-               ast_free(sc);
+                       /* this update is the same as the last, do nothing */
+                       continue;
+               }
+
+               /* update new values */
+               ast_free(hint->last_presence_subtype);
+               ast_free(hint->last_presence_message);
+               hint->last_presence_state = presence_state->state;
+               hint->last_presence_subtype = presence_state->subtype ? ast_strdup(presence_state->subtype) : NULL;
+               hint->last_presence_message = presence_state->message ? ast_strdup(presence_state->message) : NULL;
+
+               /* For general callbacks */
+               cb_iter = ao2_iterator_init(statecbs, 0);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_PRESENCE,
+                               hint,
+                               NULL);
+               }
+               ao2_iterator_destroy(&cb_iter);
+
+               /* For extension callbacks */
+               cb_iter = ao2_iterator_init(hint->callbacks, 0);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_cleanup(state_cb)) {
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_PRESENCE,
+                               hint,
+                               NULL);
+               }
+               ao2_iterator_destroy(&cb_iter);
        }
+       ao2_iterator_destroy(&hint_iter);
+       ast_mutex_unlock(&context_merge_lock);
+
+       ast_free(hint_app);
 }
 
 /*!
@@ -10906,15 +11894,38 @@ static const struct ast_data_entry pbx_data_providers[] = {
        AST_DATA_ENTRY("asterisk/core/hints", &hints_data_provider),
 };
 
+/*!
+ * \internal
+ * \brief Clean up resources on Asterisk shutdown.
+ *
+ * \note Cleans up resources allocated in load_pbx
+ */
+static void unload_pbx(void)
+{
+       int x;
+
+       presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub);
+       device_state_sub = stasis_unsubscribe_and_join(device_state_sub);
+
+       /* Unregister builtin applications */
+       for (x = 0; x < ARRAY_LEN(builtins); x++) {
+               ast_unregister_application(builtins[x].name);
+       }
+       ast_manager_unregister("ShowDialPlan");
+       ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
+       ast_custom_function_unregister(&exception_function);
+       ast_custom_function_unregister(&testtime_function);
+       ast_data_unregister(NULL);
+}
+
 int load_pbx(void)
 {
        int x;
 
+       ast_register_atexit(unload_pbx);
+
        /* Initialize the PBX */
        ast_verb(1, "Asterisk PBX Core Initializing\n");
-       if (!(extension_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
-               ast_log(LOG_WARNING, "failed to create pbx-core taskprocessor\n");
-       }
 
        ast_verb(1, "Registering builtin applications:\n");
        ast_cli_register_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
@@ -10934,13 +11945,11 @@ int load_pbx(void)
        /* Register manager application */
        ast_manager_register_xml_core("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan);
 
-       if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, device_state_cb, "pbx Device State Change", NULL,
-                       AST_EVENT_IE_END))) {
+       if (!(device_state_sub = stasis_subscribe(ast_device_state_topic_all(), device_state_cb, NULL))) {
                return -1;
        }
 
-       if (!(presence_state_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE, presence_state_cb, "pbx Presence State Change", NULL,
-                       AST_EVENT_IE_END))) {
+       if (!(presence_state_sub = stasis_subscribe(ast_presence_state_topic_all(), presence_state_cb, NULL))) {
                return -1;
        }
 
@@ -11251,17 +12260,29 @@ int ast_async_parseable_goto(struct ast_channel *chan, const char *goto_string)
 
 char *ast_complete_applications(const char *line, const char *word, int state)
 {
-       struct ast_app *app = NULL;
+       struct ast_app *app;
        int which = 0;
+       int cmp;
        char *ret = NULL;
        size_t wordlen = strlen(word);
 
        AST_RWLIST_RDLOCK(&apps);
        AST_RWLIST_TRAVERSE(&apps, app, list) {
-               if (!strncasecmp(word, app->name, wordlen) && ++which > state) {
+               cmp = strncasecmp(word, app->name, wordlen);
+               if (cmp > 0) {
+                       continue;
+               }
+               if (!cmp) {
+                       /* Found match. */
+                       if (++which <= state) {
+                               /* Not enough matches. */
+                               continue;
+                       }
                        ret = ast_strdup(app->name);
                        break;
                }
+               /* Not in container. */
+               break;
        }
        AST_RWLIST_UNLOCK(&apps);
 
@@ -11304,11 +12325,37 @@ static int statecbs_cmp(void *obj, void *arg, int flags)
        return (state_cb->change_cb == change_cb) ? CMP_MATCH | CMP_STOP : 0;
 }
 
+/*!
+ * \internal
+ * \brief Clean up resources on Asterisk shutdown
+ */
+static void pbx_shutdown(void)
+{
+       if (hints) {
+               ao2_ref(hints, -1);
+               hints = NULL;
+       }
+       if (hintdevices) {
+               ao2_ref(hintdevices, -1);
+               hintdevices = NULL;
+       }
+       if (statecbs) {
+               ao2_ref(statecbs, -1);
+               statecbs = NULL;
+       }
+       if (contexts_table) {
+               ast_hashtab_destroy(contexts_table, NULL);
+       }
+       pbx_builtin_clear_globals();
+}
+
 int ast_pbx_init(void)
 {
        hints = ao2_container_alloc(HASH_EXTENHINT_SIZE, hint_hash, hint_cmp);
        hintdevices = ao2_container_alloc(HASH_EXTENHINT_SIZE, hintdevice_hash_cb, hintdevice_cmp_multiple);
        statecbs = ao2_container_alloc(1, NULL, statecbs_cmp);
 
+       ast_register_cleanup(pbx_shutdown);
+
        return (hints && hintdevices && statecbs) ? 0 : -1;
 }