diff --git a/CHANGES b/CHANGES index c1cd83fe2de4c0cd0b30f89371d4314aa523a942..38c87e6e2530fd796ec54873cba87e91bb94ff81 100644 --- a/CHANGES +++ b/CHANGES @@ -143,6 +143,13 @@ res_pjsip when both 'SIP' and 'Q.850' Reason headers are received. This option allows the 'Q.850' Reason header to be suppressed. The default value is 'no'. +res_pjsip_endpoint_identifier_ip +------------------ + * Added regex support to the identify section match_header option. You + specify a regex instead of an explicit string by surrounding the header + value with slashes: + match_header = SIPHeader: /regex/ + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 15.4.0 to Asterisk 15.5.0 ------------ ------------------------------------------------------------------------------ diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c index 24687863e864b67c6dff99f0e19ef8bfb7b90abc..30b2a2e8e6cbb49d10cafd63913b5ed1cd2a0a5a 100644 --- a/res/res_pjsip_endpoint_identifier_ip.c +++ b/res/res_pjsip_endpoint_identifier_ip.c @@ -42,7 +42,7 @@ <description> <para>This module provides alternatives to matching inbound requests to a configured endpoint. At least one of the matching mechanisms - must be provided, or the object configuration will be invalid.</para> + must be provided, or the object configuration is invalid.</para> <para>The matching mechanisms are provided by the following configuration options:</para> <enumlist> @@ -86,6 +86,13 @@ specified with a <literal>:</literal>, as in <literal>match_header = SIPHeader: value</literal>. </para> + <para>The specified SIP header value can be a regular + expression if the value is of the form + /<replaceable>regex</replaceable>/. + </para> + <note><para>Use of a regex is expensive so be sure you need + to use a regex to match your endpoint. + </para></note> </description> </configOption> <configOption name="type"> @@ -109,13 +116,21 @@ struct ip_identify_match { AST_STRING_FIELD(endpoint_name); /*! If matching by header, the header/value to match against */ AST_STRING_FIELD(match_header); + /*! SIP header name of the match_header string */ + AST_STRING_FIELD(match_header_name); + /*! 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; /*! \brief Networks or addresses that should match this */ struct ast_ha *matches; - /*! \brief Perform SRV resolution of hostnames */ - unsigned int srv_lookups; /*! \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; }; /*! \brief Destructor function for a matching object */ @@ -126,6 +141,9 @@ 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); + } } /*! \brief Allocator function for a matching object */ @@ -146,47 +164,66 @@ static int header_identify_match_check(void *obj, void *arg, int flags) { struct ip_identify_match *identify = obj; struct pjsip_rx_data *rdata = arg; - pjsip_generic_string_hdr *header; + pjsip_hdr *header; pj_str_t pj_header_name; - pj_str_t pj_header_value; - char *c_header; - char *c_value; + int header_present; if (ast_strlen_zero(identify->match_header)) { return 0; } - c_header = ast_strdupa(identify->match_header); - c_value = strchr(c_header, ':'); - if (!c_value) { - /* This should not be possible. The object cannot be created if so. */ - ast_assert(0); - return 0; - } - *c_value = '\0'; - c_value++; - c_value = ast_strip(c_value); + pj_header_name = pj_str((void *) identify->match_header_name); + + /* Check all headers of the given name for a match. */ + header_present = 0; + for (header = NULL; + (header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &pj_header_name, header)); + header = header->next) { + char *pos; + int len; + char buf[PATH_MAX]; - pj_header_name = pj_str(c_header); - header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &pj_header_name, NULL); - if (!header) { - ast_debug(3, "Identify '%s': SIP message does not have header '%s'\n", + header_present = 1; + + /* Print header line to buf */ + len = pjsip_hdr_print_on(header, buf, sizeof(buf) - 1); + if (len < 0) { + /* Buffer not large enough or no header vptr! */ + ast_assert(0); + continue; + } + buf[len] = '\0'; + + /* Remove header name from pj_buf and trim blanks. */ + pos = strchr(buf, ':'); + if (!pos) { + /* No header name? Bug in PJPROJECT if so. */ + ast_assert(0); + continue; + } + 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)) { + return CMP_MATCH; + } + } else if (!strcmp(identify->match_header_value, pos)) { + return CMP_MATCH; + } + + ast_debug(3, "Identify '%s': SIP message has '%s' header but value '%s' does not match '%s'.\n", ast_sorcery_object_get_id(identify), - c_header); - return 0; + identify->match_header_name, + pos, + identify->match_header_value); } - - pj_header_value = pj_str(c_value); - if (pj_strcmp(&pj_header_value, &header->hvalue)) { - ast_debug(3, "Identify '%s': SIP message has header '%s' but value '%.*s' does not match '%s'\n", + if (!header_present) { + ast_debug(3, "Identify '%s': SIP message does not have '%s' header.\n", ast_sorcery_object_get_id(identify), - c_header, - (int) pj_strlen(&header->hvalue), pj_strbuf(&header->hvalue), - c_value); - return 0; + identify->match_header_name); } - - return CMP_MATCH; + return 0; } /*! \brief Comparator function for matching an object by IP address */ @@ -404,14 +441,57 @@ static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj) ast_sorcery_object_get_id(identify)); return -1; } - if (!ast_strlen_zero(identify->match_header) - && !strchr(identify->match_header, ':')) { - ast_log(LOG_ERROR, "Identify '%s' missing ':' separator in match_header '%s'.\n", - ast_sorcery_object_get_id(identify), identify->match_header); - return -1; + + if (!ast_strlen_zero(identify->match_header)) { + char *c_header; + char *c_value; + int len; + + /* Split the header name and value */ + c_header = ast_strdupa(identify->match_header); + c_value = strchr(c_header, ':'); + if (!c_value) { + ast_log(LOG_ERROR, "Identify '%s' missing ':' separator in match_header '%s'.\n", + ast_sorcery_object_get_id(identify), identify->match_header); + return -1; + } + *c_value = '\0'; + c_value = ast_strip(c_value + 1); + c_header = ast_strip(c_header); + + if (ast_strlen_zero(c_header)) { + ast_log(LOG_ERROR, "Identify '%s' has no SIP header to match in match_header '%s'.\n", + ast_sorcery_object_get_id(identify), identify->match_header); + return -1; + } + + if (!strcmp(c_value, "//")) { + /* An empty regex is the same as an empty literal string. */ + c_value = ""; + } + + if (ast_string_field_set(identify, match_header_name, c_header) + || ast_string_field_set(identify, match_header_value, c_value)) { + return -1; + } + + len = strlen(c_value); + if (2 < len && c_value[0] == '/' && c_value[len - 1] == '/') { + /* Make "/regex/" into "regex" */ + 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", + ast_sorcery_object_get_id(identify), c_value); + return -1; + } + identify->is_regex = 1; + } } if (!identify->hosts) { + /* No match addresses to resolve */ return 0; }