[Asterisk-code-review] SIP diversion: Fix REDIRECTING(reason) value inconsistencies. (asterisk[13])

Richard Mudgett asteriskteam at digium.com
Tue Mar 1 20:15:31 CST 2016


Richard Mudgett has uploaded a new change for review.

  https://gerrit.asterisk.org/2326

Change subject: SIP diversion: Fix REDIRECTING(reason) value inconsistencies.
......................................................................

SIP diversion: Fix REDIRECTING(reason) value inconsistencies.

Previous chan_sip behavior:

Before this patch chan_sip would always strip any quotes from an incoming
reason and pass that value up as the REDIRECTING(reason).  For an outgoing
reason value, chan_sip would check the value against known values and
quote any it didn't recognize.  Incoming 480 response message reason text
was just assigned to the REDIRECTING(reason).

Previous chan_pjsip behavior:

Before this patch chan_pjsip would always pass the incoming reason value
up as the REDIRECTING(reason).  For an outgoing reason value, chan_pjsip
would send the reason value as passed down.

With this patch:

Both channel drivers match incoming reason values with values documented
by REDIRECTING(reason) and values documented by RFC5806 regardless of
whether they are quoted or not.  RFC5806 values are mapped to the
equivalent REDIRECTING(reason) documented value and is set in
REDIRECTING(reason).  e.g., an incoming RFC5806 'unconditional' value or a
quoted string version ('"unconditional"') is converted to
REDIRECTING(reason)'s 'cfu' value.  The user's dialplan only needs to deal
with 'cfu' instead of any of the aliases.

The incoming 480 response reason text supported by chan_sip checks for
known reason values and if not matched then puts quotes around the reason
string and assigns that to REDIRECTING(reason).

Both channel drivers send outgoing known REDIRECTING(reason) values as the
unquoted RFC5806 equivalent.  User custom values are either sent as is or
with added quotes if SIP doesn't allow a character within the value as
part of a RFC3261 Section 25.1 token.  Note that there are still
limitations on what characters can be put in a custom user value.  e.g.,
embedding quotes in the middle of the reason string is silly and just
going to cause you grief.

* Setting a REDIRECTING(reason) value now recognizes RFC5806 aliases.
e.g., Setting REDIRECTING(reason) to 'unconditional' is converted to the
'cfu' value.

* Added missing malloc() NULL return check in res_pjsip_diversion.c
set_redirecting_reason().

* Fixed potential read from a stale pointer in res_pjsip_diversion.c
add_diversion_header().  The reason string needed to be copied into the
tdata memory pool to ensure that the string would always be available.
Otherwise, if the reason string returned by reason_code_to_str() was a
user's reason string then the string could be freed later by another
thread.

Change-Id: Ifba83d23a195a9f64d55b9c681d2e62476b68a87
---
M CHANGES
M UPGRADE.txt
M channels/chan_sip.c
M main/callerid.c
M res/res_pjsip_diversion.c
5 files changed, 208 insertions(+), 95 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/26/2326/1

diff --git a/CHANGES b/CHANGES
index 0e84678..b1353ec 100644
--- a/CHANGES
+++ b/CHANGES
@@ -29,6 +29,36 @@
    conference state and made the locked column a yes/no value instead of a
    locked/unlocked value.
 
+REDIRECTING(reason)
+------------------
+ * The REDIRECTING(reason) value is now treated consistently between
+   chan_sip and chan_pjsip.
+
+   Both channel drivers match incoming reason values with values documented
+   by REDIRECTING(reason) and values documented by RFC5806 regardless of
+   whether they are quoted or not.  RFC5806 values are mapped to the
+   equivalent REDIRECTING(reason) documented value and is set in
+   REDIRECTING(reason).  e.g., an incoming RFC5806 'unconditional' value or a
+   quoted string version ('"unconditional"') is converted to
+   REDIRECTING(reason)'s 'cfu' value.  The user's dialplan only needs to deal
+   with 'cfu' instead of any of the aliases.
+
+   The incoming 480 response reason text supported by chan_sip checks for
+   known reason values and if not matched then puts quotes around the reason
+   string and assigns that to REDIRECTING(reason).
+
+   Both channel drivers send outgoing known REDIRECTING(reason) values as the
+   unquoted RFC5806 equivalent.  User custom values are either sent as is or
+   with added quotes if SIP doesn't allow a character within the value as
+   part of a RFC3261 Section 25.1 token.  Note that there are still
+   limitations on what characters can be put in a custom user value.  e.g.,
+   embedding quotes in the middle of the reason string is just going to cause
+   you grief.
+
+ * Setting a REDIRECTING(reason) value now recognizes RFC5806 aliases.
+   e.g., Setting REDIRECTING(reason) to 'unconditional' is converted to the
+   'cfu' value.
+
 res_pjproject
 ------------------
  * This module is the successor of res_pjsip_log_forwarder.  As well as
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 43458d6..471350c 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -28,6 +28,9 @@
    res_pjproject must be explicitly loaded or res_pjsip and all of its
    dependents will fail to load.
 
