Commit 7988d1138c for asterisk.org

commit 7988d1138cb2dfd27e3e18f4af0672618f67eb6d
Author: Naveen Albert <asterisk@phreaknet.org>
Date:   Tue May 5 21:52:18 2026 -0400

    app_dial: Properly handle callee hangup while sending digits.

    If we are sending digits (either DTMF, MF, or SF) to the called channel
    after receiving progress or a wink, and the callee hangs up before we
    have finished sending it digits, there are several problems that can ensue:

    * If the callee hung up without answering, the calling channel would
      hang up and not continue in the dialplan.
    * If the callee *did* answer before hanging up, the answer was never
      passed through to the caller, since this gets "eaten" by the various
      digit streaming functions and is never processed by app_dial.

    This is generally an edge case that occurs due to some kind of signaling
    failure, but to better handle this:

    * Set to_answer to 0 to prevent hangup on the exit path, just like other
      parts of wait_for_answer.
    * Better document this usage of to_answer.
    * If the channel did answer while it was receiving digits, manually
      answer the calling channel before we abort. The call would not continue
      in the dialplan anyways (either before or after this fix), but technically
      the call was answered, so the CDRs should probably reflect that, and this
      mirrors the behavior of calls which normally do not continue.

    Resolves: #1915

    UserNote: If a called channel sends progress or wink and the caller begins
    sending digits but the callee answers and then hangs up before digit
    sending can finish, the call is now answered before being disconnected.
    If the callee hangs up without answering, the call now continues in
    the dialplan.

diff --git a/apps/app_dial.c b/apps/app_dial.c
index a6c91b2ffb..e3b051e389 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -1291,7 +1291,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 			} else {
 				ast_verb(3, "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan);
 			}
-			*to_answer = 0;
+			*to_answer = 0; /* Continue in the dialplan, since nobody answered */
 			if (is_cc_recall) {
 				ast_cc_failed(cc_recall_core_id, "Everyone is busy/congested for the recall. How sad");
 			}
@@ -1603,6 +1603,17 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 						}
 						if (res) {
 							ast_log(LOG_WARNING, "Called channel %s hung up post-progress before all digits could be sent\n", ast_channel_name(c));
+							if (ast_channel_state(c) == AST_STATE_UP) {
+								/* The called channel answered while we were sending it digits, so the answer never got processed by app_dial.
+								 * The channel is dying now, but better to answer late than never? */
+								ast_debug(1, "Channel %s answered while we were sending it digits, answering %s retroactively\n", ast_channel_name(c), ast_channel_name(in));
+								/* Indicate answer supervision to the caller before we exit.
+								 * We're not going to bridge, but this way at least the CDRs are correct, etc. */
+								ast_raw_answer(in);
+								strcpy(pa->status, "ANSWER");
+							} else {
+								*to_answer = 0; /* Continue in the dialplan, since nobody answered */
+							}
 							goto wait_over;
 						}
 					}
@@ -1630,6 +1641,14 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 						}
 						if (res) {
 							ast_log(LOG_WARNING, "Called channel %s hung up post-wink before all digits could be sent\n", ast_channel_name(c));
+							if (ast_channel_state(c) == AST_STATE_UP) {
+								/* Same as in AST_CONTROL_PROGRESS */
+								ast_debug(1, "Channel %s answered while we were sending it digits, answering %s retroactively\n", ast_channel_name(c), ast_channel_name(in));
+								ast_raw_answer(in);
+								strcpy(pa->status, "ANSWER");
+							} else {
+								*to_answer = 0; /* Continue in the dialplan, since nobody answered */
+							}
 							goto wait_over;
 						}
 					}
@@ -1937,7 +1956,11 @@ skip_frame:;

 wait_over:
 	if (!*to_answer || ast_check_hangup(in)) {
-		ast_verb(3, "Nobody picked up in %d ms\n", orig_answer_to);
+		if (orig_answer_to != -1) {
+			ast_verb(3, "Nobody picked up in %d ms\n", orig_answer_to);
+		} else {
+			ast_verb(3, "Call terminated without answer\n");
+		}
 		publish_dial_end_event(in, out_chans, NULL, "NOANSWER");
 	} else if (!*to_progress) {
 		ast_verb(3, "No early media received in %d ms\n", orig_progress_to);
@@ -2998,8 +3021,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast

 	if (!peer) {
 		if (result) {
-			res = result;
+			res = result; /* User entered a DTMF digit that matched a context */
 		} else if (to_answer) { /* Musta gotten hung up */
+			/* This does not necessarily mean that we dialed without a timeout.
+			 * to_answer is (ab)used by wait_for_answer to to indicate whether or we should continue in the dialplan or exit. */
 			res = -1;
 		} else { /* Nobody answered, next please? */
 			res = 0;