diff options
-rw-r--r-- | pjlib-util/src/pjlib-util/scanner.c | 4 | ||||
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 97 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_parser.c | 93 | ||||
-rw-r--r-- | tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_1.py | 52 | ||||
-rw-r--r-- | tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_2.py | 25 | ||||
-rw-r--r-- | tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_3.py | 35 | ||||
-rw-r--r-- | tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_4.py | 25 | ||||
-rw-r--r-- | tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_5.py | 25 |
8 files changed, 328 insertions, 28 deletions
diff --git a/pjlib-util/src/pjlib-util/scanner.c b/pjlib-util/src/pjlib-util/scanner.c index a5a56160..6e01ba6f 100644 --- a/pjlib-util/src/pjlib-util/scanner.c +++ b/pjlib-util/src/pjlib-util/scanner.c @@ -28,7 +28,7 @@ #define PJ_SCAN_IS_SPACE(c) ((c)==' ' || (c)=='\t') #define PJ_SCAN_IS_NEWLINE(c) ((c)=='\r' || (c)=='\n') #define PJ_SCAN_IS_PROBABLY_SPACE(c) ((c) <= 32) -#define PJ_SCAN_CHECK_EOF(s) (*s) +#define PJ_SCAN_CHECK_EOF(s) (s != scanner->end) #if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0 @@ -375,7 +375,7 @@ PJ_DEF(void) pj_scan_get_quotes(pj_scanner *scanner, */ do { /* loop until end_quote is found. */ - while (*s && *s != '\n' && *s != end_quote[qpair]) { + while (PJ_SCAN_CHECK_EOF(s) && *s != '\n' && *s != end_quote[qpair]) { ++s; } diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 8e73c9cb..e7f3390e 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -4023,6 +4023,97 @@ on_exit: ; } +/***************************************************************************** + * A simple module to handle otherwise unhandled request. We will register + * this with the lowest priority. + */ + +/* Notification on incoming request */ +static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + pjsip_status_code status_code; + pj_status_t status; + + /* Don't respond to ACK! */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_ack_method) == 0) + return PJ_TRUE; + + /* Create basic response. */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_notify_method) == 0) + { + /* Unsolicited NOTIFY's, send with Bad Request */ + status_code = PJSIP_SC_BAD_REQUEST; + } else { + /* Probably unknown method */ + status_code = PJSIP_SC_METHOD_NOT_ALLOWED; + } + status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(), + rdata, status_code, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create response", status); + return PJ_TRUE; + } + + /* Add Allow if we're responding with 405 */ + if (status_code == PJSIP_SC_METHOD_NOT_ALLOWED) { + const pjsip_hdr *cap_hdr; + cap_hdr = pjsip_endpt_get_capability(pjsua_get_pjsip_endpt(), + PJSIP_H_ALLOW, NULL); + if (cap_hdr) { + pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_clone(tdata->pool, + cap_hdr)); + } + } + + /* Add User-Agent header */ + { + pj_str_t user_agent; + char tmp[80]; + const pj_str_t USER_AGENT = { "User-Agent", 10}; + pjsip_hdr *h; + + pj_ansi_snprintf(tmp, sizeof(tmp), "PJSUA v%s/%s", + pj_get_version(), PJ_OS_NAME); + pj_strdup2_with_null(tdata->pool, &user_agent, tmp); + + h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, + &USER_AGENT, + &user_agent); + pjsip_msg_add_hdr(tdata->msg, h); + } + + pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata, + NULL, NULL); + + return PJ_TRUE; +} + + +/* The module instance. */ +static pjsip_module mod_default_handler = +{ + NULL, NULL, /* prev, next. */ + { "mod-default-handler", 19 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION+99, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &default_mod_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + + /***************************************************************************** * Public API @@ -4073,6 +4164,12 @@ pj_status_t app_init(int argc, char *argv[]) if (status != PJ_SUCCESS) return status; + /* Initialize our module to handle otherwise unhandled request */ + status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(), + &mod_default_handler); + if (status != PJ_SUCCESS) + return status; + #ifdef STEREO_DEMO stereo_demo(); #endif diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c index 5a45817f..bb470c72 100644 --- a/pjsip/src/pjsip/sip_parser.c +++ b/pjsip/src/pjsip/sip_parser.c @@ -54,8 +54,6 @@ */ #define GENERIC_URI_CHARS "#?;:@&=+-_.!~*'()%$,/" "%" -#define PJSIP_VERSION "SIP/2.0" - #define UNREACHED(expr) #define IS_NEWLINE(c) ((c)=='\r' || (c)=='\n') @@ -893,6 +891,36 @@ PJ_DEF(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool, return NULL; } +/* SIP version */ +static void parse_sip_version(pj_scanner *scanner) +{ + pj_str_t SIP = { "SIP", 3 }; + pj_str_t V2 = { "2.0", 3 }; + pj_str_t sip, version; + + pj_scan_get( scanner, &pconst.pjsip_ALPHA_SPEC, &sip); + if (pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); + pj_scan_get_n( scanner, 3, &version); + if (pj_stricmp(&sip, &SIP) || pj_stricmp(&version, &V2)) + on_syntax_error(scanner); +} + +static pj_bool_t is_next_sip_version(pj_scanner *scanner) +{ + pj_str_t SIP = { "SIP", 3 }; + pj_str_t sip; + int c; + + c = pj_scan_peek(scanner, &pconst.pjsip_ALPHA_SPEC, &sip); + /* return TRUE if it is "SIP" followed by "/" or space. + * we include space since the "/" may be separated by space, + * although this would mean it would return TRUE if it is a + * request and the method is "SIP"! + */ + return c && (c=='/' || c==' ' || c=='\t') && pj_stricmp(&sip, &SIP)==0; +} + /* Internal function to parse SIP message */ static pjsip_msg *int_parse_msg( pjsip_parse_ctx *ctx, pjsip_parser_err_report *err_list) @@ -926,7 +954,7 @@ retry_parse: return NULL; /* Parse request or status line */ - if (pj_scan_stricmp_alnum( scanner, PJSIP_VERSION, 7) == 0) { + if (is_next_sip_version(scanner)) { msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG); int_parse_status_line( scanner, &msg->line.status ); } else { @@ -1125,7 +1153,7 @@ PJ_DEF(void) pjsip_parse_uri_param_imp( pj_scanner *scanner, pj_pool_t *pool, } -/* Parse parameter (";" pname ["=" pvalue]) in header. */ +/* Parse parameter (";" pname ["=" pvalue]) in SIP header. */ static void int_parse_param( pj_scanner *scanner, pj_pool_t *pool, pj_str_t *pname, pj_str_t *pvalue, unsigned option) @@ -1513,9 +1541,7 @@ static void int_parse_req_line( pj_scanner *scanner, pj_pool_t *pool, pjsip_method_init_np( &req_line->method, &token); req_line->uri = int_parse_uri(scanner, pool, PJ_TRUE); - if (pj_scan_stricmp_alnum( scanner, PJSIP_VERSION, 7) != 0) - PJ_THROW( PJSIP_SYN_ERR_EXCEPTION); - pj_scan_advance_n (scanner, 7, 1); + parse_sip_version(scanner); pj_scan_get_newline( scanner ); } @@ -1525,10 +1551,7 @@ static void int_parse_status_line( pj_scanner *scanner, { pj_str_t token; - if (pj_scan_stricmp_alnum(scanner, PJSIP_VERSION, 7) != 0) - PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); - pj_scan_advance_n( scanner, 7, 1); - + parse_sip_version(scanner); pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &token); status_line->code = pj_strtoul(&token); if (*scanner->curptr != '\r' && *scanner->curptr != '\n') @@ -1618,12 +1641,32 @@ end: /* Parse generic string header. */ static void parse_generic_string_hdr( pjsip_generic_string_hdr *hdr, - pj_scanner *scanner ) + pjsip_parse_ctx *ctx) { - if (pj_cis_match(&pconst.pjsip_NOT_NEWLINE, *scanner->curptr)) + pj_scanner *scanner = ctx->scanner; + + hdr->hvalue.slen = 0; + + /* header may be mangled hence the loop */ + while (pj_cis_match(&pconst.pjsip_NOT_NEWLINE, *scanner->curptr)) { + pj_str_t next, tmp; + pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->hvalue); - else - hdr->hvalue.slen = 0; + if (IS_NEWLINE(*scanner->curptr)) + break; + /* mangled, get next fraction */ + pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &next); + /* concatenate */ + tmp.ptr = (char*)pj_pool_alloc(ctx->pool, + hdr->hvalue.slen + next.slen + 2); + tmp.slen = 0; + pj_strcpy(&tmp, &hdr->hvalue); + pj_strcat2(&tmp, " "); + pj_strcat(&tmp, &next); + tmp.ptr[tmp.slen] = '\0'; + + hdr->hvalue = tmp; + } parse_hdr_end(scanner); } @@ -1934,13 +1977,12 @@ static void int_parse_via_param( pjsip_via_hdr *hdr, pj_scanner *scanner, pj_str_t pname, pvalue; //Parse with PARAM_CHAR instead, to allow IPv6 + //No, back to using int_parse_param() for the "`" character! //int_parse_param( scanner, pool, &pname, &pvalue, 0); - /* Get ';' character */ - pj_scan_get_char(scanner); - - parse_param_imp(scanner, pool, &pname, &pvalue, - &pconst.pjsip_PARAM_CHAR_SPEC, - &pconst.pjsip_PARAM_CHAR_SPEC_ESC, 0); + //parse_param_imp(scanner, pool, &pname, &pvalue, + // &pconst.pjsip_TOKEN_SPEC, + // &pconst.pjsip_TOKEN_SPEC_ESC, 0); + int_parse_param(scanner, pool, &pname, &pvalue, 0); if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) { hdr->branch_param = pvalue; @@ -2075,10 +2117,9 @@ static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx ) else pj_list_insert_before(first, hdr); - if (pj_scan_stricmp_alnum( scanner, PJSIP_VERSION "/", 8) != 0) - PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); - - pj_scan_advance_n( scanner, 8, 1); + parse_sip_version(scanner); + if (pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->transport); int_parse_host(scanner, &hdr->sent_by.host); @@ -2119,7 +2160,7 @@ static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx ) pjsip_generic_string_hdr *hdr; hdr = pjsip_generic_string_hdr_create(ctx->pool, NULL, NULL); - parse_generic_string_hdr(hdr, ctx->scanner); + parse_generic_string_hdr(hdr, ctx); return (pjsip_hdr*)hdr; } diff --git a/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_1.py b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_1.py new file mode 100644 index 00000000..35f803c7 --- /dev/null +++ b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_1.py @@ -0,0 +1,52 @@ +# $Id$ +import inc_sip as sip +import inc_sdp as sdp + +# Torture message from RFC 4475 +# 3.1.1. Valid Messages +# 3.1.1.1. A Short Tortuous INVITE +complete_msg = \ +"""INVITE sip:vivekg@chair-dnrc.example.com;unknownparam SIP/2.0 +TO : + sip:vivekg@chair-dnrc.example.com ; tag = 1918181833n +from : "J Rosenberg \\\\\\"" <sip:jdrosen@example.com> + ; + tag = 98asjd8 +MaX-fOrWaRdS: 0068 +Call-ID: wsinv.ndaksdj@192.0.2.1 +Content-Length : 150 +cseq: 0009 + INVITE +Via : SIP / 2.0 + /UDP + 192.0.2.2;rport;branch=390skdjuw +s : +NewFangledHeader: newfangled value + continued newfangled value +UnknownHeaderWithUnusualValue: ;;,,;;,; +Content-Type: application/sdp +Route: + <sip:services.example.com;lr;unknownwith=value;unknown-no-value> +v: SIP / 2.0 / TCP spindle.example.com ; + branch = z9hG4bK9ikj8 , + SIP / 2.0 / UDP 192.168.255.111 ; branch= + z9hG4bK30239 +m:"Quoted string \\"\\"" <sip:jdrosen@example.com> ; newparam = + newvalue ; + secondparam ; q = 0.33 + +v=0 +o=mhandley 29739 7272939 IN IP4 192.0.2.3 +s=- +c=IN IP4 192.0.2.4 +t=0 0 +m=audio 49217 RTP/AVP 0 12 +m=video 3227 RTP/AVP 31 +a=rtpmap:31 LPC +""" + + +sendto_cfg = sip.SendtoCfg( "RFC 4475 3.1.1.1", + "--null-audio --auto-answer 200", + "", 481, complete_msg=complete_msg) + diff --git a/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_2.py b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_2.py new file mode 100644 index 00000000..88fd249b --- /dev/null +++ b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_2.py @@ -0,0 +1,25 @@ +# $Id$ +import inc_sip as sip +import inc_sdp as sdp + +# Torture message from RFC 4475 +# 3.1.1. Valid Messages +# 3.1.1.2. Wide Range of Valid Characters +complete_msg = \ +"""!interesting-Method0123456789_*+`.%indeed'~ sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com SIP/2.0 +Via: SIP/2.0/UDP host1.example.com;rport;branch=z9hG4bK-.!%66*_+`'~ +To: "BEL:\\\x07 NUL:\\\x00 DEL:\\\x7F" <sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*@example.com> +From: token1~` token2'+_ token3*%!.- <sip:mundane@example.com> ;fromParam''~+*_!.-%="\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x8E\xD1\x89\xD0\xB8\xD0\xB9";tag=_token~1'+`*%!-. +Call-ID: intmeth.word%ZK-!.*_+'@word`~)(><:\\/"][?}{ +CSeq: 139122385 !interesting-Method0123456789_*+`.%indeed'~ +Max-Forwards: 255 +extensionHeader-!.%*+_`'~: \xEF\xBB\xBF\xE5\xA4\xA7\xE5\x81\x9C\xE9\x9B\xBB +Content-Length: 0 + +""" + + +sendto_cfg = sip.SendtoCfg( "RFC 4475 3.1.1.2", + "--null-audio --auto-answer 200", + "", 405, complete_msg=complete_msg) + diff --git a/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_3.py b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_3.py new file mode 100644 index 00000000..4f32e979 --- /dev/null +++ b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_3.py @@ -0,0 +1,35 @@ +# $Id$ +import inc_sip as sip +import inc_sdp as sdp + +# Torture message from RFC 4475 +# 3.1.1. Valid Messages +# 3.1.1.3. Valid Use of the % Escaping Mechanism +complete_msg = \ +"""INVITE sip:sips%3Auser%40example.com@example.net SIP/2.0 +To: sip:%75se%72@example.com +From: <sip:I%20have%20spaces@example.net>;tag=$FROM_TAG +Max-Forwards: 87 +i: esc01.239409asdfakjkn23onasd0-3234 +CSeq: 234234 INVITE +Via: SIP/2.0/UDP host5.example.net;rport;branch=z9hG4bKkdjuw +C: application/sdp +Contact: + <sip:cal%6Cer@$LOCAL_IP:$LOCAL_PORT;%6C%72;n%61me=v%61lue%25%34%31> +Content-Length: 150 + +v=0 +o=mhandley 29739 7272939 IN IP4 192.0.2.1 +s=- +c=IN IP4 192.0.2.1 +t=0 0 +m=audio 49217 RTP/AVP 0 12 +m=video 3227 RTP/AVP 31 +a=rtpmap:31 LPC +""" + + +sendto_cfg = sip.SendtoCfg( "RFC 4475 3.1.1.3", + "--null-audio --auto-answer 200", + "", 200, complete_msg=complete_msg) + diff --git a/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_4.py b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_4.py new file mode 100644 index 00000000..7a05c144 --- /dev/null +++ b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_4.py @@ -0,0 +1,25 @@ +# $Id$ +import inc_sip as sip +import inc_sdp as sdp + +# Torture message from RFC 4475 +# 3.1.1. Valid Messages +# 3.1.1.4. Escaped Nulls in URIs +complete_msg = \ +"""REGISTER sip:example.com SIP/2.0 +To: sip:null-%00-null@example.com +From: sip:null-%00-null@example.com;tag=839923423 +Max-Forwards: 70 +Call-ID: escnull.39203ndfvkjdasfkq3w4otrq0adsfdfnavd +CSeq: 14398234 REGISTER +Via: SIP/2.0/UDP host5.example.com;rport;branch=z9hG4bKkdjuw +Contact: <sip:%00@host5.example.com> +Contact: <sip:%00%00@host5.example.com> +L:0 +""" + + +sendto_cfg = sip.SendtoCfg( "RFC 4475 3.1.1.4", + "--null-audio --auto-answer 200", + "", 405, complete_msg=complete_msg) + diff --git a/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_5.py b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_5.py new file mode 100644 index 00000000..2b449d11 --- /dev/null +++ b/tests/pjsua/scripts-sendto/001_torture_4475_3_1_1_5.py @@ -0,0 +1,25 @@ +# $Id$ +import inc_sip as sip +import inc_sdp as sdp + +# Torture message from RFC 4475 +# 3.1.1. Valid Messages +# 3.1.1.5. Use of % When It Is Not an Escape +complete_msg = \ +"""RE%47IST%45R sip:registrar.example.com SIP/2.0 +To: "%Z%45" <sip:resource@example.com> +From: "%Z%45" <sip:resource@example.com>;tag=f232jadfj23 +Call-ID: esc02.asdfnqwo34rq23i34jrjasdcnl23nrlknsdf +Via: SIP/2.0/TCP host.example.com;rport;branch=z9hG4bK209%fzsnel234 +CSeq: 29344 RE%47IST%45R +Max-Forwards: 70 +Contact: <sip:alias1@host1.example.com> +C%6Fntact: <sip:alias2@host2.example.com> +Contact: <sip:alias3@host3.example.com> +l: 0 +""" + +sendto_cfg = sip.SendtoCfg( "RFC 4475 3.1.1.5", + "--null-audio --auto-answer 200", + "", 405, complete_msg=complete_msg) + |