+REDIRECTING(reason):
+ - See the CHANGES file for a description of the behavior change.
+
 ODBC:
  - Connection pooling/sharing has been completely removed from Asterisk
    in favor of letting ODBC take care of it instead. It is strongly
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 4e0d811..e467b80 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -710,7 +710,7 @@
  */
 static const struct sip_reasons {
 	enum AST_REDIRECTING_REASON code;
-	char * const text;
+	const char *text;
 } sip_reason_table[] = {
 	{ AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
 	{ AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
@@ -723,8 +723,8 @@
 	{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
 	{ AST_REDIRECTING_REASON_AWAY, "away" },
-	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
-	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
+	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" },		/* Non-standard */
+	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" },	/* Non-standard */
 };
 
 
@@ -2370,52 +2370,54 @@
 	return errorvalue;
 }
 
-static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+/*!
+ * \internal
+ * \brief Determine if the given string is a SIP token.
+ * \since 13.8.0
+ *
+ * \param str String to determine if is a SIP token.
+ *
+ * \note A token is defined by RFC3261 Section 25.1
+ *
+ * \return Non-zero if the string is a SIP token.
+ */
+static int sip_is_token(const char *str)
 {
-	enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
-	int i;
+	int is_token;
 
-	for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
-		if (!strcasecmp(text, sip_reason_table[i].text)) {
-			ast = sip_reason_table[i].code;
-			break;
-		}
+	if (ast_strlen_zero(str)) {
+		/* An empty string is not a token. */
+		return 0;
 	}
 
-	return ast;
+	is_token = 1;
+	do {
+		if (!isalnum(*str)
+			&& !strchr("-.!%*_+`'~", *str)) {
+			/* The character is not allowed in a token. */
+			is_token = 0;
+			break;
+		}
+	} while (*++str);
+
+	return is_token;
 }
 
-static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason, int *table_lookup)
+static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason)
 {
-	int code = reason->code;
+	int idx;
+	int code;
 
-	/* If there's a specific string set, then we just
-	 * use it.
-	 */
+	/* use specific string if given */
 	if (!ast_strlen_zero(reason->str)) {
-		/* If we care about whether this can be found in
-		 * the table, then we need to check about that.
-		 */
-		if (table_lookup) {
-			/* If the string is literally "unknown" then don't bother with the lookup
-			 * because it can lead to a false negative.
-			 */
-			if (!strcasecmp(reason->str, "unknown") ||
-					sip_reason_str_to_code(reason->str) != AST_REDIRECTING_REASON_UNKNOWN) {
-				*table_lookup = TRUE;
-			} else {
-				*table_lookup = FALSE;
-			}
-		}
 		return reason->str;
 	}
 
-	if (table_lookup) {
-		*table_lookup = TRUE;
-	}
-
-	if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
-		return sip_reason_table[code].text;
+	code = reason->code;
+	for (idx = 0; idx < ARRAY_LEN(sip_reason_table); ++idx) {
+		if (code == sip_reason_table[idx].code) {
+			return sip_reason_table[idx].text;
+		}
 	}
 
 	return "unknown";
@@ -14218,7 +14220,7 @@
 {
 	struct ast_party_id diverting_from;
 	const char *reason;
-	int found_in_table;
+	const char *quote_str;
 	char header_text[256];
 	char encoded_number[SIPBUFSIZE/2];
 
@@ -14243,17 +14245,18 @@
 		ast_copy_string(encoded_number, diverting_from.number.str, sizeof(encoded_number));
 	}
 
-	reason = sip_reason_code_to_str(&ast_channel_redirecting(pvt->owner)->reason, &found_in_table);
+	reason = sip_reason_code_to_str(&ast_channel_redirecting(pvt->owner)->reason);
+
+	/* Reason is either already quoted or it is a token to not need quotes added. */
+	quote_str = *reason == '\"' || sip_is_token(reason) ? "" : "\"";
 
 	/* We at least have a number to place in the Diversion header, which is enough */
 	if (!diverting_from.name.valid
 		|| ast_strlen_zero(diverting_from.name.str)) {
 		snprintf(header_text, sizeof(header_text), "<sip:%s@%s>;reason=%s%s%s",
-				encoded_number,
-				ast_sockaddr_stringify_host_remote(&pvt->ourip),
-				found_in_table ? "" : "\"",
-				reason,
-				found_in_table ? "" : "\"");
+			encoded_number,
+			ast_sockaddr_stringify_host_remote(&pvt->ourip),
+			quote_str, reason, quote_str);
 	} else {
 		char escaped_name[SIPBUFSIZE/2];
 		if (sip_cfg.pedanticsipchecking) {
@@ -14262,12 +14265,10 @@
 			ast_copy_string(escaped_name, diverting_from.name.str, sizeof(escaped_name));
 		}
 		snprintf(header_text, sizeof(header_text), "\"%s\" <sip:%s@%s>;reason=%s%s%s",
-				escaped_name,
-				encoded_number,
-				ast_sockaddr_stringify_host_remote(&pvt->ourip),
-				found_in_table ? "" : "\"",
-				reason,
-				found_in_table ? "" : "\"");
+			escaped_name,
+			encoded_number,
+			ast_sockaddr_stringify_host_remote(&pvt->ourip),
+			quote_str, reason, quote_str);
 	}
 
 	add_header(req, "Diversion", header_text);
