From 68ff57bc42f041d9786e472690ee0c15ccf23e17 Mon Sep 17 00:00:00 2001
From: Grzegorz Sluja <grzegorz.sluja@iopsys.eu>
Date: Fri, 14 Oct 2022 08:57:14 +0000
Subject: [PATCH] Fix some regressions caused by unattended call transfer

- The issue about TELCHAN stuck after call hold/unhold
- Asterisk crashes when call transfer is provided in an invalid scenario: Transferor calls to Transferee,
  then Transferor calls to Transfer Target, i.e. trying to let the caller do the transfer.
---
 channels/chan_brcm.c  |  6 -----
 channels/chan_pjsip.c | 53 ++++++++++++++++++++-----------------------
 res/res_pjsip_refer.c | 12 ++++++++++
 3 files changed, 37 insertions(+), 34 deletions(-)

diff --git a/channels/chan_brcm.c b/channels/chan_brcm.c
index 663a59d1f9..b86714b4fd 100644
--- a/channels/chan_brcm.c
+++ b/channels/chan_brcm.c
@@ -552,7 +552,6 @@ static int brcm_indicate(struct ast_channel *ast, int condition, const void *dat
 	//ast_mutex_lock(&sub->parent->lock);
 	switch(condition) {
 	case AST_CONTROL_UNHOLD:
-		res = 0; //We still want asterisk core to play tone
 		brcm_stop_dialtone(sub->parent);
 
 		// Play a beep when unholding.
@@ -577,7 +576,6 @@ static int brcm_indicate(struct ast_channel *ast, int condition, const void *dat
 		sub->codec = -1;
 		if (sub->channel_state == RINGBACK)
 			endpt_signal(sub->parent->line_id, "ringback", "off", NULL);
-		res = 0;
 		break;
 	case AST_CONTROL_RINGING:
 		ast_debug(4, "Got AST_CONTROL_RINGING on %s, sub->codec = %d\n", ast_channel_name(ast), sub->codec);
@@ -589,10 +587,8 @@ static int brcm_indicate(struct ast_channel *ast, int condition, const void *dat
 			sub->call_id = ast_channel_callid(sub->owner);
 			endpt_connection(sub->parent->line_id, sub->call_id, "update");
 		}
-		res = 0;
 		break;
 	case AST_CONTROL_UNHOLD_FOR_TRANSFER:
-		res = -1;
 		if (sub->channel_state == TRANSFERING) {
 			struct ast_channel *bridged_chan = ast_channel_bridge_peer(sub->owner);
 			if (bridged_chan) {
@@ -661,11 +657,9 @@ static int brcm_indicate(struct ast_channel *ast, int condition, const void *dat
 					}
 				}
 			}
-			res = 0;
 		} else if (sub->owner && (sub->call_id == 0)) {
 			sub->call_id = ast_channel_callid(sub->owner);
 			endpt_connection(sub->parent->line_id, sub->call_id, "update");
-			res = 0;
 		} else {
 			res = -1;
 		}
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 633027b02e..674af7786d 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -1808,10 +1808,12 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
 			}
 		}
 		struct ast_channel *bridged_chan;
+		ast_channel_unlock(ast);
 		bridged_chan = ast_channel_bridge_peer(ast);
-
+		ast_channel_lock(ast);
 		if (bridged_chan) {
 			ast_indicate(bridged_chan, AST_CONTROL_UNHOLD_FOR_TRANSFER);
+			ast_channel_unref(bridged_chan);
 		}
 		break;
 	case AST_CONTROL_SRCUPDATE:
@@ -2073,7 +2075,7 @@ static void xfer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
 	struct ast_channel *bridged_chan = ast_channel_bridge_peer(chan);
 	if (bridged_chan) {
 		while (2 < ao2_ref(bridged_chan, 0))
-			ao2_ref(bridged_chan, -1);
+			ast_channel_unref(bridged_chan);
 	}
 
 	if (res) {
@@ -2175,16 +2177,28 @@ static void transfer_refer(struct ast_sip_session *session, const char *target)
 				chan_name, target, replaces);
 	}
 
