From 86e8716952b7b7fde146f621fc5b74ef3bd2ae3a Mon Sep 17 00:00:00 2001
From: George Joseph <gjoseph@digium.com>
Date: Thu, 6 Oct 2016 08:58:26 -0600
Subject: [PATCH] app_dial:  Add the "Q" option to set the cause on unanswered
 channels

The "Q" option will set the cause on the unanswered channels when
another channel answers.  It overrides the default of
ANSWERED_ELSEWHERE.

NOTE:  chan_sip does not support setting the cause on a CANCEL to
anything other than ANSWERED_ELSEWHERE.

ASTERISK-26446 #close

Change-Id: I71742e0919aaa16784c30a2b2e73fbeed7672e47
---
 CHANGES         |  6 ++++++
 apps/app_dial.c | 56 ++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 57 insertions(+), 5 deletions(-)

diff --git a/CHANGES b/CHANGES
index be279879d0..96fdd13f79 100644
--- a/CHANGES
+++ b/CHANGES
@@ -103,6 +103,12 @@ app_confbridge
     instance, allows a channel to immediately exit the ConfBridge without having
     to wait for a leave announcement to play.
 
+app_dial
+------------------
+ * Added the "Q" option which sets the Q.850/Q.931 cause on unanswered channels
+   when another channel answers the call.  The default of ANSWERED_ELSEWHERE
+   is unchanged.
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 13 to Asterisk 14 --------------------
 ------------------------------------------------------------------------------
diff --git a/apps/app_dial.c b/apps/app_dial.c
index 316d38da15..b5d8f50c76 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -375,6 +375,25 @@ ASTERISK_REGISTER_FILE()
 					<para>Enable privacy mode. Use <replaceable>x</replaceable> as the family/key in the AstDB database if
 					it is provided. The current extension is used if a database family/key is not specified.</para>
 				</option>
