diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 1a2c78ec8c..18d40cc21a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -635,59 +635,6 @@ static const struct cfsip_methods { { SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD } }; -/*! \brief List of well-known SIP options. If we get this in a require, - we should check the list and answer accordingly. */ -static const struct cfsip_options { - int id; /*!< Bitmap ID */ - int supported; /*!< Supported by Asterisk ? */ - char * const text; /*!< Text id, as in standard */ -} sip_options[] = { /* XXX used in 3 places */ - /* RFC3262: PRACK 100% reliability */ - { SIP_OPT_100REL, NOT_SUPPORTED, "100rel" }, - /* RFC3959: SIP Early session support */ - { SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" }, - /* SIMPLE events: RFC4662 */ - { SIP_OPT_EVENTLIST, NOT_SUPPORTED, "eventlist" }, - /* RFC 4916- Connected line ID updates */ - { SIP_OPT_FROMCHANGE, NOT_SUPPORTED, "from-change" }, - /* GRUU: Globally Routable User Agent URI's */ - { SIP_OPT_GRUU, NOT_SUPPORTED, "gruu" }, - /* RFC4244 History info */ - { SIP_OPT_HISTINFO, NOT_SUPPORTED, "histinfo" }, - /* RFC3911: SIP Join header support */ - { SIP_OPT_JOIN, NOT_SUPPORTED, "join" }, - /* Disable the REFER subscription, RFC 4488 */ - { SIP_OPT_NOREFERSUB, NOT_SUPPORTED, "norefersub" }, - /* SIP outbound - the final NAT battle - draft-sip-outbound */ - { SIP_OPT_OUTBOUND, NOT_SUPPORTED, "outbound" }, - /* RFC3327: Path support */ - { SIP_OPT_PATH, NOT_SUPPORTED, "path" }, - /* RFC3840: Callee preferences */ - { SIP_OPT_PREF, NOT_SUPPORTED, "pref" }, - /* RFC3312: Precondition support */ - { SIP_OPT_PRECONDITION, NOT_SUPPORTED, "precondition" }, - /* RFC3323: Privacy with proxies*/ - { SIP_OPT_PRIVACY, NOT_SUPPORTED, "privacy" }, - /* RFC-ietf-sip-uri-list-conferencing-02.txt conference invite lists */ - { SIP_OPT_RECLISTINV, NOT_SUPPORTED, "recipient-list-invite" }, - /* RFC-ietf-sip-uri-list-subscribe-02.txt - subscription lists */ - { SIP_OPT_RECLISTSUB, NOT_SUPPORTED, "recipient-list-subscribe" }, - /* RFC3891: Replaces: header for transfer */ - { SIP_OPT_REPLACES, SUPPORTED, "replaces" }, - /* One version of Polycom firmware has the wrong label */ - { SIP_OPT_REPLACES, SUPPORTED, "replace" }, - /* RFC4412 Resource priorities */ - { SIP_OPT_RESPRIORITY, NOT_SUPPORTED, "resource-priority" }, - /* RFC3329: Security agreement mechanism */ - { SIP_OPT_SEC_AGREE, NOT_SUPPORTED, "sec_agree" }, - /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */ - { SIP_OPT_SDP_ANAT, NOT_SUPPORTED, "sdp-anat" }, - /* RFC4028: SIP Session-Timers */ - { SIP_OPT_TIMER, SUPPORTED, "timer" }, - /* RFC4538: Target-dialog */ - { SIP_OPT_TARGET_DIALOG,NOT_SUPPORTED, "tdialog" }, -}; - /*! \brief Diversion header reasons * * The core defines a bunch of constants used to define @@ -1480,7 +1427,6 @@ static int determine_firstline_parts(struct sip_request *req); static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype); static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize); static int find_sip_method(const char *msg); -static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported); static unsigned int parse_allowed_methods(struct sip_request *req); static unsigned int set_pvt_allowed_methods(struct sip_pvt *pvt, struct sip_request *req); static int parse_request(struct sip_request *req); @@ -2941,58 +2887,6 @@ static int find_sip_method(const char *msg) return res; } -/*! \brief Parse supported header in incoming packet */ -static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported) -{ - char *next, *sep; - char *temp; - unsigned int profile = 0; - int i, found; - - if (ast_strlen_zero(supported) ) - return 0; - temp = ast_strdupa(supported); - - if (sipdebug) - ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported); - - for (next = temp; next; next = sep) { - found = FALSE; - if ( (sep = strchr(next, ',')) != NULL) - *sep++ = '\0'; - next = ast_skip_blanks(next); - if (sipdebug) - ast_debug(3, "Found SIP option: -%s-\n", next); - for (i = 0; i < ARRAY_LEN(sip_options); i++) { - if (!strcasecmp(next, sip_options[i].text)) { - profile |= sip_options[i].id; - found = TRUE; - if (sipdebug) - ast_debug(3, "Matched SIP option: %s\n", next); - break; - } - } - - /* This function is used to parse both Suported: and Require: headers. - Let the caller of this function know that an unknown option tag was - encountered, so that if the UAC requires it then the request can be - rejected with a 420 response. */ - if (!found) - profile |= SIP_OPT_UNKNOWN; - - if (!found && sipdebug) { - if (!strncasecmp(next, "x-", 2)) - ast_debug(3, "Found private SIP option, not supported: %s\n", next); - else - ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next); - } - } - - if (pvt) - pvt->sipoptions = profile; - return profile; -} - /*! \brief See if we pass debug IP filter */ static inline int sip_debug_test_addr(const struct sockaddr_in *addr) { @@ -20497,18 +20391,22 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int /* Find out what they support */ if (!p->sipoptions) { const char *supported = get_header(req, "Supported"); - if (!ast_strlen_zero(supported)) - parse_sip_options(p, supported); + if (!ast_strlen_zero(supported)) { + p->sipoptions = parse_sip_options(supported, NULL, 0); + } } /* Find out what they require */ required = get_header(req, "Require"); if (!ast_strlen_zero(required)) { - required_profile = parse_sip_options(NULL, required); - if (required_profile && !(required_profile & (SIP_OPT_REPLACES | SIP_OPT_TIMER))) { - /* At this point we only support REPLACES and Session-Timer */ - transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required); - ast_log(LOG_WARNING, "Received SIP INVITE with unsupported required extension: %s\n", required); + char unsupported[256] = { 0, }; + required_profile = parse_sip_options(required, unsupported, ARRAY_LEN(unsupported)); + + /* If there are any options required that we do not support, + * then send a 420 with only those unsupported options listed */ + if (!ast_strlen_zero(unsupported)) { + transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, unsupported); + ast_log(LOG_WARNING, "Received SIP INVITE with unsupported required extension: required:%s unsupported:%s\n", required, unsupported); p->invitestate = INV_COMPLETED; if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); diff --git a/channels/sip/include/reqresp_parser.h b/channels/sip/include/reqresp_parser.h index 60fc646986..58784a621c 100644 --- a/channels/sip/include/reqresp_parser.h +++ b/channels/sip/include/reqresp_parser.h @@ -122,4 +122,18 @@ void sip_request_parser_register_tests(void); */ void sip_request_parser_unregister_tests(void); +/*! + * \brief Parse supported header in incoming packet + * + * \details This function parses through the options parameters and + * builds a bit field representing all the SIP options in that field. When an + * item is found that is not supported, it is copied to the unsupported + * out buffer. + * + * \param option list + * \param unsupported out buffer (optional) + * \param unsupported out buffer length (optional) + */ +unsigned int parse_sip_options(const char *options, char *unsupported, size_t unsupported_len); + #endif diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index ca8a897f43..01cbb2c73c 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -1698,5 +1698,57 @@ struct contact { AST_LIST_HEAD_NOLOCK(contactliststruct, contact); +/*! \brief List of well-known SIP options. If we get this in a require, + we should check the list and answer accordingly. */ +static const struct cfsip_options { + int id; /*!< Bitmap ID */ + int supported; /*!< Supported by Asterisk ? */ + char * const text; /*!< Text id, as in standard */ +} sip_options[] = { /* XXX used in 3 places */ + /* RFC3262: PRACK 100% reliability */ + { SIP_OPT_100REL, NOT_SUPPORTED, "100rel" }, + /* RFC3959: SIP Early session support */ + { SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" }, + /* SIMPLE events: RFC4662 */ + { SIP_OPT_EVENTLIST, NOT_SUPPORTED, "eventlist" }, + /* RFC 4916- Connected line ID updates */ + { SIP_OPT_FROMCHANGE, NOT_SUPPORTED, "from-change" }, + /* GRUU: Globally Routable User Agent URI's */ + { SIP_OPT_GRUU, NOT_SUPPORTED, "gruu" }, + /* RFC4244 History info */ + { SIP_OPT_HISTINFO, NOT_SUPPORTED, "histinfo" }, + /* RFC3911: SIP Join header support */ + { SIP_OPT_JOIN, NOT_SUPPORTED, "join" }, + /* Disable the REFER subscription, RFC 4488 */ + { SIP_OPT_NOREFERSUB, NOT_SUPPORTED, "norefersub" }, + /* SIP outbound - the final NAT battle - draft-sip-outbound */ + { SIP_OPT_OUTBOUND, NOT_SUPPORTED, "outbound" }, + /* RFC3327: Path support */ + { SIP_OPT_PATH, NOT_SUPPORTED, "path" }, + /* RFC3840: Callee preferences */ + { SIP_OPT_PREF, NOT_SUPPORTED, "pref" }, + /* RFC3312: Precondition support */ + { SIP_OPT_PRECONDITION, NOT_SUPPORTED, "precondition" }, + /* RFC3323: Privacy with proxies*/ + { SIP_OPT_PRIVACY, NOT_SUPPORTED, "privacy" }, + /* RFC-ietf-sip-uri-list-conferencing-02.txt conference invite lists */ + { SIP_OPT_RECLISTINV, NOT_SUPPORTED, "recipient-list-invite" }, + /* RFC-ietf-sip-uri-list-subscribe-02.txt - subscription lists */ + { SIP_OPT_RECLISTSUB, NOT_SUPPORTED, "recipient-list-subscribe" }, + /* RFC3891: Replaces: header for transfer */ + { SIP_OPT_REPLACES, SUPPORTED, "replaces" }, + /* One version of Polycom firmware has the wrong label */ + { SIP_OPT_REPLACES, SUPPORTED, "replace" }, + /* RFC4412 Resource priorities */ + { SIP_OPT_RESPRIORITY, NOT_SUPPORTED, "resource-priority" }, + /* RFC3329: Security agreement mechanism */ + { SIP_OPT_SEC_AGREE, NOT_SUPPORTED, "sec_agree" }, + /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */ + { SIP_OPT_SDP_ANAT, NOT_SUPPORTED, "sdp-anat" }, + /* RFC4028: SIP Session-Timers */ + { SIP_OPT_TIMER, SUPPORTED, "timer" }, + /* RFC4538: Target-dialog */ + { SIP_OPT_TARGET_DIALOG,NOT_SUPPORTED, "tdialog" }, +}; #endif diff --git a/channels/sip/reqresp_parser.c b/channels/sip/reqresp_parser.c index 446e9637fe..bf35bc2263 100644 --- a/channels/sip/reqresp_parser.c +++ b/channels/sip/reqresp_parser.c @@ -1575,6 +1575,235 @@ AST_TEST_DEFINE(parse_contact_header_test) return res; } +/*! + * \brief Parse supported header in incoming packet + * + * \details This function parses through the options parameters and + * builds a bit field representing all the SIP options in that field. When an + * item is found that is not supported, it is copied to the unsupported + * out buffer. + * + * \param option list + * \param unsupported out buffer (optional) + * \param unsupported out buffer length (optional) + */ +unsigned int parse_sip_options(const char *options, char *unsupported, size_t unsupported_len) +{ + char *next, *sep; + char *temp; + int i, found, supported; + unsigned int profile = 0; + + char *out = unsupported; + size_t outlen = unsupported_len; + char *cur_out = out; + + if (out && (outlen > 0)) { + memset(out, 0, outlen); + } + + if (ast_strlen_zero(options) ) + return 0; + + temp = ast_strdupa(options); + + ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", options); + + for (next = temp; next; next = sep) { + found = FALSE; + supported = FALSE; + if ((sep = strchr(next, ',')) != NULL) { + *sep++ = '\0'; + } + + /* trim leading and trailing whitespace */ + next = ast_strip(next); + + if (ast_strlen_zero(next)) { + continue; /* if there is a blank argument in there just skip it */ + } + + ast_debug(3, "Found SIP option: -%s-\n", next); + for (i = 0; i < ARRAY_LEN(sip_options); i++) { + if (!strcasecmp(next, sip_options[i].text)) { + profile |= sip_options[i].id; + if (sip_options[i].supported == SUPPORTED) { + supported = TRUE; + } + found = TRUE; + ast_debug(3, "Matched SIP option: %s\n", next); + break; + } + } + + /* If option is not supported, add to unsupported out buffer */ + if (!supported && out && outlen) { + size_t copylen = strlen(next); + size_t cur_outlen = strlen(out); + /* Check to see if there is enough room to store this option. + * Copy length is string length plus 2 for the ',' and '\0' */ + if ((cur_outlen + copylen + 2) < outlen) { + /* if this isn't the first item, add the ',' */ + if (cur_outlen) { + *cur_out = ','; + cur_out++; + cur_outlen++; + } + ast_copy_string(cur_out, next, (outlen - cur_outlen)); + cur_out += copylen; + } + } + + if (!found) { + profile |= SIP_OPT_UNKNOWN; + if (!strncasecmp(next, "x-", 2)) + ast_debug(3, "Found private SIP option, not supported: %s\n", next); + else + ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next); + } + } + + return profile; +} + +AST_TEST_DEFINE(sip_parse_options_test) +{ + int res = AST_TEST_PASS; + char unsupported[64]; + unsigned int option_profile = 0; + struct testdata { + char *name; + char *input_options; + char *expected_unsupported; + unsigned int expected_profile; + AST_LIST_ENTRY(testdata) list; + }; + + struct testdata *testdataptr; + static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; + + struct testdata test1 = { + .name = "test_all_unsupported", + .input_options = "unsupported1,,, ,unsupported2,unsupported3,unsupported4", + .expected_unsupported = "unsupported1,unsupported2,unsupported3,unsupported4", + .expected_profile = SIP_OPT_UNKNOWN, + }; + struct testdata test2 = { + .name = "test_all_unsupported_one_supported", + .input_options = " unsupported1, replaces, unsupported3 , , , ,unsupported4", + .expected_unsupported = "unsupported1,unsupported3,unsupported4", + .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES + }; + struct testdata test3 = { + .name = "test_two_supported_two_unsupported", + .input_options = ",, timer ,replaces ,unsupported3,unsupported4", + .expected_unsupported = "unsupported3,unsupported4", + .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES | SIP_OPT_TIMER, + }; + + struct testdata test4 = { + .name = "test_all_supported", + .input_options = "timer,replaces", + .expected_unsupported = "", + .expected_profile = SIP_OPT_REPLACES | SIP_OPT_TIMER, + }; + + struct testdata test5 = { + .name = "test_all_supported_redundant", + .input_options = "timer,replaces,timer,replace,timer,replaces", + .expected_unsupported = "", + .expected_profile = SIP_OPT_REPLACES | SIP_OPT_TIMER, + }; + struct testdata test6 = { + .name = "test_buffer_overflow", + .input_options = "unsupported1,replaces,timer,unsupported4,unsupported_huge____" + "____________________________________,__________________________________________" + "________________________________________________", + .expected_unsupported = "unsupported1,unsupported4", + .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES | SIP_OPT_TIMER, + }; + struct testdata test7 = { + .name = "test_null_input", + .input_options = NULL, + .expected_unsupported = "", + .expected_profile = 0, + }; + struct testdata test8 = { + .name = "test_whitespace_input", + .input_options = " ", + .expected_unsupported = "", + .expected_profile = 0, + }; + struct testdata test9 = { + .name = "test_whitespace_plus_option_input", + .input_options = " , , ,timer , , , , , ", + .expected_unsupported = "", + .expected_profile = SIP_OPT_TIMER, + }; + + switch (cmd) { + case TEST_INIT: + info->name = "sip_parse_options_test"; + info->category = "channels/chan_sip/"; + info->summary = "Tests parsing of sip options"; + info->description = + "Tests parsing of SIP options from supported and required " + "header fields. Verifies when unsupported options are encountered " + "that they are appended to the unsupported out buffer and that the " + "correct bit field representnig the option profile is returned."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &test1); + AST_LIST_INSERT_TAIL(&testdatalist, &test2, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test3, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test4, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test5, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test6, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test7, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test8, list); + AST_LIST_INSERT_TAIL(&testdatalist, &test9, list); + + /* Test with unsupported char buffer */ + AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { + option_profile = parse_sip_options(testdataptr->input_options, unsupported, ARRAY_LEN(unsupported)); + if (option_profile != testdataptr->expected_profile || + strcmp(unsupported, testdataptr->expected_unsupported)) { + ast_test_status_update(test, "Test with output buffer \"%s\", expected unsupported: %s actual unsupported:" + "%s expected bit profile: %x actual bit profile: %x\n", + testdataptr->name, + testdataptr->expected_unsupported, + unsupported, + testdataptr->expected_profile, + option_profile); + res = AST_TEST_FAIL; + } else { + ast_test_status_update(test, "\"%s\" passed got expected unsupported: %s and bit profile: %x\n", + testdataptr->name, + unsupported, + option_profile); + } + + option_profile = parse_sip_options(testdataptr->input_options, NULL, 0); + if (option_profile != testdataptr->expected_profile) { + ast_test_status_update(test, "NULL output test \"%s\", expected bit profile: %x actual bit profile: %x\n", + testdataptr->name, + testdataptr->expected_profile, + option_profile); + res = AST_TEST_FAIL; + } else { + ast_test_status_update(test, "\"%s\" with NULL output buf passed, bit profile: %x\n", + testdataptr->name, + option_profile); + } + } + + return res; +} + + void sip_request_parser_register_tests(void) { AST_TEST_REGISTER(get_calleridname_test); @@ -1584,6 +1813,7 @@ void sip_request_parser_register_tests(void) AST_TEST_REGISTER(sip_parse_uri_fully_test); AST_TEST_REGISTER(parse_name_andor_addr_test); AST_TEST_REGISTER(parse_contact_header_test); + AST_TEST_REGISTER(sip_parse_options_test); } void sip_request_parser_unregister_tests(void) { @@ -1594,4 +1824,5 @@ void sip_request_parser_unregister_tests(void) AST_TEST_UNREGISTER(sip_parse_uri_fully_test); AST_TEST_UNREGISTER(parse_name_andor_addr_test); AST_TEST_UNREGISTER(parse_contact_header_test); + AST_TEST_UNREGISTER(sip_parse_options_test); }