/* Copyright (C) 2019-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ // written by Giuseppe Longo use crate::sdp::parser::{sdp_parse_message, SdpMessage}; use nom8::bytes::streaming::{tag, take, take_until, take_while, take_while1}; use nom8::character::streaming::{char, crlf}; use nom8::combinator::{map, map_res, opt}; use nom8::error::{Error, ErrorKind}; use nom8::sequence::delimited; use nom8::{AsChar, Err, IResult, Needed, Parser}; use std; use std::collections::HashMap; #[derive(Debug)] pub struct Header { pub name: String, pub value: String, } #[derive(Debug)] pub struct Request { pub method: String, pub path: String, pub version: String, pub headers: HashMap>, pub request_line_len: u16, pub headers_len: u16, pub body_offset: u16, pub body_len: u16, pub body: Option, } #[derive(Debug)] pub struct Response { pub version: String, pub code: String, pub reason: String, pub headers: HashMap>, pub response_line_len: u16, pub headers_len: u16, pub body_offset: u16, pub body_len: u16, pub body: Option, } /** * Valid tokens and chars are defined in RFC3261: * https://www.rfc-editor.org/rfc/rfc3261#section-25.1 */ #[inline] fn is_token_char(b: u8) -> bool { b.is_alphanum() || b"!%'*+-._`~".contains(&b) } #[inline] fn is_method_char(b: u8) -> bool { b.is_alpha() } #[inline] fn is_request_uri_char(b: u8) -> bool { is_token_char(b) || b"~#@:;=?+&$,/".contains(&b) } #[inline] fn is_version_char(b: u8) -> bool { b.is_dec_digit() || b".".contains(&b) } #[inline] fn is_reason_phrase(b: u8) -> bool { is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b) } fn is_header_name(b: u8) -> bool { is_token_char(b) } fn is_header_value(b: u8) -> bool { is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b) } fn expand_header_name(h: &str) -> &str { match h { "i" => "Call-ID", "m" => "Contact", "e" => "Content-Encoding", "l" => "Content-Length", "c" => "Content-Type", "f" => "From", "s" => "Subject", "k" => "Supported", "t" => "To", "v" => "Via", _ => h, } } pub fn sip_probe_protocol(input: &[u8]) -> IResult<&[u8], ()> { let len = std::cmp::min(input.len(), 65536); let i = &input[..len]; if tag::<_, _, Error<&[u8]>>("SIP/").parse(i).is_ok() { return Ok((input, ())); } if take_until::<_, _, Error<&[u8]>>("SIP/").parse(i).is_ok() { Ok((input, ())) } else { Err(Err::Error(Error::new(i, ErrorKind::Tag))) } } pub fn parse_request(oi: &[u8]) -> IResult<&[u8], Request> { let (i, method) = parse_method(oi)?; let (i, _) = char(' ').parse(i)?; let (i, path) = parse_request_uri(i)?; let (i, _) = char(' ').parse(i)?; let (i, version) = parse_version(i)?; let (hi, _) = crlf.parse(i)?; let request_line_len = oi.len() - hi.len(); let (phi, headers) = parse_headers(hi)?; let headers_len = hi.len() - phi.len(); let (bi, _) = crlf.parse(phi)?; let body_offset = oi.len() - bi.len(); let (i, body) = opt(sdp_parse_message).parse(bi)?; Ok(( i, Request { method: method.into(), path: path.into(), version, headers, request_line_len: request_line_len as u16, headers_len: headers_len as u16, body_offset: body_offset as u16, body_len: bi.len() as u16, body, }, )) } pub fn parse_response(oi: &[u8]) -> IResult<&[u8], Response> { let (i, version) = parse_version(oi)?; let (i, _) = char(' ').parse(i)?; let (i, code) = parse_code(i)?; let (i, _) = char(' ').parse(i)?; let (i, reason) = parse_reason(i)?; let (hi, _) = crlf.parse(i)?; let response_line_len = oi.len() - hi.len(); let (phi, headers) = parse_headers(hi)?; let headers_len = hi.len() - phi.len(); let (bi, _) = crlf.parse(phi)?; let body_offset = oi.len() - bi.len(); let (i, body) = opt(sdp_parse_message).parse(bi)?; Ok(( i, Response { version, code: code.into(), reason: reason.into(), headers, response_line_len: response_line_len as u16, headers_len: headers_len as u16, body_offset: body_offset as u16, body_len: bi.len() as u16, body, }, )) } #[inline] fn parse_method(i: &[u8]) -> IResult<&[u8], &str> { map_res(take_while(is_method_char), std::str::from_utf8).parse(i) } #[inline] fn parse_request_uri(i: &[u8]) -> IResult<&[u8], &str> { map_res(take_while1(is_request_uri_char), std::str::from_utf8).parse(i) } #[inline] fn parse_version(i: &[u8]) -> IResult<&[u8], String> { let (i, prefix) = map_res(tag("SIP/"), std::str::from_utf8).parse(i)?; let (i, version) = map_res(take_while1(is_version_char), std::str::from_utf8).parse(i)?; Ok((i, format!("{}{}", prefix, version))) } #[inline] fn parse_code(i: &[u8]) -> IResult<&[u8], &str> { map_res(take(3_usize), std::str::from_utf8).parse(i) } #[inline] fn parse_reason(i: &[u8]) -> IResult<&[u8], &str> { map_res(take_while(is_reason_phrase), std::str::from_utf8).parse(i) } #[inline] fn header_name(i: &[u8]) -> IResult<&[u8], &str> { map_res(take_while(is_header_name), std::str::from_utf8).parse(i) } #[inline] fn header_value(i: &[u8]) -> IResult<&[u8], &str> { map_res(parse_header_value, std::str::from_utf8).parse(i) } #[inline] fn hcolon(i: &[u8]) -> IResult<&[u8], char> { delimited( take_while(|c: u8| c.is_space()), char(':'), take_while(|c: u8| c.is_space()), ) .parse(i) } fn message_header(i: &[u8]) -> IResult<&[u8], Header> { let (i, n) = map(header_name, expand_header_name).parse(i)?; let (i, _) = hcolon(i)?; let (i, v) = header_value(i)?; let (i, _) = crlf.parse(i)?; Ok(( i, Header { name: String::from(n), value: String::from(v), }, )) } pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option> { let (i, line) = map_res(take_while1(is_reason_phrase), std::str::from_utf8).parse(i)?; Ok((i, Some(line.into()))) } pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap>> { let mut headers_map: HashMap> = HashMap::new(); loop { match crlf.parse(input) as IResult<&[u8], _> { Ok((_, _)) => { break; } Err(Err::Error(_)) => {} Err(Err::Failure(_)) => {} Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)), }; let (rest, header) = message_header(input)?; headers_map .entry(header.name) .or_default() .push(header.value); input = rest; } Ok((input, headers_map)) } fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> { let mut end_pos = 0; let mut trail_spaces = 0; let mut idx = 0; while idx < buf.len() { match buf[idx] { b'\n' => { idx += 1; if idx >= buf.len() { return Err(Err::Incomplete(Needed::new(1))); } match buf[idx] { b' ' | b'\t' => { idx += 1; continue; } _ => { return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos])); } } } b' ' | b'\t' => { trail_spaces += 1; } b'\r' => {} b => { trail_spaces = 0; if !is_header_value(b) { return Err(Err::Incomplete(Needed::new(1))); } end_pos = idx + 1; } } idx += 1; } Ok((&b""[..], buf)) } #[cfg(test)] mod tests { use crate::sip::parser::*; #[test] fn test_parse_request() { let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ From: ;tag=903df0a\r\n\ To: \r\n\ Content-Length: 0\r\n\ \r\n" .as_bytes(); let (_, req) = parse_request(buf).unwrap(); assert_eq!(req.method, "REGISTER"); assert_eq!(req.path, "sip:sip.cybercity.dk"); assert_eq!(req.version, "SIP/2.0"); assert_eq!(req.headers["Content-Length"].first().unwrap(), "0"); } #[test] fn test_parse_request_trail_space_header() { let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ From: ;tag=903df0a\r\n\ To: \r\n\ Content-Length: 4 \r\n\ \r\nABCD" .as_bytes(); let (body, req) = parse_request(buf).expect("parsing failed"); assert_eq!(req.method, "REGISTER"); assert_eq!(req.path, "sip:sip.cybercity.dk"); assert_eq!(req.version, "SIP/2.0"); assert_eq!(req.headers["Content-Length"].first().unwrap(), "4"); assert_eq!(body, "ABCD".as_bytes()); } #[test] fn test_parse_response() { let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\ \r\n" .as_bytes(); let (_, resp) = parse_response(buf).unwrap(); assert_eq!(resp.version, "SIP/2.0"); assert_eq!(resp.code, "401"); assert_eq!(resp.reason, "Unauthorized"); } #[test] fn test_parse_invalid_version() { let buf: &[u8] = "HTTP/1.1\r\n".as_bytes(); // This test must fail if 'HTTP/1.1' is accepted assert!(parse_version(buf).is_err()); } #[test] fn test_parse_valid_version() { let buf: &[u8] = "SIP/2.0\r\n".as_bytes(); let (_rem, result) = parse_version(buf).unwrap(); assert_eq!(result, "SIP/2.0"); } #[test] fn test_probe_sip_request() { let buf = b"REGISTER sip:sip.example.com SIP/2.0\r\n"; assert!(sip_probe_protocol(buf).is_ok()); } #[test] fn test_probe_sip_response() { let buf = b"SIP/2.0 200 OK\r\n"; assert!(sip_probe_protocol(buf).is_ok()); } #[test] fn test_header_multi_value() { let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ From: ;tag=903df0a\r\n\ To: \r\n\ Route: \r\n\ Route: \r\n\ \r\n" .as_bytes(); let (_, req) = parse_request(buf).unwrap(); assert_eq!(req.method, "REGISTER"); assert_eq!(req.path, "sip:sip.cybercity.dk"); assert_eq!(req.version, "SIP/2.0"); assert_eq!( req.headers["Route"].first().unwrap(), "" ); assert_eq!( req.headers["Route"].get(1).unwrap(), "" ); } }