ARI: Add ability to raise arbitrary User Events
[asterisk/asterisk.git] / main / pbx.c
index ee472d8..faddc47 100644 (file)
@@ -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
@@ -845,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
@@ -1601,9 +1608,11 @@ int pbx_exec(struct ast_channel *c,      /*!< Channel */
        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_channel_publish_snapshot(c);
+       ast_channel_unlock(c);
 
        if (app->module)
                u = __ast_module_user_add(app->module, c);
@@ -1616,10 +1625,6 @@ int pbx_exec(struct ast_channel *c,      /*!< Channel */
        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;
@@ -3627,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;
@@ -4001,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.
@@ -4099,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'
  */
@@ -4120,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);
@@ -4132,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);
@@ -4169,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);
@@ -4208,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)
@@ -4656,8 +4834,6 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
        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)
@@ -4704,7 +4880,7 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                                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),
@@ -5732,6 +5908,12 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
 
        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);
@@ -5739,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_HANGUP_EXEC);
-
        /* 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);
@@ -6097,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);
@@ -8620,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);
@@ -8983,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;
@@ -9002,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 */
@@ -9904,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 */
@@ -9935,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);
@@ -9948,12 +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) {
@@ -9994,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;
 }
@@ -10021,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)
+{
+       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, ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy), ao2_cleanup);
+       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));
@@ -10050,13 +10283,17 @@ 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;
        }
 
@@ -10065,14 +10302,15 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
                return -1;
        }
 
+       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);
 
        if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
                struct ast_party_connected_line connected;
@@ -10105,106 +10343,148 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
                ast_dial_set_state_callback(outgoing->dial, pbx_outgoing_state_callback);
        }
 
-       if (channel) {
-               *channel = dialed;
-               ast_channel_ref(*channel);
-               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);
-                       ast_channel_unref(*channel);
-               }
-               ast_mutex_unlock(&outgoing->lock);
                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 (synchronous) {
-               if (channel && *channel) {
-                       ast_channel_unlock(*channel);
-               }
+               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 (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);
 
-                       if (outgoing->dial_res != AST_DIAL_RESULT_ANSWERED) {
-                               ast_mutex_unlock(&outgoing->lock);
-                               /* The dial operation failed. */
-                               return -1;
+               /* 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 (channel && *channel) {
-                       ast_channel_lock(*channel);
+               if (locked_channel) {
+                       ast_channel_lock(dialed);
                }
        }
 
-       /* Wait for execution to complete */
-       if (synchronous > 1) {
-               while (!outgoing->executed) {
-                       ast_cond_wait(&outgoing->cond, &outgoing->lock);
-               }
+       if (locked_channel) {
+               *locked_channel = dialed;
        }
+       return 0;
+}
 
-       ast_mutex_unlock(&outgoing->lock);
+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 = ast_dial_reason(outgoing->dial, 0);
+       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_channel_accountcode_set(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 --
@@ -10480,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_channel_unlock(chan);
        wait_for_hangup(chan, data);
        return -1;
 }
@@ -10496,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_channel_unlock(chan);
        wait_for_hangup(chan, data);
        return -1;
 }
@@ -10532,9 +10816,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
        }
 
        if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
-               if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
-                       ast_log(AST_LOG_WARNING, "Failed to disable CDR on %s\n", ast_channel_name(chan));
-               }
+               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);