-	if (PJSIP_URI_SCHEME_IS_SIP(session->request_uri))
-		snprintf(referto, sizeof(referto), "<sip:%s@%s%s>", target, session->endpoint->fromdomain, replaces);
-	else if (PJSIP_URI_SCHEME_IS_SIPS(session->request_uri))
-		snprintf(referto, sizeof(referto), "<sips:%s@%s%s>", target, session->endpoint->fromdomain, replaces);
-	else if (PJSIP_URI_SCHEME_IS_TEL(session->request_uri))
-		snprintf(referto, sizeof(referto), "<tel:%s@%s%s>", target, session->endpoint->fromdomain, replaces);
-	else {
+	char method[5] = {0};
+	if (session->request_uri) {
+		if (PJSIP_URI_SCHEME_IS_SIP(session->request_uri))
+			strncpy(method, "sip", sizeof(method) - 1);
+		else if (PJSIP_URI_SCHEME_IS_SIPS(session->request_uri))
+			strncpy(method, "sips", sizeof(method) - 1);
+		else if (PJSIP_URI_SCHEME_IS_TEL(session->request_uri))
+			strncpy(method, "tel", sizeof(method) - 1);
+		else {
+			ast_log(LOG_ERROR, "Unsupported URI for Refer-To header. Refer will not be sent!\n");
+			goto failure;
+		}
+
+	} else if (session->contact) {
+		char *contact_uri = ast_strdupa(session->contact->uri);
+		strncpy(method, strtok(contact_uri, ":"), sizeof(method) - 1);
+	} else {
 		ast_log(LOG_ERROR, "Unsupported URI for Refer-To header. Refer will not be sent!\n");
 		goto failure;
 	}
+
+	snprintf(referto, sizeof(referto), "<%s:%s@%s%s>", method, target, session->endpoint->fromdomain, replaces);
 	ast_debug(1, "Refer-To: %s\n", referto);
 
 	if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, referto), &packet) != PJ_SUCCESS) {
@@ -2194,9 +2208,6 @@ static void transfer_refer(struct ast_sip_session *session, const char *target)
 	// unattended call transfer
 	if (!chan_name) {
 		const char *callid = chan_pjsip_get_uniqueid(session->channel);
-		const char *totag;
-		const char *fromtag;
-
 		char local_tag[pj_strlen(&session->inv_session->dlg->local.info->tag) + 1];
 		char remote_tag[pj_strlen(&session->inv_session->dlg->remote.info->tag) + 1];
 
@@ -2212,16 +2223,7 @@ static void transfer_refer(struct ast_sip_session *session, const char *target)
 	if (!ast_strlen_zero(ref_by_val)) {
 		ast_sip_add_header(packet, "Referred-By", ref_by_val);
 	} else {
-		if (PJSIP_URI_SCHEME_IS_SIP(session->request_uri))
-			snprintf(local_info, sizeof(local_info), "<sip:%s@%s>", session->endpoint->contact_user, session->endpoint->fromdomain);
-		else if (PJSIP_URI_SCHEME_IS_SIPS(session->request_uri))
-			snprintf(local_info, sizeof(local_info), "<sips:%s@%s>", session->endpoint->contact_user, session->endpoint->fromdomain);
-		else if (PJSIP_URI_SCHEME_IS_TEL(session->request_uri))
-			snprintf(local_info, sizeof(local_info), "<tel:%s@%s>", session->endpoint->contact_user, session->endpoint->fromdomain);
-		else {
-			ast_log(LOG_ERROR, "Unsupported URI for Referred-By header. Refer will not be sent!\n");
-			goto failure;
-		}
+		snprintf(local_info, sizeof(local_info), "<%s:%s@%s>", method, session->endpoint->contact_user, session->endpoint->fromdomain);
 		ast_sip_add_header(packet, "Referred-By", local_info);
 	}
 
@@ -2258,12 +2260,7 @@ static int transfer(void *data)
 				target = contact->uri;
 			}
 		}
-
-		if (ast_channel_state(trnf_data->session->channel) == AST_STATE_RING) {
-			transfer_redirect(trnf_data->session, target);
-		} else {
-			transfer_refer(trnf_data->session, target);
-		}
+		transfer_refer(trnf_data->session, target);
 	}
 
 	ao2_ref(trnf_data, -1);
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index 5af0af6229..2c442426a9 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -64,6 +64,8 @@ struct refer_progress {
 	int sent_100;
 	/*! \brief Whether to notifies all the progress details on blind transfer */
 	unsigned int refer_blind_progress;
+	/*! \brief Pointer to Invite session state. */
+	unsigned int *invite_state;
 };
 
 /*! \brief REFER Progress notification structure */
@@ -153,6 +155,15 @@ static int refer_progress_notify(void *data)
 		return 0;
 	}
 
+	if (*notification->progress->invite_state != PJSIP_INV_STATE_CONFIRMED) {
+		ast_debug(3, "Not sending NOTIFY of response '%d' and state '%u' on progress monitor '%p' as invite session has been terminated\n",
+			notification->response, notification->state, notification->progress);
+		pjsip_evsub_set_mod_data(sub, refer_progress_module.id, NULL);
+		sub = NULL;
+		pjsip_dlg_dec_lock(notification->progress->dlg);
+		return 0;
+	}
+
 	/* Send a deferred initial 100 Trying SIP frag NOTIFY if we haven't already. */
 	if (!notification->progress->sent_100) {
 		notification->progress->sent_100 = 1;
@@ -421,6 +432,7 @@ static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *
 
 	/* To prevent a potential deadlock we need the dialog so we can lock/unlock */
 	(*progress)->dlg = session->inv_session->dlg;
+	(*progress)->invite_state = &session->inv_session->state;
 	/* We also need to make sure it stays around until we're done with it */
 	pjsip_dlg_inc_session((*progress)->dlg, &refer_progress_module);
 
-- 
GitLab