@@ -17696,6 +17697,9 @@
 	char *params, *reason_param = NULL;
 	struct sip_request *req;
 
+	ast_assert(reason_code != NULL);
+	ast_assert(reason_str != NULL);
+
 	req = oreq ? oreq : &p->initreq;
 
 	ast_copy_string(tmp, sip_get_header(req, "Diversion"), sizeof(tmp));
@@ -17729,16 +17733,6 @@
 			if ((end = strchr(reason_param, ';'))) {
 				*end = '\0';
 			}
-			/* Remove enclosing double-quotes */
-			if (*reason_param == '"')
-				reason_param = ast_strip_quoted(reason_param, "\"", "\"");
-			if (!ast_strlen_zero(reason_param)) {
-				sip_set_redirstr(p, reason_param);
-				if (p->owner) {
-					pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause);
-					pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason_param);
-				}
-			}
 		}
 	}
 
@@ -17770,12 +17764,27 @@
 	}
 
 	if (!ast_strlen_zero(reason_param)) {
-		if (reason_code) {
-			*reason_code = sip_reason_str_to_code(reason_param);
+		*reason_str = ast_strdup(reason_param);
+
+		/* Remove any enclosing double-quotes */
+		if (*reason_param == '"') {
+			reason_param = ast_strip_quoted(reason_param, "\"", "\"");
 		}
 
-		if (reason_str) {
-			*reason_str = ast_strdup(reason_param);
+		*reason_code = ast_redirecting_reason_parse(reason_param);
+		if (*reason_code < 0) {
+			*reason_code = AST_REDIRECTING_REASON_UNKNOWN;
+		} else {
+			ast_free(*reason_str);
+			*reason_str = ast_strdup("");
+		}
+
+		if (!ast_strlen_zero(reason_param)) {
+			sip_set_redirstr(p, reason_param);
+			if (p->owner) {
+				pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause);
+				pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason_param);
+			}
 		}
 	}
 
@@ -22650,10 +22659,11 @@
 		redirecting->to.name.str = redirecting_to_name;
 	}
 	redirecting->reason.code = reason;
