diff --git a/CHANGES b/CHANGES index bf7bc75de92bcaf6978b8089bfb2b544f327f402..38228e37a23406b2d70be99dabe9d693a40532f8 100644 --- a/CHANGES +++ b/CHANGES @@ -79,6 +79,17 @@ res_pjsip configure these options then you already had to do a reload after making changes. + * Added "ignore_uri_user_options" global configuration option for + compatibility with an ITSP that sends URI user field options. When enabled + the user field is truncated at the first semicolon. + Example: + URI: "sip:1235557890;phone-context=national@x.x.x.x;user=phone" + The user field is "1235557890;phone-context=national" + Which is truncated to this: "1235557890" + + Note: The caller-id and redirecting number strings obtained from incoming + SIP URI user fields are now always truncated at the first semicolon. + app_confbridge ------------------ * Some sounds played into the bridge are played asynchronously. This, for diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index e6b32495ba36f5864c41de7d143f24acec8f4725..c6293b6fbcaaa5dd8a30e46587d9b1be243978f9 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -991,6 +991,22 @@ ; If disabled then unsolicited mwi will start processing ; on the endpoint's next contact update. +;ignore_uri_user_options=no ; Enable/Disable ignoring SIP URI user field options. + ; If you have this option enabled and there are semicolons + ; in the user field of a SIP URI then the field is truncated + ; at the first semicolon. This effectively makes the semicolon + ; a non-usable character for PJSIP endpoint names, extensions, + ; and AORs. This can be useful for improving compatability with + ; an ITSP that likes to use user options for whatever reason. + ; Example: + ; URI: "sip:1235557890;phone-context=national@x.x.x.x;user=phone" + ; The user field is "1235557890;phone-context=national" + ; Which becomes this: "1235557890" + ; + ; Note: The caller-id and redirecting number strings obtained + ; from incoming SIP URI user fields are always truncated at the + ; first semicolon. + ; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl ;==========================ACL SECTION OPTIONS========================= ;[acl] diff --git a/contrib/ast-db-manage/config/versions/a6ef36f1309_ps_globals_add_ignore_uri_user_options.py b/contrib/ast-db-manage/config/versions/a6ef36f1309_ps_globals_add_ignore_uri_user_options.py new file mode 100644 index 0000000000000000000000000000000000000000..1556521d55279674429bd50d47f0fdbc42c64ff2 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/a6ef36f1309_ps_globals_add_ignore_uri_user_options.py @@ -0,0 +1,32 @@ +"""ps_globals add ignore_uri_user_options + +Revision ID: a6ef36f1309 +Revises: 7f3e21abe318 +Create Date: 2016-08-31 12:24:22.368956 + +""" + +# revision identifiers, used by Alembic. +revision = 'a6ef36f1309' +down_revision = '7f3e21abe318' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + +def upgrade(): + ############################# Enums ############################## + + # yesno_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.add_column('ps_globals', sa.Column('ignore_uri_user_options', yesno_values)) + + +def downgrade(): + op.drop_column('ps_globals', 'ignore_uri_user_options') + diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 8a5ad29c59810d9f745320c0199d394ffdce59b8..92bdabb662f772ee4b71482600fba9de3a664cef 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -2446,6 +2446,38 @@ int ast_sip_get_mwi_tps_queue_low(void); */ unsigned int ast_sip_get_mwi_disable_initial_unsolicited(void); +/*! + * \brief Retrieve the global setting 'ignore_uri_user_options'. + * \since 13.12.0 + * + * \retval non zero if ignore the user field options. + */ +unsigned int ast_sip_get_ignore_uri_user_options(void); + +/*! + * \brief Truncate the URI user field options string if enabled. + * \since 13.12.0 + * + * \param str URI user field string to truncate if enabled + * + * \details + * We need to be able to handle URI's looking like + * "sip:1235557890;phone-context=national@x.x.x.x;user=phone" + * + * Where the URI user field is: + * "1235557890;phone-context=national" + * + * When truncated the string will become: + * "1235557890" + */ +#define AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(str) \ + do { \ + char *__semi = strchr((str), ';'); \ + if (__semi && ast_sip_get_ignore_uri_user_options()) { \ + *__semi = '\0'; \ + } \ + } while (0) + /*! * \brief Retrieve the system debug setting (yes|no|host). * diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 7bb10c07f01b59035ebceca2653d6283f1f2181a..d1ebf64e433fb64fd5b5e5cac840708863492c36 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -1550,6 +1550,30 @@ </para> </description> </configOption> + <configOption name="ignore_uri_user_options"> + <synopsis>Enable/Disable ignoring SIP URI user field options.</synopsis> + <description> + <para>If you have this option enabled and there are semicolons + in the user field of a SIP URI then the field is truncated + at the first semicolon. This effectively makes the semicolon + a non-usable character for PJSIP endpoint names, extensions, + and AORs. This can be useful for improving compatability with + an ITSP that likes to use user options for whatever reason. + </para> + <example title="Sample SIP URI"> + sip:1235557890;phone-context=national@x.x.x.x;user=phone + </example> + <example title="Sample SIP URI user field"> + 1235557890;phone-context=national + </example> + <example title="Sample SIP URI user field truncated"> + 1235557890 + </example> + <note><para>The caller-id and redirecting number strings + obtained from incoming SIP URI user fields are always truncated + at the first semicolon.</para></note> + </description> + </configOption> </configObject> </configFile> </configInfo> diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c index 281630ae43ac510955c6a445bfcf80566b669294..fc1227d2523305581584b874d3d84c2b5528aa53 100644 --- a/res/res_pjsip/config_global.c +++ b/res/res_pjsip/config_global.c @@ -47,6 +47,7 @@ #define DEFAULT_MWI_TPS_QUEUE_HIGH AST_TASKPROCESSOR_HIGH_WATER_LEVEL #define DEFAULT_MWI_TPS_QUEUE_LOW -1 #define DEFAULT_MWI_DISABLE_INITIAL_UNSOLICITED 0 +#define DEFAULT_IGNORE_URI_USER_OPTIONS 0 /*! * \brief Cached global config object @@ -100,6 +101,8 @@ struct global_config { /*! Nonzero to disable sending unsolicited mwi to all endpoints on startup */ unsigned int disable_initial_unsolicited; } mwi; + /*! Nonzero if URI user field options are ignored. */ + unsigned int ignore_uri_user_options; }; static void global_destructor(void *obj) @@ -384,6 +387,20 @@ unsigned int ast_sip_get_mwi_disable_initial_unsolicited(void) return disable_initial_unsolicited; } +unsigned int ast_sip_get_ignore_uri_user_options(void) +{ + unsigned int ignore_uri_user_options; + struct global_config *cfg; + + cfg = get_global_cfg(); + if (!cfg) { + return DEFAULT_IGNORE_URI_USER_OPTIONS; + } + + ignore_uri_user_options = cfg->ignore_uri_user_options; + ao2_ref(cfg, -1); + return ignore_uri_user_options; +} /*! * \internal @@ -533,6 +550,9 @@ int ast_sip_initialize_sorcery_global(void) ast_sorcery_object_field_register(sorcery, "global", "mwi_disable_initial_unsolicited", DEFAULT_MWI_DISABLE_INITIAL_UNSOLICITED ? "yes" : "no", OPT_BOOL_T, 1, FLDSET(struct global_config, mwi.disable_initial_unsolicited)); + ast_sorcery_object_field_register(sorcery, "global", "ignore_uri_user_options", + DEFAULT_IGNORE_URI_USER_OPTIONS ? "yes" : "no", + OPT_BOOL_T, 1, FLDSET(struct global_config, ignore_uri_user_options)); if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) { return -1; diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c index a282224c92fd05111f60f06f808d0c97b18470e5..09fe1559bfb0d8d5de4d18a28aef24a2299443d5 100644 --- a/res/res_pjsip/pjsip_options.c +++ b/res/res_pjsip/pjsip_options.c @@ -750,8 +750,7 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) pjsip_sip_uri *sip_ruri; char exten[AST_MAX_EXTENSION]; - if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, - &pjsip_options_method)) { + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) { return PJ_FALSE; } @@ -768,13 +767,20 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) sip_ruri = pjsip_uri_get_uri(ruri); ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten)); + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(exten); + if (ast_shutting_down()) { /* * Not taking any new calls at this time. * Likely a server availability OPTIONS poll. */ send_options_response(rdata, 503); - } else if (!ast_strlen_zero(exten) && !ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) { + } else if (!ast_strlen_zero(exten) + && !ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) { send_options_response(rdata, 404); } else { send_options_response(rdata, 200); diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c index 429cb6107832cbe66539d96df18781a7ef1a9c17..16b19ec2ba4cfc645888e1cb2ea6e1fe2870f52a 100644 --- a/res/res_pjsip_caller_id.c +++ b/res/res_pjsip_caller_id.c @@ -46,11 +46,29 @@ static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id) char cid_num[AST_CHANNEL_NAME]; pjsip_sip_uri *uri; pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri; + char *semi; uri = pjsip_uri_get_uri(id_name_addr); ast_copy_pj_str(cid_name, &id_name_addr->display, sizeof(cid_name)); ast_copy_pj_str(cid_num, &uri->user, 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@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)) { diff --git a/res/res_pjsip_diversion.c b/res/res_pjsip_diversion.c index 82c3caaed6ea3a40532ee3b99737982a82a46bbe..301d9fc9212cc3f5eed84f4067523c5fd3f608ea 100644 --- a/res/res_pjsip_diversion.c +++ b/res/res_pjsip_diversion.c @@ -148,11 +148,32 @@ static void set_redirecting_id(pjsip_name_addr *name_addr, struct ast_party_id * struct ast_set_party_id *update) { pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr->uri); + char *semi; + pj_str_t uri_user; + + uri_user = uri->user; + + /* Always truncate redirecting number at a semicolon. */ + semi = pj_strchr(&uri_user, ';'); + if (semi) { + /* + * We need to be able to handle URI's looking like + * "sip:1235557890;phone-context=national@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. + */ + pj_strset(&uri_user, (char *) pj_strbuf(&uri_user), semi - pj_strbuf(&uri_user)); + } - if (pj_strlen(&uri->user)) { + if (pj_strlen(&uri_user)) { update->number = 1; data->number.valid = 1; - set_redirecting_value(&data->number.str, &uri->user); + set_redirecting_value(&data->number.str, &uri_user); } if (pj_strlen(&name_addr->display)) { diff --git a/res/res_pjsip_endpoint_identifier_user.c b/res/res_pjsip_endpoint_identifier_user.c index 6aa2c553206d888e21ba59a5113b2aa4c617caa6..369cb62fc9eb27ef7caf9328fe9b44f4dff11b5e 100644 --- a/res/res_pjsip_endpoint_identifier_user.c +++ b/res/res_pjsip_endpoint_identifier_user.c @@ -33,6 +33,7 @@ static int get_from_header(pjsip_rx_data *rdata, char *username, size_t username { pjsip_uri *from = rdata->msg_info.from->uri; pjsip_sip_uri *sip_from; + if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) { return -1; } @@ -115,18 +116,25 @@ static struct ast_sip_endpoint *find_endpoint(pjsip_rx_data *rdata, char *endpoi static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata) { - char username[64], domain[64]; + char username[64]; + char domain[64]; struct ast_sip_endpoint *endpoint; if (get_from_header(rdata, username, sizeof(username), domain, sizeof(domain))) { return NULL; } + + /* + * We may want to be matched without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(username); + ast_debug(3, "Attempting identify by From username '%s' domain '%s'\n", username, domain); endpoint = find_endpoint(rdata, username, domain); if (!endpoint) { ast_debug(3, "Endpoint not found for From username '%s' domain '%s'\n", username, domain); - ao2_cleanup(endpoint); return NULL; } if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) { diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c index 7efb1a20e6c44574b462f8c7db476bfb685fa9f0..e63c825b58b49ea646dab26b2488bd528c17f704 100644 --- a/res/res_pjsip_messaging.c +++ b/res/res_pjsip_messaging.c @@ -133,6 +133,12 @@ static struct ast_sip_endpoint* get_outbound_endpoint( } else if ((aor_uri = strchr(name, '@'))) { /* format was 'endpoint@' - don't use the rest */ *aor_uri = '\0'; + + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(name); } /* at this point, if name is not empty then it @@ -448,6 +454,12 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as sip_ruri = pjsip_uri_get_uri(ruri); ast_copy_pj_str(exten, &sip_ruri->user, AST_MAX_EXTENSION); + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(exten); + endpt = ast_pjsip_rdata_get_endpoint(rdata); ast_assert(endpt != NULL); @@ -528,7 +540,7 @@ static void msg_data_destroy(void *obj) static struct msg_data* msg_data_create(const struct ast_msg *msg, const char *to, const char *from) { - char *tag; + char *uri_params; struct msg_data *mdata = ao2_alloc(sizeof(*mdata), msg_data_destroy); if (!mdata) { @@ -553,9 +565,14 @@ static struct msg_data* msg_data_create(const struct ast_msg *msg, const char *t return NULL; } - /* sometimes from can still contain the tag at this point, so remove it */ - if ((tag = strchr(mdata->from, ';'))) { - *tag = '\0'; + /* + * Sometimes from URI can contain URI parameters, so remove them. + * + * sip:user;user-options@domain;uri-parameters + */ + uri_params = strchr(mdata->from, '@'); + if (uri_params && (uri_params = strchr(mdata->from, ';'))) { + *uri_params = '\0'; } return mdata; } diff --git a/res/res_pjsip_path.c b/res/res_pjsip_path.c index 2dde7323e61e56780518a9a332385c9300d7976b..e170a750d8d044d6de4aefe7cccd244ff0a22f6b 100644 --- a/res/res_pjsip_path.c +++ b/res/res_pjsip_path.c @@ -40,7 +40,8 @@ static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri char *configured_aors, *aor_name; pjsip_sip_uri *sip_uri; char *domain_name; - RAII_VAR(struct ast_str *, id, NULL, ast_free); + char *username; + struct ast_str *id = NULL; if (ast_strlen_zero(endpoint->aors)) { return NULL; @@ -49,6 +50,14 @@ static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri sip_uri = pjsip_uri_get_uri(uri); domain_name = ast_alloca(sip_uri->host.slen + 1); ast_copy_pj_str(domain_name, &sip_uri->host, sip_uri->host.slen + 1); + username = ast_alloca(sip_uri->user.slen + 1); + ast_copy_pj_str(username, &sip_uri->user, sip_uri->user.slen + 1); + + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(username); configured_aors = ast_strdupa(endpoint->aors); @@ -60,15 +69,16 @@ static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri continue; } - if (!pj_strcmp2(&sip_uri->user, aor_name)) { + if (!strcmp(username, aor_name)) { break; } - if (!id && !(id = ast_str_create(sip_uri->user.slen + sip_uri->host.slen + 2))) { - return NULL; + if (!id && !(id = ast_str_create(strlen(username) + sip_uri->host.slen + 2))) { + aor_name = NULL; + break; } - ast_str_set(&id, 0, "%.*s@", (int)sip_uri->user.slen, sip_uri->user.ptr); + ast_str_set(&id, 0, "%s@", username); if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { ast_str_append(&id, 0, "%s", alias->domain); ao2_cleanup(alias); @@ -77,10 +87,10 @@ static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri } if (!strcmp(aor_name, ast_str_buffer(id))) { - ast_free(id); break; } } + ast_free(id); if (ast_strlen_zero(aor_name)) { return NULL; diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index fe16c613ab86b408c4707f8ba6467f329f20c223..015ef99c7f3067a06ca09920c48d8be746834afc 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -1378,6 +1378,12 @@ static int sub_persistence_recreate(void *obj) resource = ast_alloca(resource_size); ast_copy_pj_str(resource, &request_uri->user, resource_size); + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(resource); + handler = subscription_get_handler_from_rdata(rdata); if (!handler || !handler->notifier) { ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get subscription handler.\n", @@ -2750,6 +2756,12 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) resource = ast_alloca(resource_size); ast_copy_pj_str(resource, &request_uri_sip->user, resource_size); + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(resource); + expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, rdata->msg_info.msg->hdr.next); if (expires_header) { @@ -2963,6 +2975,12 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi resource_name = ast_alloca(resource_size); ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size); + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(resource_name); + resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name); if (!resource) { ast_debug(1, "No 'inbound-publication' defined for resource '%s'\n", resource_name); diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c index 19367bf3261d5a960558eace8e921136bfd20efd..c1dee8225a095f7863ef4ee2afcd09e0235c57d8 100644 --- a/res/res_pjsip_refer.c +++ b/res/res_pjsip_refer.c @@ -814,6 +814,13 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r /* Using the user portion of the target URI see if it exists as a valid extension in their context */ ast_copy_pj_str(exten, &target->user, sizeof(exten)); + + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(exten); + if (!ast_exists_extension(NULL, context, exten, 1, NULL)) { ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted blind transfer to '%s@%s' but target does not exist\n", ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), exten, context); diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index fd87ef7bbbdae294da158b8c48070281c666b848..a8d2bdc4c2c160e6a7e5193d40e7a29e0ef244aa 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -626,6 +626,12 @@ static struct ast_sip_aor *find_registrar_aor(struct pjsip_rx_data *rdata, struc username = ast_alloca(uri->user.slen + 1); ast_copy_pj_str(username, &uri->user, uri->user.slen + 1); + /* + * We may want to match without any user options getting + * in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(username); + aor_name = find_aor_name(username, domain_name, endpoint->aors); if (aor_name) { ast_debug(3, "Matched aor '%s' by To username\n", aor_name); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index a26359ffb18c6bef586f6227c4d8562f900d4aa3..7e885c3bddb1047ac8cca51c4bd9ba7da1ae6a7a 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -1982,6 +1982,12 @@ static enum sip_get_destination_result get_destination(struct ast_sip_session *s sip_ruri = pjsip_uri_get_uri(ruri); ast_copy_pj_str(session->exten, &sip_ruri->user, sizeof(session->exten)); + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(session->exten); + pickup_cfg = ast_get_chan_features_pickup_config(session->channel); if (!pickup_cfg) { ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); @@ -3095,6 +3101,13 @@ static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const char exten[AST_MAX_EXTENSION]; ast_copy_pj_str(exten, &uri->user, sizeof(exten)); + + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(exten); + ast_channel_call_forward_set(session->channel, exten); } else if (session->endpoint->redirect_method == AST_SIP_REDIRECT_URI_CORE) { char target_uri[PJSIP_MAX_URL_SIZE];