From 51b0ca2db039f9479dc01a345f00728e36e84699 Mon Sep 17 00:00:00 2001
From: Naveen Albert <asterisk@phreaknet.org>
Date: Mon, 1 Apr 2024 17:16:29 -0400
Subject: [PATCH] callerid.c: Parse previously ignored Caller ID parameters.

Commit f2f397c1a8cc48913434ebb297f0ff50d96993db previously
made it possible to send Caller ID parameters to FXS stations
which, prior to that, could not be sent.

This change is complementary in that we now handle receiving
all these parameters on FXO lines and provide these up to
the dialplan, via chan_dahdi. In particular:

* If a redirecting reason is provided, the channel's redirecting
  reason is set. No redirecting number is set, since there is
  no parameter for this in the Caller ID protocol, but the reason
  can be checked to determine if and why a call was forwarded.
* If the Call Qualifier parameter is received, the Call Qualifier
  variable is set.
* Some comments have been added to explain why some of the code
  is the way it is, to assist other people looking at it.

With this change, Asterisk's Caller ID implementation is now
reasonably complete for both FXS and FXO operation.

Resolves: #681
(cherry picked from commit 44381b2fe90f7e9663a179a2ab27048f994f75d1)
---
 channels/chan_dahdi.c       |  18 +++++-
 funcs/func_callerid.c       |   2 +
 include/asterisk/callerid.h |  18 +++++-
 main/callerid.c             | 123 ++++++++++++++++++++++++++++++++----
 4 files changed, 144 insertions(+), 17 deletions(-)

diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 1e075224fb..81d8b6d19a 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -1436,6 +1436,7 @@ static int my_get_callerid(void *pvt, char *namebuf, char *numbuf, enum analog_e
 	int res;
 	unsigned char buf[256];
 	int flags;
+	int redirecting;
 
 	poller.fd = p->subs[SUB_REAL].dfd;
 	poller.events = POLLPRI | POLLIN;
@@ -1482,7 +1483,8 @@ static int my_get_callerid(void *pvt, char *namebuf, char *numbuf, enum analog_e
 		}
 
 		if (res == 1) {
-			callerid_get(p->cs, &name, &num, &flags);
+			struct ast_channel *chan = analog_p->ss_astchan;
+			callerid_get_with_redirecting(p->cs, &name, &num, &flags, &redirecting);
 			if (name)
 				ast_copy_string(namebuf, name, ANALOG_MAX_CID);
 			if (num)
@@ -1490,7 +1492,6 @@ static int my_get_callerid(void *pvt, char *namebuf, char *numbuf, enum analog_e
 
 			if (flags & (CID_PRIVATE_NUMBER | CID_UNKNOWN_NUMBER)) {
 				/* If we got a presentation, we must set it on the channel */
-				struct ast_channel *chan = analog_p->ss_astchan;
 				struct ast_party_caller caller;
 
 				ast_party_caller_set_init(&caller, ast_channel_caller(chan));
@@ -1499,8 +1500,19 @@ static int my_get_callerid(void *pvt, char *namebuf, char *numbuf, enum analog_e
 				ast_party_caller_set(ast_channel_caller(chan), &caller, NULL);
 				ast_party_caller_free(&caller);
 			}
+			if (redirecting) {
+				/* There is a redirecting reason available in the Caller*ID received.
+				 * No idea what the redirecting number is, since the Caller*ID protocol
+				 * has no parameter for that, but at least we know WHY it was redirected. */
+				ast_channel_redirecting(chan)->reason.code = redirecting;
+			}
+
+			if (flags & CID_QUALIFIER) {
+				/* This is the inverse of how the qualifier is set in sig_analog */
+				pbx_builtin_setvar_helper(chan, "CALL_QUALIFIER", "1");
+			}
 
-			ast_debug(1, "CallerID number: %s, name: %s, flags=%d\n", num, name, flags);
+			ast_debug(1, "CallerID number: %s, name: %s, flags=%d, redirecting=%s\n", num, name, flags, ast_redirecting_reason_name(&ast_channel_redirecting(chan)->reason));
 			return 0;
 		}
 	}
diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c
index 850db5c906..1f962aacc9 100644
--- a/funcs/func_callerid.c
+++ b/funcs/func_callerid.c
@@ -192,6 +192,8 @@
 					You are responsible for setting it if/when needed.</para>
 					<para>Supporting Caller ID units will display the LDC
 					(Long Distance Call) indicator when they receive this parameter.</para>
+					<para>For incoming calls on FXO ports, if the Call Qualifier parameter is received,
+					this variable will also be set to 1.</para>
 					<para>This option must be used with a channel driver
 					that allows Asterisk to generate the Caller ID spill,
 					which currently only includes <literal>chan_dahdi</literal>.</para>
diff --git a/include/asterisk/callerid.h b/include/asterisk/callerid.h
index 77b5822ba1..3c6d9c3b7c 100644
--- a/include/asterisk/callerid.h
+++ b/include/asterisk/callerid.h
@@ -190,15 +190,29 @@ int callerid_feed_jp(struct callerid_state *cid, unsigned char *ubuf, int sample
  * \param cid Callerid state machine to act upon
  * \param number Pass the address of a pointer-to-char (will contain the phone number)
  * \param name Pass the address of a pointer-to-char (will contain the name)
- * \param flags Pass the address of an int variable(will contain the various callerid flags)
+ * \param flags Pass the address of an int variable (will contain the various callerid flags - presentation flags and call qualifier)
  *
  * \details
  * This function extracts a callerid string out of a callerid_state state machine.
  * If no number is found, *number will be set to NULL.  Likewise for the name.
- * Flags can contain any of the following:
+ * Flags can contain any of the following: CID_PRIVATE_NAME, CID_PRIVATE_NUMBER, CID_UNKNOWN_NAME, CID_UNKNOWN_NUMBER, CID_MSGWAITING, CID_NOMSGWAITING, CID_QUALIFIER
  */
 void callerid_get(struct callerid_state *cid, char **number, char **name, int *flags);
 
+/*! \brief Extract info out of callerID state machine.  Flags are listed above
+ * \param cid Callerid state machine to act upon
+ * \param[out] number Pass the address of a pointer-to-char (will contain the phone number)
+ * \param[out] name Pass the address of a pointer-to-char (will contain the name)
+ * \param[out] flags Pass the address of an int variable (will contain the various callerid flags)
+ * \param[out] redirecting Pass the address of an int variable (will contain the redirecting reason, if received - presentation flags and call qualifier)
+ *
+ * \details
+ * This function extracts a callerid string out of a callerid_state state machine.
+ * If no number is found, *number will be set to NULL.  Likewise for the name.
+ * Flags can contain any of the following: CID_PRIVATE_NAME, CID_PRIVATE_NUMBER, CID_UNKNOWN_NAME, CID_UNKNOWN_NUMBER, CID_MSGWAITING, CID_NOMSGWAITING, CID_QUALIFIER
+ */
+void callerid_get_with_redirecting(struct callerid_state *cid, char **name, char **number, int *flags, int *redirecting);
+
 /*!
  * \brief Get and parse DTMF-based callerid
  * \param cidstring The actual transmitted string.
diff --git a/main/callerid.c b/main/callerid.c
index 9755c456a7..da0e7fc68c 100644
--- a/main/callerid.c
+++ b/main/callerid.c
@@ -53,6 +53,7 @@ struct callerid_state {
 	char name[64];
 	char number[64];
 	int flags;
+	int redirecting;
 	int sawflag;
 	int len;
 
@@ -185,17 +186,26 @@ struct callerid_state *callerid_new(int cid_signalling)
 	return cid;
 }
 
-void callerid_get(struct callerid_state *cid, char **name, char **number, int *flags)
+void callerid_get_with_redirecting(struct callerid_state *cid, char **name, char **number, int *flags, int *redirecting)
 {
 	*flags = cid->flags;
-	if (cid->flags & (CID_UNKNOWN_NAME | CID_PRIVATE_NAME))
+	if (cid->flags & (CID_UNKNOWN_NAME | CID_PRIVATE_NAME)) {
 		*name = NULL;
-	else
+	} else {
 		*name = cid->name;
-	if (cid->flags & (CID_UNKNOWN_NUMBER | CID_PRIVATE_NUMBER))
+	}
+	if (cid->flags & (CID_UNKNOWN_NUMBER | CID_PRIVATE_NUMBER)) {
 		*number = NULL;
-	else
+	} else {
 		*number = cid->number;
+	}
+	*redirecting = cid->redirecting;
+}
+
+void callerid_get(struct callerid_state *cid, char **name, char **number, int *flags)
+{
+	int redirecting;
+	return callerid_get_with_redirecting(cid, name, number, flags, &redirecting);
 }
 
 void callerid_get_dtmf(char *cidstring, char *number, int *flags)
@@ -541,6 +551,21 @@ int callerid_feed_jp(struct callerid_state *cid, unsigned char *ubuf, int len, s
 	return 0;
 }
 
+static const char *mdmf_param_name(int param)
+{
+	switch (param) {
+	case 0x1: return "Date/Time";
+	case 0x2: return "Caller Number";
+	case 0x3: return "DNIS";
+	case 0x4: return "Reason For Absence of Number";
+	case 0x5: return "Reason For Redirection";
+	case 0x6: return "Call Qualifier";
+	case 0x7: return "Name";
+	case 0x8: return "Reason For Absence of Name";
+	case 0xB: return "Message Waiting";
+	default: return "Unknown";
+	}
+}
 
 int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, struct ast_format *codec)
 {
@@ -631,18 +656,41 @@ int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, stru
 				cid->name[0] = '\0';
 				/* Update flags */
 				cid->flags = 0;
+				cid->redirecting = 0;
 				/* If we get this far we're fine.  */
 				if ((cid->type == 0x80) || (cid->type == 0x82)) {
 					/* MDMF */
+					ast_debug(6, "%s Caller*ID spill received\n", cid->type == 0x80 ? "MDMF" : "MDMF Message Waiting");
 					/* Go through each element and process */
 					for (x = 0; x < cid->pos;) {
-						switch (cid->rawdata[x++]) {
+						int param = cid->rawdata[x++];
+						ast_debug(7, "Caller*ID parameter %d (%s), length %d\n", param, mdmf_param_name(param), cid->rawdata[x]);
+						switch (param) {
 						case 1:
-							/* Date */
+							/* Date/Time... in theory we could synchronize our time according to the Caller*ID,
+							 * but it would be silly for a telephone switch to do that. */
 							break;
+						/* For MDMF spills, we would expect to get an "O" or a "P"
+						 * for paramter 4 (or 8) as opposed to 2 (or 7) to indicate
+						 * a blocked or out of area presentation.
+						 * However, for SDMF, which doesn't have parameters,
+						 * there is no differentiation, which is why the logic below
+						 * just checks the number and name field and here, we use the same
+						 * parsing logic for both parameters. Technically, it would be wrong
+						 * to receive an 'O' or 'P' for parameters 2 or 7 and treat it as
+						 * the reason for absence fields, but that is not likely to happen,
+						 * and if it did, could possibly be buggy Caller ID generation we would
+						 * want to treat the same way, anyways.
+						 *
+						 * The "Dialable Directory Number" is how the number would be called back.
+						 * Asterisk doesn't really have a corresponding thing for this,
+						 * so log it if we get it for debugging, but treat the same otherwise.
+						 */
+						case 3: /* Dialable Directory Number / Number (for Zebble) */
+							ast_debug(3, "Caller*ID Dialable Directory Number: '%.*s'\n", cid->rawdata[x], cid->rawdata + x + 1);
+							/* Fall through */
 						case 2: /* Number */
-						case 3: /* Number (for Zebble) */
-						case 4: /* Number */
+						case 4: /* Reason for Absence of Number. */
 							res = cid->rawdata[x];
 							if (res > 32) {
 								ast_log(LOG_NOTICE, "Truncating long caller ID number from %d bytes to 32\n", cid->rawdata[x]);
@@ -654,10 +702,43 @@ int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, stru
 								cid->number[res] = '\0';
 							}
 							break;
+						case 5: /* Reason for Redirection */
+							res = cid->rawdata[x];
+							if (res != 1) {
+								ast_log(LOG_WARNING, "Redirecting parameter length is %d?\n", res);
+								break;
+							}
+							switch (*(cid->rawdata + x + 1)) {
+							case 0x1:
+								cid->redirecting = AST_REDIRECTING_REASON_USER_BUSY;
+								break;
+							case 0x2:
+								cid->redirecting = AST_REDIRECTING_REASON_NO_ANSWER;
+								break;
+							case 0x3:
+								cid->redirecting = AST_REDIRECTING_REASON_UNCONDITIONAL;
+								break;
+							case 0x4:
+								cid->redirecting = AST_REDIRECTING_REASON_CALL_FWD_DTE;
+								break;
+							case 0x5:
+								cid->redirecting = AST_REDIRECTING_REASON_DEFLECTION;
+								break;
+							default:
+								ast_log(LOG_WARNING, "Redirecting reason is %02x?\n", *(cid->rawdata + x + 1));
+								break;
+							}
+							break;
 						case 6: /* Stentor Call Qualifier (ie. Long Distance call) */
+							res = cid->rawdata[x];
+							if (res == 1 && *(cid->rawdata + x + 1) == 'L') {
+								cid->flags |= CID_QUALIFIER;
+							} else if (res >= 1) {
+								ast_debug(2, "Invalid value (len %d) received for Call Qualifier: '%c'\n", res, *(cid->rawdata + x + 1));
+							}
 							break;
 						case 7: /* Name */
-						case 8: /* Name */
+						case 8: /* Reason for Absence of Name */
 							res = cid->rawdata[x];
 							if (res > 32) {
 								ast_log(LOG_NOTICE, "Truncating long caller ID name from %d bytes to 32\n", cid->rawdata[x]);
@@ -692,6 +773,7 @@ int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, stru
 					}
 				} else if (cid->type == 0x6) {
 					/* VMWI SDMF */
+					ast_debug(6, "VMWI SDMF Caller*ID spill received\n");
 					if (cid->rawdata[2] == 0x42) {
 						cid->flags |= CID_MSGWAITING;
 					} else if (cid->rawdata[2] == 0x6f) {
@@ -699,21 +781,38 @@ int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, stru
 					}
 				} else {
 					/* SDMF */
+					ast_debug(6, "SDMF Caller*ID spill received\n");
 					ast_copy_string(cid->number, cid->rawdata + 8, sizeof(cid->number));
 				}
 				if (!strcmp(cid->number, "P")) {
+					ast_debug(6, "Caller*ID number is private\n");
 					strcpy(cid->number, "");
 					cid->flags |= CID_PRIVATE_NUMBER;
-				} else if (!strcmp(cid->number, "O") || ast_strlen_zero(cid->number)) {
+				} else if (!strcmp(cid->number, "O")) {
+					ast_debug(6, "Caller*ID number is out of area\n");
 					strcpy(cid->number, "");
 					cid->flags |= CID_UNKNOWN_NUMBER;
+				} else if (ast_strlen_zero(cid->number)) {
+					ast_debug(6, "No Caller*ID number provided, and no reason provided for its absence\n");
+					strcpy(cid->number, "");
+					cid->flags |= CID_UNKNOWN_NUMBER;
+				} else {
+					ast_debug(6, "Caller*ID number is '%s'\n", cid->number);
 				}
 				if (!strcmp(cid->name, "P")) {
+					ast_debug(6, "Caller*ID name is private\n");
 					strcpy(cid->name, "");
 					cid->flags |= CID_PRIVATE_NAME;
-				} else if (!strcmp(cid->name, "O") || ast_strlen_zero(cid->name)) {
+				} else if (!strcmp(cid->name, "O")) {
+					ast_debug(6, "Caller*ID name is out of area\n");
 					strcpy(cid->name, "");
 					cid->flags |= CID_UNKNOWN_NAME;
+				} else if (ast_strlen_zero(cid->name)) {
+					ast_debug(6, "No Caller*ID name provided, and no reason provided for its absence\n");
+					strcpy(cid->name, "");
+					cid->flags |= CID_UNKNOWN_NAME;
+				} else {
+					ast_debug(6, "Caller*ID name is '%s'\n", cid->name);
 				}
 				return 1;
 				break;
-- 
GitLab