+	ast_free(redirecting->reason.str);
+	redirecting->reason.str = reason_str;
 	if (reason_str) {
-		ast_debug(3, "Got redirecting reason %s\n", reason_str);
-		ast_free(redirecting->reason.str);
-		redirecting->reason.str = reason_str;
+		ast_debug(3, "Got redirecting reason %s\n", ast_strlen_zero(reason_str)
+			? sip_reason_code_to_str(&redirecting->reason) : reason_str);
 	}
 }
 
@@ -23453,14 +23463,22 @@
 		if (p->owner && !req->ignore) {
 			struct ast_party_redirecting redirecting;
 			struct ast_set_party_redirecting update_redirecting;
+			char *quoted_rest = ast_alloca(strlen(rest) + 3);
+
 			ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(p->owner));
 			memset(&update_redirecting, 0, sizeof(update_redirecting));
 
-			redirecting.reason.code = sip_reason_str_to_code(rest);
-			redirecting.reason.str = ast_strdup(rest);
+			redirecting.reason.code = ast_redirecting_reason_parse(rest);
+			if (redirecting.reason.code < 0) {
+				sprintf(quoted_rest, "\"%s\"", rest);/* Safe */
+
+				redirecting.reason.code = AST_REDIRECTING_REASON_UNKNOWN;
+				redirecting.reason.str = quoted_rest;
+			} else {
+				redirecting.reason.str = "";
+			}
 
 			ast_channel_queue_redirecting_update(p->owner, &redirecting, &update_redirecting);
-			ast_party_redirecting_free(&redirecting);
 
 			ast_queue_control(p->owner, AST_CONTROL_BUSY);
 		}
diff --git a/main/callerid.c b/main/callerid.c
index a99dafc..a9864da 100644
--- a/main/callerid.c
+++ b/main/callerid.c
@@ -1209,7 +1209,16 @@
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER,   "out_of_order", "Called DTE Out-Of-Order" },
 	{ AST_REDIRECTING_REASON_AWAY,           "away",         "Callee is Away" },
 	{ AST_REDIRECTING_REASON_CALL_FWD_DTE,   "cf_dte",       "Call Forwarding By The Called DTE" },
-	{ AST_REDIRECTING_REASON_SEND_TO_VM,     "send_to_vm",   "Call is being redirected to user's voicemail"},
+	{ AST_REDIRECTING_REASON_SEND_TO_VM,     "send_to_vm",   "Call is being redirected to user's voicemail" },
+
+	/* Convenience SIP aliases.  Alias descriptions are not used. */
+	{ AST_REDIRECTING_REASON_USER_BUSY,      "user-busy" },
+	{ AST_REDIRECTING_REASON_NO_ANSWER,      "no-answer" },
+	{ AST_REDIRECTING_REASON_UNCONDITIONAL,  "unconditional" },
+	{ AST_REDIRECTING_REASON_TIME_OF_DAY,    "time-of-day" },
+	{ AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" },
+	{ AST_REDIRECTING_REASON_FOLLOW_ME,      "follow-me" },
+	{ AST_REDIRECTING_REASON_OUT_OF_ORDER,   "out-of-service" },
 /* *INDENT-ON* */
 };
 
@@ -1232,7 +1241,7 @@
 
 	for (index = 0; index < ARRAY_LEN(redirecting_reason_types); ++index) {
 		if (redirecting_reason_types[index].value == data) {
-			return redirecting_reason_types[index].description;
+			return redirecting_reason_types[index].description ?: "Redirecting reason alias-bug";
 		}
 	}
 
diff --git a/res/res_pjsip_diversion.c b/res/res_pjsip_diversion.c
index 4d9aca4..41e6c82 100644
--- a/res/res_pjsip_diversion.c
+++ b/res/res_pjsip_diversion.c
@@ -37,6 +37,39 @@
 
 static const pj_str_t diversion_name = { "Diversion", 9 };
 
