ARI: Add ability to raise arbitrary User Events
[asterisk/asterisk.git] / main / pbx.c
index 81fd48d..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,7 +64,6 @@ 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"
@@ -106,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
@@ -470,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.
@@ -523,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>
@@ -542,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>
@@ -560,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>
@@ -580,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>
@@ -657,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">
@@ -822,6 +841,17 @@ struct ast_app;
 
 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
@@ -1139,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 *);
@@ -1153,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
@@ -1227,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)
@@ -1261,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;
 }
@@ -1326,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 },
@@ -1457,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 );
@@ -1565,33 +1604,27 @@ 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);
        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;
 }
 
-
-/*! Go no deeper than this through includes (not counting loops) */
-#define AST_PBX_MAX_STACK      128
-
 static struct ast_app *pbx_findapp_nolock(const char *name)
 {
        struct ast_app *cur;
@@ -3599,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;
@@ -3973,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.
@@ -4071,6 +4126,33 @@ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_m
        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;
+}
+
 /*! \brief return a pointer to the arguments of the function,
  * and terminates the function name with '\\0'
  */
@@ -4092,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);
@@ -4104,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);
@@ -4141,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);
@@ -4180,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)
@@ -4278,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);
@@ -4308,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);
@@ -4354,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);
@@ -4473,11 +4679,12 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
-                               if (!ltmp)
-                                       ltmp = ast_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;
@@ -4507,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);
@@ -4562,11 +4769,12 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
 
                        /* Substitute if necessary */
                        if (needsub) {
-                               size_t used;
-                               if (!ltmp)
-                                       ltmp = ast_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;
@@ -4597,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:
  *
@@ -4640,12 +4829,11 @@ 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);
-       RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
-       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 
        ast_rdlock_contexts();
        if (found)
@@ -4666,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);
@@ -4676,12 +4876,11 @@ 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);
-                       {
+                       if (VERBOSITY_ATLEAST(3)) {
                                ast_verb(3, "Executing [%s@%s:%d] " COLORIZE_FMT "(\"" COLORIZE_FMT "\", \"" COLORIZE_FMT "\") %s\n",
                                        exten, context, priority,
                                        COLORIZE(COLOR_BRCYAN, 0, app->name),
@@ -4689,18 +4888,6 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                                        COLORIZE(COLOR_BRMAGENTA, 0, passdata),
                                        "in new stack");
                        }
-                       snapshot = ast_channel_snapshot_create(c);
-                       if (snapshot) {
-                               /* pbx_exec sets application name and data, but we don't want to log
-                                * every exec. Just update the snapshot here instead.
-                                */
-                               ast_string_field_set(snapshot, appl, app->name);
-                               ast_string_field_set(snapshot, data, passdata);
-                               msg = stasis_message_create(ast_channel_snapshot_type(), snapshot);
-                               if (msg) {
-                                       stasis_publish(ast_channel_topic(c), msg);
-                               }
-                       }
                        return pbx_exec(c, app, passdata);      /* 0 on success, -1 on failure */
                }
        } else if (q.swo) {     /* not found here, but in another switch */
@@ -4891,7 +5078,10 @@ const char *ast_extension_state2str(int extension_state)
        return "Unknown";
 }
 
-/*! \internal \brief Check extension state for an extension by using hint */
+/*!
+ * \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)
 {
@@ -5115,7 +5305,7 @@ static void get_device_state_causing_channels(struct ao2_container *c)
        ao2_iterator_destroy(&iter);
 }
 
-static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *msg)
+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;
@@ -5228,7 +5418,7 @@ static void device_state_cb(void *unused, struct stasis_subscription *sub, struc
                ao2_iterator_destroy(&cb_iter);
 
                /* For extension callbacks */
-               /* extended callbacks are called when the state changed or when AST_EVENT_RINGING is
+               /* 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);
@@ -5276,7 +5466,10 @@ static void destroy_state_cb(void *doomed)
        }
 }
 
-/*! \internal \brief Add watcher for extension states with destructor */
+/*!
+ * \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)
 {
@@ -5715,9 +5908,11 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
 
        ast_channel_lock(chan);
 
-       if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
-               ast_cdr_end(ast_channel_cdr(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)) {
@@ -5726,12 +5921,6 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
        ast_channel_exten_set(chan, "h");
        ast_channel_priority_set(chan, 1);
 
-       /*
-        * 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_APPUNLOAD);
-
        /* 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);
@@ -5799,15 +5988,11 @@ int ast_pbx_hangup_handler_run(struct ast_channel *chan)
                return 0;
        }
 
-       if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
-               ast_cdr_end(ast_channel_cdr(chan));
-       }
-
        /*
         * 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_APPUNLOAD);
+       ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
 
        for (;;) {
                handlers = ast_channel_hangup_handlers(chan);
@@ -6088,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);
@@ -6116,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 */
@@ -6231,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;
                        }
@@ -6341,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);
                }
        }
 
@@ -6392,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;
                }
        }
