From 83f1317eb4ab8adb7181424854c4016e317448d9 Mon Sep 17 00:00:00 2001 From: Sperl Viktor Date: Thu, 28 Mar 2024 14:20:26 +0100 Subject: [PATCH] res_pjsip_endpoint_identifier_ip: Endpoint identifier request URI Add ability to match against PJSIP request URI. UserNote: this new feature let users match endpoints based on the indound SIP requests' URI. To do so, add 'request_uri' to the endpoint's 'identify_by' option. The 'match_request_uri' option of the identify can be an exact match for the entire request uri, or a regular expression (between slashes). It's quite similar to the header identifer. Fixes: #599 --- configs/samples/pjsip.conf.sample | 1 + ...fd3_add_match_request_uri_attribute_to_.py | 20 +++ include/asterisk/res_pjsip.h | 2 + res/res_pjsip/pjsip_config.xml | 8 ++ res/res_pjsip/pjsip_configuration.c | 5 + res/res_pjsip_endpoint_identifier_ip.c | 128 ++++++++++++++++-- 6 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/cf150a175fd3_add_match_request_uri_attribute_to_.py diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 9d454089d3..6b8936b2d4 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -686,6 +686,7 @@ ; "auth_username": Identify by the Authorization username and realm ; "ip": Identify by the source IP address ; "header": Identify by a configured SIP header value. + ; "request_uri": Identify by the configured SIP request URI. ; In the username and auth_username cases, if an exact match ; on both username and domain/realm fails, the match is ; retried with just the username. diff --git a/contrib/ast-db-manage/config/versions/cf150a175fd3_add_match_request_uri_attribute_to_.py b/contrib/ast-db-manage/config/versions/cf150a175fd3_add_match_request_uri_attribute_to_.py new file mode 100644 index 0000000000..9c9f38f925 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/cf150a175fd3_add_match_request_uri_attribute_to_.py @@ -0,0 +1,20 @@ +"""add match_request_uri attribute to identify + +Revision ID: cf150a175fd3 +Revises: 8fce8496f03e +Create Date: 2024-03-28 14:19:15.033869 + +""" + +# revision identifiers, used by Alembic. +revision = 'cf150a175fd3' +down_revision = '8fce8496f03e' + +from alembic import op +import sqlalchemy as sa + +def upgrade(): + op.add_column('ps_endpoint_id_ips', sa.Column('match_request_uri', sa.String(255))) + +def downgrade(): + op.drop_column('ps_endpoint_id_ips', 'match_request_uri') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 214605209a..d0e1e59223 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -615,6 +615,8 @@ enum ast_sip_endpoint_identifier_type { AST_SIP_ENDPOINT_IDENTIFY_BY_IP = (1 << 2), /*! Identify based on arbitrary headers */ AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER = (1 << 3), + /*! Identify based on request uri */ + AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI = (1 << 4), }; AST_VECTOR(ast_sip_identify_by_vector, enum ast_sip_endpoint_identifier_type); diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index 6a8421f18b..74024a45e8 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -559,6 +559,14 @@ endpoint identification. + + Matches the endpoint based on the configured SIP + request uri. + + This method of identification is not configured here + but simply allowed by this configuration option. + + diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 7dbe7ad16e..58a50c484a 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -423,6 +423,9 @@ static const char *sip_endpoint_identifier_type2str(enum ast_sip_endpoint_identi case AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER: str = "header"; break; + case AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI: + str = "request_uri"; + break; } return str; } @@ -448,6 +451,8 @@ static int sip_endpoint_identifier_str2type(const char *str) method = AST_SIP_ENDPOINT_IDENTIFY_BY_IP; } else if (!strcasecmp(str, "header")) { method = AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER; + } else if (!strcasecmp(str, "request_uri")) { + method = AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI; } else { method = -1; } diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c index d8de26112c..da5c363f3a 100644 --- a/res/res_pjsip_endpoint_identifier_ip.c +++ b/res/res_pjsip_endpoint_identifier_ip.c @@ -110,6 +110,20 @@ + + Request URI to match against. + + The SIP request URI is used to match against. + + The specified SIP request URI can be a regular + expression if the value is of the form + /regex/. + + Use of a regex is expensive so be sure you need + to use a regex to match your endpoint. + + + Must be of type 'identify'. @@ -129,6 +143,8 @@ struct ip_identify_match { AST_DECLARE_STRING_FIELDS( /*! The name of the endpoint */ AST_STRING_FIELD(endpoint_name); + /*! If matching by request, the value to match against */ + AST_STRING_FIELD(match_request_uri); /*! If matching by header, the header/value to match against */ AST_STRING_FIELD(match_header); /*! SIP header name of the match_header string */ @@ -136,16 +152,20 @@ struct ip_identify_match { /*! SIP header value of the match_header string */ AST_STRING_FIELD(match_header_value); ); - /*! Compiled match_header regular expression when is_regex is non-zero */ - regex_t regex_buf; + /*! Compiled match_header regular expression when is_header_regex is non-zero */ + regex_t regex_header_buf; + /*! Compiled match_request_uri regular expression when is_request_uri_regex is non-zero */ + regex_t regex_request_uri_buf; /*! \brief Networks or addresses that should match this */ struct ast_ha *matches; /*! \brief Hosts to be resolved when applying configuration */ struct ao2_container *hosts; /*! \brief Perform SRV resolution of hostnames */ unsigned int srv_lookups; - /*! Non-zero if match_header has a regular expression (i.e., regex_buf is valid) */ - unsigned int is_regex:1; + /*! Non-zero if match_header has a regular expression (i.e., regex_header_buf is valid) */ + unsigned int is_header_regex:1; + /*! Non-zero if match_header or match_request has a regular expression (i.e., regex_request_uri_buf is valid) */ + unsigned int is_request_uri_regex:1; }; /*! \brief Destructor function for a matching object */ @@ -156,8 +176,11 @@ static void ip_identify_destroy(void *obj) ast_string_field_free_memory(identify); ast_free_ha(identify->matches); ao2_cleanup(identify->hosts); - if (identify->is_regex) { - regfree(&identify->regex_buf); + if (identify->is_header_regex) { + regfree(&identify->regex_header_buf); + } + if (identify->is_request_uri_regex) { + regfree(&identify->regex_request_uri_buf); } } @@ -219,8 +242,8 @@ static int header_identify_match_check(void *obj, void *arg, int flags) pos = ast_strip(pos + 1); /* Does header value match what we are looking for? */ - if (identify->is_regex) { - if (!regexec(&identify->regex_buf, pos, 0, NULL, 0)) { + if (identify->is_header_regex) { + if (!regexec(&identify->regex_header_buf, pos, 0, NULL, 0)) { return CMP_MATCH; } } else if (!strcmp(identify->match_header_value, pos)) { @@ -241,6 +264,41 @@ static int header_identify_match_check(void *obj, void *arg, int flags) return 0; } +/*! \brief Comparator function for matching an object by request URI */ +static int request_identify_match_check(void *obj, void *arg, int flags) +{ + struct ip_identify_match *identify = obj; + struct pjsip_rx_data *rdata = arg; + int len; + char buf[PJSIP_MAX_URL_SIZE]; + + if (ast_strlen_zero(identify->match_request_uri)) { + return 0; + } + + /* Print request URI to req_buf */ + len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, rdata->msg_info.msg->line.req.uri, buf, sizeof(buf) - 1); + if (len < 0) { + /* Buffer not large enough or no pj uri vptr! */ + ast_assert(0); + } else { + /* Terminate the pj_str */ + buf[len] = '\0'; + /* Does request URI match what we are looking for? */ + if (identify->is_request_uri_regex) { + if (!regexec(&identify->regex_request_uri_buf, buf, 0, NULL, 0)) { + return CMP_MATCH; + } + } else if (!strcmp(identify->match_request_uri, buf)) { + return CMP_MATCH; + } + ast_debug(3, "Identify '%s': request URI not match '%s' (value='%s').\n", + ast_sorcery_object_get_id(identify), identify->match_request_uri, buf); + } + + return 0; +} + /*! \brief Comparator function for matching an object by IP address */ static int ip_identify_match_check(void *obj, void *arg, int flags) { @@ -314,10 +372,19 @@ static struct ast_sip_endpoint *header_identify(pjsip_rx_data *rdata) return common_identify(header_identify_match_check, rdata); } +static struct ast_sip_endpoint *request_identify(pjsip_rx_data *rdata) +{ + return common_identify(request_identify_match_check, rdata); +} + static struct ast_sip_endpoint_identifier header_identifier = { .identify_endpoint = header_identify, }; +static struct ast_sip_endpoint_identifier request_identifier = { + .identify_endpoint = request_identify, +}; + /*! \brief Helper function which performs a host lookup and adds result to identify match */ static int ip_identify_match_host_lookup(struct ip_identify_match *identify, const char *host) { @@ -453,6 +520,7 @@ static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj) return -1; } if (ast_strlen_zero(identify->match_header) /* No header to match */ + && ast_strlen_zero(identify->match_request_uri) /* and no request to match */ /* and no static IP addresses with a mask */ && !identify->matches /* and no addresses to resolve */ @@ -501,12 +569,38 @@ static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj) c_value[len - 1] = '\0'; ++c_value; - if (regcomp(&identify->regex_buf, c_value, REG_EXTENDED | REG_NOSUB)) { - ast_log(LOG_ERROR, "Identify '%s' failed to compile match_header regex '%s'.\n", + if (regcomp(&identify->regex_header_buf, c_value, REG_EXTENDED | REG_NOSUB)) { + ast_log(LOG_ERROR, "Identify '%s' failed to compile match_request_uri regex '%s'.\n", ast_sorcery_object_get_id(identify), c_value); return -1; } - identify->is_regex = 1; + identify->is_header_regex = 1; + } + } + + if (!ast_strlen_zero(identify->match_request_uri)) { + char *c_string; + int len; + + len = strlen(identify->match_request_uri); + c_string = ast_strdupa(identify->match_request_uri); + + if (!strcmp(c_string, "//")) { + /* An empty regex is the same as an empty literal string. */ + c_string = ""; + } + + if (2 < len && c_string[0] == '/' && c_string[len - 1] == '/') { + /* Make "/regex/" into "regex" */ + c_string[len - 1] = '\0'; + ++c_string; + + if (regcomp(&identify->regex_request_uri_buf, c_string, REG_EXTENDED | REG_NOSUB)) { + ast_log(LOG_ERROR, "Identify '%s' failed to compile match_header regex '%s'.\n", + ast_sorcery_object_get_id(identify), c_string); + return -1; + } + identify->is_request_uri_regex = 1; } } @@ -788,10 +882,17 @@ static int cli_print_body(void *obj, void *arg, int flags) if (!ast_strlen_zero(ident->match_header)) { ast_str_append(&context->output_buffer, 0, "%*s: %s\n", indent, - "Match", + "Header", ident->match_header); } + if (!ast_strlen_zero(ident->match_request_uri)) { + ast_str_append(&context->output_buffer, 0, "%*s: %s\n", + indent, + "RequestURI", + ident->match_request_uri); + } + context->indent_level--; if (context->indent_level == 0) { @@ -851,11 +952,13 @@ static int load_module(void) ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name)); ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, match_to_str, match_to_var_list, 0, 0); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header)); + ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_request_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_request_uri)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups)); ast_sorcery_load_object(ast_sip_get_sorcery(), "identify"); ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip"); ast_sip_register_endpoint_identifier_with_name(&header_identifier, "header"); + ast_sip_register_endpoint_identifier_with_name(&request_identifier, "request_uri"); ast_sip_register_endpoint_formatter(&endpoint_identify_formatter); cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL); @@ -890,6 +993,7 @@ static int unload_module(void) ast_sip_unregister_cli_formatter(cli_formatter); ast_sip_unregister_endpoint_formatter(&endpoint_identify_formatter); ast_sip_unregister_endpoint_identifier(&header_identifier); + ast_sip_unregister_endpoint_identifier(&request_identifier); ast_sip_unregister_endpoint_identifier(&ip_identifier); return 0;