+/*!
+ * \internal
+ * \brief Determine if the given string is a SIP token.
+ * \since 13.8.0
+ *
+ * \param str String to determine if is a SIP token.
+ *
+ * \note A token is defined by RFC3261 Section 25.1
+ *
+ * \return Non-zero if the string is a SIP token.
+ */
+static int sip_is_token(const char *str)
+{
+	int is_token;
+
+	if (ast_strlen_zero(str)) {
+		/* An empty string is not a token. */
+		return 0;
+	}
+
+	is_token = 1;
+	do {
+		if (!isalnum(*str)
+			&& !strchr("-.!%*_+`'~", *str)) {
+			/* The character is not allowed in a token. */
+			is_token = 0;
+			break;
+		}
+	} while (*++str);
+
+	return is_token;
+}
+
 /*! \brief Diversion header reasons
  *
  * The core defines a bunch of constants used to define
@@ -46,7 +79,7 @@
  */
 static const struct reasons {
 	enum AST_REDIRECTING_REASON code;
-	char *const text;
+	const char *text;
 } reason_table[] = {
 	{ AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
 	{ AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
@@ -59,39 +92,28 @@
 	{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
 	{ AST_REDIRECTING_REASON_AWAY, "away" },
-	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
-	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
+	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" },		/* Non-standard */
+	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" },	/* Non-standard */
 };
 
 static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason)
 {
-	int code = reason->code;
+	int idx;
+	int code;
 
 	/* use specific string if given */
 	if (!ast_strlen_zero(reason->str)) {
 		return reason->str;
 	}
 
-	if (code >= 0 && code < ARRAY_LEN(reason_table)) {
-		return reason_table[code].text;
-	}
-
-	return "unknown";
-}
-
-static enum AST_REDIRECTING_REASON reason_str_to_code(const char *text)
-{
-	enum AST_REDIRECTING_REASON code = AST_REDIRECTING_REASON_UNKNOWN;
-	int i;
-
-	for (i = 0; i < ARRAY_LEN(reason_table); ++i) {
-		if (!strcasecmp(text, reason_table[i].text)) {
-			code = reason_table[i].code;
-			break;
+	code = reason->code;
+	for (idx = 0; idx < ARRAY_LEN(reason_table); ++idx) {
+		if (code == reason_table[idx].code) {
+			return reason_table[idx].text;
 		}
 	}
 
-	return code;
+	return "unknown";
 }
 
 static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
@@ -159,13 +181,31 @@
 {
 	static const pj_str_t reason_name = { "reason", 6 };
 	pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name);
+	char *reason_str;
 
 	if (!reason) {
 		return;
 	}
 
 	set_redirecting_value(&data->str, &reason->value);
-	data->code = reason_str_to_code(data->str);
+	if (!data->str) {
+		/* Oops, allocation failure */
+		return;
+	}
+	reason_str = ast_strdupa(data->str);
+
+	/* Remove any enclosing double-quotes */
+	if (*reason_str == '"') {
+		reason_str = ast_strip_quoted(reason_str, "\"", "\"");
+	}
+
+	data->code = ast_redirecting_reason_parse(reason_str);
+	if (data->code < 0) {
+		data->code = AST_REDIRECTING_REASON_UNKNOWN;
+	} else {
+		ast_free(data->str);
+		data->str = ast_strdup("");
+	}
 }
 
 static void set_redirecting(struct ast_sip_session *session,
@@ -251,6 +291,9 @@
 	pjsip_sip_uri *uri;
 	pjsip_param *param;
 	pjsip_fromto_hdr *old_hdr;
+	const char *reason_str;
+	const char *quote_str;
+	char *reason_buf;
 
 	struct ast_party_id *id = &data->from;
 	pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
@@ -272,7 +315,17 @@
 
 	param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
 	param->name = pj_str("reason");
-	param->value = pj_str((char*)reason_code_to_str(&data->reason));
+
+	reason_str = reason_code_to_str(&data->reason);
+
+	/* Reason is either already quoted or it is a token to not need quotes added. */
+	quote_str = *reason_str == '\"' || sip_is_token(reason_str) ? "" : "\"";
+
+	reason_buf = pj_pool_alloc(tdata->pool, strlen(reason_str) + 3);
+	sprintf(reason_buf, "%s%s%s", quote_str, reason_str, quote_str);/* Safe */
+
+	param->value = pj_str(reason_buf);
+
 	pj_list_insert_before(&hdr->other_param, param);
 
 	hdr->uri = (pjsip_uri *) name_addr;

-- 
To view, visit https://gerrit.asterisk.org/2326
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ifba83d23a195a9f64d55b9c681d2e62476b68a87
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-Owner: Richard Mudgett <rmudgett at digium.com>



More information about the asterisk-code-review mailing list