@@ -6708,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)
@@ -6738,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)
@@ -6754,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
@@ -6766,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);
@@ -6789,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)
@@ -6850,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;
 
@@ -6884,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 {
@@ -7356,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;
@@ -7556,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));
@@ -8087,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:
@@ -8104,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;
 }
 
@@ -8239,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"),
@@ -8555,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);
@@ -8918,6 +9171,8 @@ 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;
@@ -8937,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 */
@@ -9701,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);
@@ -9721,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) {
@@ -9734,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);
@@ -9815,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 {
@@ -9824,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 {
@@ -9839,8 +10092,6 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
 struct pbx_outgoing {
        /*! \brief Dialing structure being used */
        struct ast_dial *dial;
-       /*! \brief Mutex lock for synchronous dialing */
-       ast_mutex_t lock;
        /*! \brief Condition for synchronous dialing */
        ast_cond_t cond;
        /*! \brief Application to execute */
@@ -9853,6 +10104,8 @@ struct pbx_outgoing {
        char exten[AST_MAX_EXTENSION];
        /*! \brief Dialplan priority */
        int priority;
+       /*! \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 */
@@ -9868,7 +10121,6 @@ static void pbx_outgoing_destroy(void *obj)
                ast_dial_destroy(outgoing->dial);
        }
 
-       ast_mutex_destroy(&outgoing->lock);
        ast_cond_destroy(&outgoing->cond);
 
        ast_free(outgoing->appdata);
@@ -9881,11 +10133,12 @@ static void *pbx_outgoing_exec(void *data)
        enum ast_dial_result res;
 
        /* Notify anyone interested that dialing is complete */
-       ast_mutex_lock(&outgoing->lock);
        res = ast_dial_run(outgoing->dial, NULL, 0);
+       ao2_lock(outgoing);
+       outgoing->dial_res = res;
        outgoing->dialed = 1;
        ast_cond_signal(&outgoing->cond);
-       ast_mutex_unlock(&outgoing->lock);
+       ao2_unlock(outgoing);
 
        /* If the outgoing leg was not answered we can immediately return and go no further */
        if (res != AST_DIAL_RESULT_ANSWERED) {
@@ -9926,10 +10179,10 @@ static void *pbx_outgoing_exec(void *data)
        }
 
        /* Notify anyone else again that may be interested that execution is complete */
-       ast_mutex_lock(&outgoing->lock);
+       ao2_lock(outgoing);
        outgoing->executed = 1;
        ast_cond_signal(&outgoing->cond);
-       ast_mutex_unlock(&outgoing->lock);
+       ao2_unlock(outgoing);
 
        return NULL;
 }
@@ -9953,21 +10206,69 @@ static void pbx_outgoing_state_callback(struct ast_dial *dial)
        ast_queue_control(channel, AST_CONTROL_ANSWER);
 }
 
-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 **channel, int early_media)
+/*!
+ * \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)
 {
-       RAII_VAR(struct pbx_outgoing *, outgoing, ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy), ao2_cleanup);
+       enum ast_control_frame_type pbx_reason;
+
+       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;
+               }
+       }
+
+       return pbx_reason;
+}
+
+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;
 
+       outgoing = ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy);
        if (!outgoing) {
                return -1;
        }
-
-       if (channel) {
-               *channel = NULL;
-       }
+       ast_cond_init(&outgoing->cond, NULL);
 
        if (!ast_strlen_zero(app)) {
                ast_copy_string(outgoing->app, app, sizeof(outgoing->app));
@@ -9982,136 +10283,208 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
                return -1;
        }
 
-       if (ast_dial_append(outgoing->dial, type, addr)) {
+       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));
+               }
                return -1;
        }
 
        dialed = ast_dial_get_channel(outgoing->dial, 0);
+       if (!dialed) {
+               return -1;
+       }
 
-       ast_set_variables(dialed, vars);
-
+       ast_channel_lock(dialed);
+       if (vars) {
+               ast_set_variables(dialed, vars);
+       }
        if (account) {
-               ast_cdr_setaccount(dialed, account);
+               ast_channel_accountcode_set(dialed, account);
        }
-       ast_set_flag(ast_channel_cdr(dialed), AST_CDR_FLAG_ORIGINATED);
+       ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
+       ast_channel_unlock(dialed);
 
-       if (!ast_strlen_zero(cid_num) && !ast_strlen_zero(cid_name)) {
+       if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
                struct ast_party_connected_line connected;
 
-               ast_party_connected_line_set_init(&connected, ast_channel_connected(dialed));
-
+               /*
+                * 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);
-               connected.id.number.valid = 1;
-               connected.id.number.str = (char *) cid_num;
-               connected.id.number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-               connected.id.name.valid = 1;
-               connected.id.name.str = (char *) cid_name;
-               connected.id.name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
 
+               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);
        }
 
        if (early_media) {
-               ast_dial_set_state_callback(outgoing->dial, &pbx_outgoing_state_callback);
+               ast_dial_set_state_callback(outgoing->dial, pbx_outgoing_state_callback);
        }
 
-       if (channel) {
-               *channel = dialed;
-               ast_channel_lock(*channel);
+       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);
+               }
        }
 
-       ast_mutex_init(&outgoing->lock);
-       ast_cond_init(&outgoing->cond, NULL);
-
        ao2_ref(outgoing, +1);
-
-       ast_mutex_lock(&outgoing->lock);
-
        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);
-               if (channel) {
-                       ast_channel_unlock(*channel);
-               }
                ao2_ref(outgoing, -1);
+               if (locked_channel) {
+                       if (!synchronous) {
+                               ast_channel_unlock(dialed);
+                       }
+                       ast_channel_unref(dialed);
+               }
                return -1;
        }
 
-       /* Wait for dialing to complete */
-       if (channel || synchronous) {
-               if (channel) {
-                       ast_channel_unlock(*channel);
-               }
+       if (synchronous) {
+               ao2_lock(outgoing);
+               /* Wait for dialing to complete */
                while (!outgoing->dialed) {
-                       ast_cond_wait(&outgoing->cond, &outgoing->lock);
+                       ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
                }
-               if (channel) {
-                       ast_channel_lock(*channel);
+               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);
 
-       /* Wait for execution to complete */
-       if (synchronous > 1) {
-               while (!outgoing->executed) {
-                       ast_cond_wait(&outgoing->cond, &outgoing->lock);
+               /* 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));
+               }
+
+               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);
                }
        }
 
-       ast_mutex_unlock(&outgoing->lock);
+       if (locked_channel) {
+               *locked_channel = dialed;
+       }
+       return 0;
+}
 
-       if (reason) {
-               *reason = ast_dial_reason(outgoing->dial, 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;
        }
 
-       if ((synchronous > 1) && ast_dial_state(outgoing->dial) != AST_DIAL_RESULT_ANSWERED &&
-               ast_strlen_zero(app) && ast_exists_extension(NULL, context, "failed", 1, NULL)) {
-               struct ast_channel *failed = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", NULL, 0, "OutgoingSpoolFailed");
+       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 (failed) {
-                       char failed_reason[4] = "";
+       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;
 
-                       if (!ast_strlen_zero(context)) {
-                               ast_channel_context_set(failed, context);
-                       }
+               /* We do not have to worry about a locked_channel if dialing failed. */
+               ast_assert(!locked_channel || !*locked_channel);
 
-                       if (account) {
-                               ast_cdr_setaccount(failed, account);
-                       }
+               /*!
+                * \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];
 
-                       set_ext_pri(failed, "failed", 1);
                        ast_set_variables(failed, vars);
-                       snprintf(failed_reason, sizeof(failed_reason), "%d", ast_dial_reason(outgoing->dial, 0));
+                       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_log(LOG_ERROR, "Unable to run PBX on '%s'\n",
+                                       ast_channel_name(failed));
                                ast_hangup(failed);
                        }
                }
        }
 
-       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 **channel, int early_media)
-{
-       return pbx_outgoing_attempt(type, cap, addr, timeout, context, exten, priority, NULL, NULL, reason, synchronous, cid_num,
-               cid_name, vars, account, channel, early_media);
+       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)
+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);
+       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 --
@@ -10256,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
@@ -10388,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;
 }
@@ -10404,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;
 }
@@ -10418,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);
@@ -10426,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);
@@ -10441,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)
@@ -10461,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);
@@ -10469,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;
 }
@@ -10990,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);
        }
 
@@ -11040,12 +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);
                ast_channel_publish_varset(chan, name, value);
+
+               if (headp != &globals) {
+                       ast_channel_publish_snapshot(chan);
+               }
        }
 
        if (chan)
@@ -11211,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");
@@ -11219,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") &&
@@ -11228,41 +11613,130 @@ 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));
+       }
+
+       return res;
+}
+
+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_digit_str(chan, data, "", ast_channel_language(chan));
        return res;
 }
 
 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_character_str(chan, data, "", ast_channel_language(chan));
        return res;
 }
 
 static int pbx_builtin_sayphonetic(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_phonetic_str(chan, data, "", ast_channel_language(chan));
+               res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
        return res;
 }
 
-static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *msg)
+static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
 {
        struct ast_presence_state_message *presence_state = stasis_message_data(msg);
        struct ast_hint *hint;
@@ -11420,8 +11894,12 @@ 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 */
+/*!
+ * \internal
+ * \brief Clean up resources on Asterisk shutdown.
+ *
+ * \note Cleans up resources allocated in load_pbx
+ */
 static void unload_pbx(void)
 {
        int x;
@@ -11877,7 +12355,7 @@ int ast_pbx_init(void)
        hintdevices = ao2_container_alloc(HASH_EXTENHINT_SIZE, hintdevice_hash_cb, hintdevice_cmp_multiple);
        statecbs = ao2_container_alloc(1, NULL, statecbs_cmp);
 
-       ast_register_atexit(pbx_shutdown);
+       ast_register_cleanup(pbx_shutdown);
 
        return (hints && hintdevices && statecbs) ? 0 : -1;
 }