+				<option name="Q">
+					<argument name="cause" required="true"/>
+					<para>Specify the Q.850/Q.931 <replaceable>cause</replaceable> to send on
+					unanswered channels when another channel answers the call.
+					As with <literal>Hangup()</literal>, <replaceable>cause</replaceable>
+					can be a numeric cause code or a name such as
+						<literal>NO_ANSWER</literal>,
+						<literal>USER_BUSY</literal>,
+						<literal>CALL_REJECTED</literal> or
+						<literal>ANSWERED_ELSEWHERE</literal> (the default if Q isn't specified).
+						You can also specify <literal>0</literal> or <literal>NONE</literal>
+						to send no cause.  See the <filename>causes.h</filename> file for the
+						full list of valid causes and names.
+						</para>
+					<note>
+						<para>chan_sip does not support setting the cause on a CANCEL to anything
+						other than ANSWERED_ELSEWHERE.</para>
+					</note>
+				</option>
 				<option name="r">
 					<para>Default: Indicate ringing to the calling party, even if the called party isn't actually ringing. Pass no audio to the calling
 					party until the called channel has answered.</para>
@@ -520,6 +539,9 @@ ASTERISK_REGISTER_FILE()
 			<example title="Dial with call length limit">
 			 same => n,Dial(PJSIP/alice,,L(60000:30000:10000))
 			</example>
+			<example title="Dial alice and bob and send NO_ANSWER to bob instead of ANSWERED_ELSEWHERE when alice answers">
+			 same => n,Dial(PJSIP/alice&amp;PJSIP/bob,,Q(NO_ANSWER))
+			</example>
 			<example title="Dial with pre-dial subroutines">
 			[default]
 
@@ -684,6 +706,7 @@ enum {
 #define OPT_PREDIAL_CALLEE   (1LLU << 41)
 #define OPT_PREDIAL_CALLER   (1LLU << 42)
 #define OPT_RING_WITH_EARLY_MEDIA (1LLU << 43)
+#define OPT_HANGUPCAUSE      (1LLU << 44)
 
 enum {
 	OPT_ARG_ANNOUNCE = 0,
@@ -705,6 +728,7 @@ enum {
 	OPT_ARG_FORCE_CID_PRES,
 	OPT_ARG_PREDIAL_CALLEE,
 	OPT_ARG_PREDIAL_CALLER,
+	OPT_ARG_HANGUPCAUSE,
 	/* note: this entry _MUST_ be the last one in the enum */
 	OPT_ARG_ARRAY_SIZE
 };
@@ -738,6 +762,7 @@ AST_APP_OPTIONS(dial_exec_options, BEGIN_OPTIONS
 	AST_APP_OPTION_ARG('O', OPT_OPERMODE, OPT_ARG_OPERMODE),
 	AST_APP_OPTION('p', OPT_SCREENING),
 	AST_APP_OPTION_ARG('P', OPT_PRIVACY, OPT_ARG_PRIVACY),
+	AST_APP_OPTION_ARG('Q', OPT_HANGUPCAUSE, OPT_ARG_HANGUPCAUSE),
 	AST_APP_OPTION_ARG('r', OPT_RINGBACK, OPT_ARG_RINGBACK),
 	AST_APP_OPTION('R', OPT_RING_WITH_EARLY_MEDIA),
 	AST_APP_OPTION_ARG('S', OPT_DURATION_STOP, OPT_ARG_DURATION_STOP),
@@ -796,7 +821,7 @@ static void chanlist_free(struct chanlist *outgoing)
 	ast_free(outgoing);
 }
 
-static void hanguptree(struct dial_head *out_chans, struct ast_channel *exception, int answered_elsewhere)
+static void hanguptree(struct dial_head *out_chans, struct ast_channel *exception, int hangupcause)
 {
 	/* Hang up a tree of stuff */
 	struct chanlist *outgoing;
@@ -804,9 +829,9 @@ static void hanguptree(struct dial_head *out_chans, struct ast_channel *exceptio
 	while ((outgoing = AST_LIST_REMOVE_HEAD(out_chans, node))) {
 		/* Hangup any existing lines we have open */
 		if (outgoing->chan && (outgoing->chan != exception)) {
-			if (answered_elsewhere) {
+			if (hangupcause >= 0) {
 				/* This is for the channel drivers */
-				ast_channel_hangupcause_set(outgoing->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
+				ast_channel_hangupcause_set(outgoing->chan, hangupcause);
 			}
 			ast_hangup(outgoing->chan);
 		}
@@ -2768,6 +2793,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 	} else {
 		const char *number;
 		int dial_end_raised = 0;
+		int cause = -1;
 
 		if (ast_test_flag64(&opts, OPT_CALLER_ANSWER))
 			ast_answer(chan);
@@ -2778,7 +2804,23 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		/* Ah ha!  Someone answered within the desired timeframe.  Of course after this
 		   we will always return with -1 so that it is hung up properly after the
 		   conversation.  */
-		hanguptree(&out_chans, peer, 1);
+
+		if (ast_test_flag64(&opts, OPT_HANGUPCAUSE)
+			&& !ast_strlen_zero(opt_args[OPT_ARG_HANGUPCAUSE])) {
+			cause = ast_str2cause(opt_args[OPT_ARG_HANGUPCAUSE]);
+			if (cause <= 0) {
+				if (!strcasecmp(opt_args[OPT_ARG_HANGUPCAUSE], "NONE")) {
+					cause = 0;
+				} else if (sscanf(opt_args[OPT_ARG_HANGUPCAUSE], "%30d", &cause) != 1
+					|| cause < 0) {
+					ast_log(LOG_WARNING, "Invalid cause given to Dial(...Q(<cause>)): \"%s\"\n",
+						opt_args[OPT_ARG_HANGUPCAUSE]);
+					cause = -1;
+				}
+			}
+		}
+		hanguptree(&out_chans, peer, cause >= 0 ? cause : AST_CAUSE_ANSWERED_ELSEWHERE);
+
 		/* If appropriate, log that we have a destination channel and set the answer time */
 		if (ast_channel_name(peer))
 			pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", ast_channel_name(peer));
@@ -3182,7 +3224,11 @@ out:
 	}
 
 	ast_channel_early_bridge(chan, NULL);
-	hanguptree(&out_chans, NULL, ast_channel_hangupcause(chan)==AST_CAUSE_ANSWERED_ELSEWHERE || ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE) ? 1 : 0 ); /* forward 'answered elsewhere' if we received it */
+	 /* forward 'answered elsewhere' if we received it */
+	hanguptree(&out_chans, NULL,
+		ast_channel_hangupcause(chan) == AST_CAUSE_ANSWERED_ELSEWHERE
+		|| ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE)
+		? AST_CAUSE_ANSWERED_ELSEWHERE : -1);
 	pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
 	ast_debug(1, "Exiting with DIALSTATUS=%s.\n", pa.status);
 
-- 
GitLab