[Asterisk-code-review] res_pjsip_session: Use Caller ID for extension matching. (asterisk[20])

George Joseph asteriskteam at digium.com
Tue Dec 20 14:13:20 CST 2022


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/19731 )

Change subject: res_pjsip_session: Use Caller ID for extension matching.
......................................................................

res_pjsip_session: Use Caller ID for extension matching.

Currently, there is no Caller ID available to us when
checking for an extension match when handling INVITEs.
As a result, extension patterns that depend on the Caller ID
are not matched and calls may be incorrectly rejected.

The Caller ID is not available because the supplement that
adds Caller ID to the session does not execute until after
this check. Supplement callbacks cannot yet be executed
at this point since the session is not yet in the appropriate
state.

To fix this without impacting existing behavior, the Caller ID
number is now retrieved before attempting to pattern match.
This ensures pattern matching works correctly and there is
no behavior change to the way supplements are called.

ASTERISK-28767 #close

Change-Id: Iec7f5a3b90e51b65ccf74342f96bf80314b7cfc7
---
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
M res/res_pjsip_caller_id.c
M res/res_pjsip_session.c
4 files changed, 298 insertions(+), 226 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved; Approved for Submit




diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 35c7339..3e19d8d 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -3604,6 +3604,25 @@
 void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes);
 
 /*!
+ * \brief Set the ID for a connected line update
+ *
+ * \retval -1 on failure, 0 on success
+ */
+int ast_sip_set_id_connected_line(struct pjsip_rx_data *rdata, struct ast_party_id *id);
+
+/*!
+ * \brief Set the ID from an INVITE
+ *
+ * \param rdata
+ * \param id ID structure to fill
+ * \param default_id Default ID structure with data to use (for non-trusted endpoints)
+ * \param trusted_inbound Whether or not the endpoint is trusted (controls whether PAI or RPID can be used)
+ *
+ * \retval -1 on failure, 0 on success
+ */
+int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id *id, struct ast_party_id *default_id, int trust_inbound);
+
+/*!
  * \brief Set name and number information on an identity header.
  *
  * \param pool Memory pool to use for string duplication
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 208a277..38a91bd 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -41,6 +41,7 @@
 #include "asterisk/sorcery.h"
 #include "asterisk/file.h"
 #include "asterisk/cli.h"
+#include "asterisk/callerid.h"
 #include "asterisk/res_pjsip_cli.h"
 #include "asterisk/test.h"
 #include "asterisk/res_pjsip_presence_xml.h"
@@ -2450,6 +2451,234 @@
 }
 
 /*!
+ * \internal
+ * \brief Set an ast_party_id name and number based on an identity header.
+ * \param hdr From, P-Asserted-Identity, or Remote-Party-ID header on incoming message
+ * \param[out] id The ID to set data on
+ */
+static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id)
+{
+	char cid_name[AST_CHANNEL_NAME];
+	char cid_num[AST_CHANNEL_NAME];
+	pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri;
+	char *semi;
+
+	ast_copy_pj_str(cid_name, &id_name_addr->display, sizeof(cid_name));
+	ast_copy_pj_str(cid_num, ast_sip_pjsip_uri_get_username(hdr->uri), sizeof(cid_num));
+
+	/* Always truncate caller-id number at a semicolon. */
+	semi = strchr(cid_num, ';');
+	if (semi) {
+		/*
+		 * We need to be able to handle URI's looking like
+		 * "sip:1235557890;phone-context=national at x.x.x.x;user=phone"
+		 *
+		 * Where the uri->user field will result in:
+		 * "1235557890;phone-context=national"
+		 *
+		 * People don't care about anything after the semicolon
+		 * showing up on their displays even though the RFC
+		 * allows the semicolon.
+		 */
+		*semi = '\0';
+	}
+
+	ast_free(id->name.str);
+	id->name.str = ast_strdup(cid_name);
+	if (!ast_strlen_zero(cid_name)) {
+		id->name.valid = 1;
+	}
+	ast_free(id->number.str);
+	id->number.str = ast_strdup(cid_num);
+	if (!ast_strlen_zero(cid_num)) {
+		id->number.valid = 1;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get a P-Asserted-Identity or Remote-Party-ID header from an incoming message
+ *
+ * This function will parse the header as if it were a From header. This allows for us
+ * to easily manipulate the URI, as well as add, modify, or remove parameters from the
+ * header
+ *
+ * \param rdata The incoming message
+ * \param header_name The name of the ID header to find
+ * \retval NULL No ID header present or unable to parse ID header
+ * \retval non-NULL The parsed ID header
+ */
+static pjsip_fromto_hdr *get_id_header(pjsip_rx_data *rdata, const pj_str_t *header_name)
+{
+	static const pj_str_t from = { "From", 4 };
+	pj_str_t header_content;
+	pjsip_fromto_hdr *parsed_hdr;
+	pjsip_generic_string_hdr *ident = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+			header_name, NULL);
+	int parsed_len;
+
+	if (!ident) {
+		return NULL;
+	}
+
+	pj_strdup_with_null(rdata->tp_info.pool, &header_content, &ident->hvalue);
+
+	parsed_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from, header_content.ptr,
+			pj_strlen(&header_content), &parsed_len);
+
+	if (!parsed_hdr) {
+		return NULL;
+	}
+
+	return parsed_hdr;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a P-Asserted-Identity header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the contents of a Privacy header in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Successfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_pai(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	static const pj_str_t pai_str = { "P-Asserted-Identity", 19 };
+	static const pj_str_t privacy_str = { "Privacy", 7 };
+	pjsip_fromto_hdr *pai_hdr = get_id_header(rdata, &pai_str);
+	pjsip_generic_string_hdr *privacy;
+
+	if (!pai_hdr) {
+		return -1;
+	}
+
+	set_id_from_hdr(pai_hdr, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	privacy = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &privacy_str, NULL);
+	if (!privacy || !pj_stricmp2(&privacy->hvalue, "none")) {
+		id->number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+		id->name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+	} else {
+		id->number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+		id->name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a Remote-Party-ID header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the privacy and screen parameters in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_rpid(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	static const pj_str_t rpid_str = { "Remote-Party-ID", 15 };
+	static const pj_str_t privacy_str = { "privacy", 7 };
+	static const pj_str_t screen_str = { "screen", 6 };
+	pjsip_fromto_hdr *rpid_hdr = get_id_header(rdata, &rpid_str);
+	pjsip_param *screen;
+	pjsip_param *privacy;
+
+	if (!rpid_hdr) {
+		return -1;
+	}
+
+	set_id_from_hdr(rpid_hdr, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	privacy = pjsip_param_find(&rpid_hdr->other_param, &privacy_str);
+	screen = pjsip_param_find(&rpid_hdr->other_param, &screen_str);
+	if (privacy && !pj_stricmp2(&privacy->value, "full")) {
+		id->number.presentation = AST_PRES_RESTRICTED;
+		id->name.presentation = AST_PRES_RESTRICTED;
+	} else {
+		id->number.presentation = AST_PRES_ALLOWED;
+		id->name.presentation = AST_PRES_ALLOWED;
+	}
+	if (screen && !pj_stricmp2(&screen->value, "yes")) {
+		id->number.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+		id->name.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+	} else {
+		id->number.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
+		id->name.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a From
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * no information from the message in order to set privacy. It relies on endpoint
+ * configuration for privacy information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_from(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	pjsip_fromto_hdr *from = pjsip_msg_find_hdr(rdata->msg_info.msg,
+			PJSIP_H_FROM, rdata->msg_info.msg->hdr.next);
+
+	if (!from) {
+		/* This had better not happen */
+		return -1;
+	}
+
+	set_id_from_hdr(from, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_sip_set_id_connected_line(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	return !set_id_from_pai(rdata, id) || !set_id_from_rpid(rdata, id) ? 0 : -1;
+}
+
+int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id *id, struct ast_party_id *default_id, int trust_inbound)
+{
+	if (trust_inbound && (!set_id_from_pai(rdata, id) || !set_id_from_rpid(rdata, id))) {
+		/* Trusted: Check PAI and RPID */
+		ast_free(id->tag);
+		id->tag = ast_strdup(default_id->tag);
+		return 0;
+	}
+	/* Not trusted: check the endpoint config or use From. */
+	ast_party_id_copy(id, default_id);
+	if (!default_id->number.valid) {
+		set_id_from_from(rdata, id);
+	}
+	return 0;
+}
+
+/*!
  * \brief Set name and number information on an identity header.
  *
  * \param pool Memory pool to use for string duplication
diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c
index 64fd8c7..b11e590 100644
--- a/res/res_pjsip_caller_id.c
+++ b/res/res_pjsip_caller_id.c
@@ -37,89 +37,6 @@
 
 /*!
  * \internal
- * \brief Set an ast_party_id name and number based on an identity header.
- * \param hdr From, P-Asserted-Identity, or Remote-Party-ID header on incoming message
- * \param[out] id The ID to set data on
- */
-static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id)
-{
-	char cid_name[AST_CHANNEL_NAME];
-	char cid_num[AST_CHANNEL_NAME];
-	pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri;
-	char *semi;
-
-	ast_copy_pj_str(cid_name, &id_name_addr->display, sizeof(cid_name));
-	ast_copy_pj_str(cid_num, ast_sip_pjsip_uri_get_username(hdr->uri), sizeof(cid_num));
-
-	/* Always truncate caller-id number at a semicolon. */
-	semi = strchr(cid_num, ';');
-	if (semi) {
-		/*
-		 * We need to be able to handle URI's looking like
-		 * "sip:1235557890;phone-context=national at x.x.x.x;user=phone"
-		 *
-		 * Where the uri->user field will result in:
-		 * "1235557890;phone-context=national"
-		 *
-		 * People don't care about anything after the semicolon
-		 * showing up on their displays even though the RFC
-		 * allows the semicolon.
-		 */
-		*semi = '\0';
-	}
-
-	ast_free(id->name.str);
-	id->name.str = ast_strdup(cid_name);
-	if (!ast_strlen_zero(cid_name)) {
-		id->name.valid = 1;
-	}
-	ast_free(id->number.str);
-	id->number.str = ast_strdup(cid_num);
-	if (!ast_strlen_zero(cid_num)) {
-		id->number.valid = 1;
-	}
-}
-
-/*!
- * \internal
- * \brief Get a P-Asserted-Identity or Remote-Party-ID header from an incoming message
- *
- * This function will parse the header as if it were a From header. This allows for us
- * to easily manipulate the URI, as well as add, modify, or remove parameters from the
- * header
- *
- * \param rdata The incoming message
- * \param header_name The name of the ID header to find
- * \retval NULL No ID header present or unable to parse ID header
- * \retval non-NULL The parsed ID header
- */
-static pjsip_fromto_hdr *get_id_header(pjsip_rx_data *rdata, const pj_str_t *header_name)
-{
-	static const pj_str_t from = { "From", 4 };
-	pj_str_t header_content;
-	pjsip_fromto_hdr *parsed_hdr;
-	pjsip_generic_string_hdr *ident = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
-			header_name, NULL);
-	int parsed_len;
-
-	if (!ident) {
-		return NULL;
-	}
-
-	pj_strdup_with_null(rdata->tp_info.pool, &header_content, &ident->hvalue);
-
-	parsed_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from, header_content.ptr,
-			pj_strlen(&header_content), &parsed_len);
-
-	if (!parsed_hdr) {
-		return NULL;
-	}
-
-	return parsed_hdr;
-}
-
-/*!
- * \internal
  * \brief Set an ANI2 integer based on OLI data in a From header
  *
  * This uses the contents of a From header in order to set Originating Line information.
@@ -161,130 +78,6 @@
 
 /*!
  * \internal
- * \brief Set an ast_party_id structure based on data in a P-Asserted-Identity header
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * the contents of a Privacy header in order to set presentation information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Successfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_pai(pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	static const pj_str_t pai_str = { "P-Asserted-Identity", 19 };
-	static const pj_str_t privacy_str = { "Privacy", 7 };
-	pjsip_fromto_hdr *pai_hdr = get_id_header(rdata, &pai_str);
-	pjsip_generic_string_hdr *privacy;
-
-	if (!pai_hdr) {
-		return -1;
-	}
-
-	set_id_from_hdr(pai_hdr, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	privacy = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &privacy_str, NULL);
-	if (!privacy || !pj_stricmp2(&privacy->hvalue, "none")) {
-		id->number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-		id->name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-	} else {
-		id->number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
-		id->name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
-	}
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Set an ast_party_id structure based on data in a Remote-Party-ID header
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * the privacy and screen parameters in order to set presentation information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Succesfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_rpid(pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	static const pj_str_t rpid_str = { "Remote-Party-ID", 15 };
-	static const pj_str_t privacy_str = { "privacy", 7 };
-	static const pj_str_t screen_str = { "screen", 6 };
-	pjsip_fromto_hdr *rpid_hdr = get_id_header(rdata, &rpid_str);
-	pjsip_param *screen;
-	pjsip_param *privacy;
-
-	if (!rpid_hdr) {
-		return -1;
-	}
-
-	set_id_from_hdr(rpid_hdr, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	privacy = pjsip_param_find(&rpid_hdr->other_param, &privacy_str);
-	screen = pjsip_param_find(&rpid_hdr->other_param, &screen_str);
-	if (privacy && !pj_stricmp2(&privacy->value, "full")) {
-		id->number.presentation = AST_PRES_RESTRICTED;
-		id->name.presentation = AST_PRES_RESTRICTED;
-	} else {
-		id->number.presentation = AST_PRES_ALLOWED;
-		id->name.presentation = AST_PRES_ALLOWED;
-	}
-	if (screen && !pj_stricmp2(&screen->value, "yes")) {
-		id->number.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
-		id->name.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
-	} else {
-		id->number.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
-		id->name.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
-	}
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Set an ast_party_id structure based on data in a From
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * no information from the message in order to set privacy. It relies on endpoint
- * configuration for privacy information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Succesfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_from(struct pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	pjsip_fromto_hdr *from = pjsip_msg_find_hdr(rdata->msg_info.msg,
-			PJSIP_H_FROM, rdata->msg_info.msg->hdr.next);
-
-	if (!from) {
-		/* This had better not happen */
-		return -1;
-	}
-
-	set_id_from_hdr(from, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	return 0;
-}
-
-/*!
- * \internal
  * \brief Determine if a connected line update should be queued
  *
  * This uses information about the session and the ID that would be queued
@@ -388,7 +181,7 @@
 	}
 
 	ast_party_id_init(&id);
-	if (!set_id_from_pai(rdata, &id) || !set_id_from_rpid(rdata, &id)) {
+	if (!ast_sip_set_id_connected_line(rdata, &id)) {
 		if (should_queue_connected_line_update(session, &id)) {
 			queue_connected_line_update(session, &id);
 		}
@@ -417,22 +210,8 @@
 		 * INVITE.  Set the session ID directly because the channel
 		 * has not been created yet.
 		 */
-		if (session->endpoint->id.trust_inbound
-			&& (!set_id_from_pai(rdata, &session->id)
-				|| !set_id_from_rpid(rdata, &session->id))) {
-			ast_free(session->id.tag);
-			session->id.tag = ast_strdup(session->endpoint->id.self.tag);
-			return 0;
-		}
-		ast_party_id_copy(&session->id, &session->endpoint->id.self);
-		if (!session->endpoint->id.self.number.valid) {
-			set_id_from_from(rdata, &session->id);
-		}
-		if (!set_id_from_oli(rdata, &ani2)) {
-			session->ani2 = ani2;
-		} else {
-			session->ani2 = 0;
-		}
+		ast_sip_set_id_from_invite(rdata, &session->id, &session->endpoint->id.self, session->endpoint->id.trust_inbound);
+		session->ani2 = set_id_from_oli(rdata, &ani2) ? 0 : ani2;
 	} else {
 		/*
 		 * ReINVITE or UPDATE.  Check for changes to the ID and queue
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 652b735..e7ba3ed 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -3641,6 +3641,21 @@
 	return session;
 }
 
+/*! \brief Fetch just the Caller ID number in order of PAI, RPID, From */
+static int fetch_callerid_num(struct ast_sip_session *session, pjsip_rx_data *rdata, char *buf, size_t len)
+{
+	int res = -1;
+	struct ast_party_id id;
+
+	ast_party_id_init(&id);
+	if (!ast_sip_set_id_from_invite(rdata, &id, &session->endpoint->id.self, session->endpoint->id.trust_inbound)) {
+		ast_copy_string(buf, id.number.str, len);
+		res = 0;
+	}
+	ast_party_id_free(&id);
+	return res;
+}
+
 enum sip_get_destination_result {
 	/*! The extension was successfully found */
 	SIP_GET_DEST_EXTEN_FOUND,
@@ -3664,6 +3679,7 @@
  */
 static enum sip_get_destination_result get_destination(struct ast_sip_session *session, pjsip_rx_data *rdata)
 {
+	char cid_num[AST_CHANNEL_NAME];
 	pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
 	struct ast_features_pickup_config *pickup_cfg;
 	const char *pickupexten;
@@ -3695,8 +3711,10 @@
 		ao2_ref(pickup_cfg, -1);
 	}
 
+	fetch_callerid_num(session, rdata, cid_num, sizeof(cid_num));
+
 	if (!strcmp(session->exten, pickupexten) ||
-		ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, NULL)) {
+		ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, S_OR(cid_num, NULL))) {
 		/*
 		 * Save off the INVITE Request-URI in case it is
 		 * needed: CHANNEL(pjsip,request_uri)
@@ -3711,7 +3729,7 @@
 	 */
 	if (session->endpoint->allow_overlap && (
 		!strncmp(session->exten, pickupexten, strlen(session->exten)) ||
-		ast_canmatch_extension(NULL, session->endpoint->context, session->exten, 1, NULL))) {
+		ast_canmatch_extension(NULL, session->endpoint->context, session->exten, 1, S_OR(cid_num, NULL)))) {
 		/* Overlap partial match */
 		return SIP_GET_DEST_EXTEN_PARTIAL;
 	}

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/19731
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 20
Gerrit-Change-Id: Iec7f5a3b90e51b65ccf74342f96bf80314b7cfc7
Gerrit-Change-Number: 19731
Gerrit-PatchSet: 2
Gerrit-Owner: N A <asterisk at phreaknet.org>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20221220/8e158f2c/attachment-0001.html>


More information about the asterisk-code-review mailing list