diff --git a/Cargo.toml b/Cargo.toml index b5fcb1b..f4c2830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ prctl = "1.0" smoltcp = { version = "0.9.1", git = "https://github.com/smoltcp-rs/smoltcp", features = ["std", "phy-tuntap_interface"] } thiserror = "1.0" url = "2.3" +digest_auth = "0.3.1" +httparse = "1.8.0" +unicase = "2.6.0" [target.'cfg(target_os="android")'.dependencies] android_logger = "0.13" diff --git a/src/error.rs b/src/error.rs index de014e8..dbfe962 100644 --- a/src/error.rs +++ b/src/error.rs @@ -48,6 +48,12 @@ pub enum Error { #[error("std::num::ParseIntError {0:?}")] IntParseError(#[from] std::num::ParseIntError), + + #[error("httparse::Error {0}")] + HttpError(#[from] httparse::Error), + + #[error("digest_auth::Error {0}")] + DigestAuthError(#[from] digest_auth::Error), } impl From<&str> for Error { diff --git a/src/http.rs b/src/http.rs index a51f153..ba0decb 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,24 +1,41 @@ use crate::error::Error; use crate::tun2proxy::{ - Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, + Connection, ConnectionManager, Destination, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection, TcpProxy, }; use crate::Credentials; use base64::Engine; +use httparse::Response; use smoltcp::wire::IpProtocol; -use std::collections::VecDeque; +use std::cell::RefCell; +use std::collections::hash_map::RandomState; +use std::collections::{HashMap, VecDeque}; +use std::iter::FromIterator; use std::net::SocketAddr; use std::rc::Rc; +use std::str; +use unicase::UniCase; + +#[derive(Eq, PartialEq, Debug)] +#[allow(dead_code)] +enum AuthenticationScheme { + None, + Basic, + Digest, +} #[derive(Eq, PartialEq, Debug)] #[allow(dead_code)] enum HttpState { SendRequest, - ExpectStatusCode, + ExpectResponseHeaders, ExpectResponse, + Reset, Established, } +pub(crate) type DigestState = digest_auth::WwwAuthenticateHeader; + pub struct HttpConnection { state: HttpState, client_inbuf: VecDeque, @@ -27,82 +44,264 @@ pub struct HttpConnection { server_outbuf: VecDeque, data_buf: VecDeque, crlf_state: u8, + counter: usize, + skip: usize, + digest_state: Rc>>, + before: bool, + credentials: Option, + destination: Destination, } -impl HttpConnection { - fn new(connection: &Connection, manager: Rc) -> Self { - let mut server_outbuf: VecDeque = VecDeque::new(); - { - let credentials = manager.get_credentials(); - server_outbuf.extend(b"CONNECT ".iter()); - server_outbuf.extend(connection.dst.to_string().as_bytes()); - server_outbuf.extend(b" HTTP/1.1\r\nHost: ".iter()); - server_outbuf.extend(connection.dst.to_string().as_bytes()); - server_outbuf.extend(b"\r\n".iter()); - if let Some(credentials) = credentials { - server_outbuf.extend(b"Proxy-Authorization: Basic "); - let mut auth_plain = credentials.username.clone(); - auth_plain.extend(b":".iter()); - auth_plain.extend(&credentials.password); - let auth_b64 = base64::engine::general_purpose::STANDARD.encode(auth_plain); - server_outbuf.extend(auth_b64.as_bytes().iter()); - server_outbuf.extend(b"\r\n".iter()); - } - server_outbuf.extend(b"\r\n".iter()); - } +static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate"; +static PROXY_AUTHORIZATION: &str = "Proxy-Authorization"; +static CONNECTION: &str = "Connection"; +static TRANSFER_ENCODING: &str = "Transfer-Encoding"; +static CONTENT_LENGTH: &str = "Content-Length"; - Self { - state: HttpState::ExpectStatusCode, +impl HttpConnection { + fn new( + connection: &Connection, + manager: Rc, + digest_state: Rc>>, + ) -> Result { + let mut res = Self { + state: HttpState::ExpectResponseHeaders, client_inbuf: Default::default(), server_inbuf: Default::default(), client_outbuf: Default::default(), - server_outbuf, + server_outbuf: Default::default(), data_buf: Default::default(), - crlf_state: Default::default(), + skip: 0, + counter: 0, + crlf_state: 0, + digest_state, + before: false, + credentials: manager.get_credentials().clone(), + destination: connection.dst.clone(), + }; + + res.send_tunnel_request()?; + Ok(res) + } + + fn send_tunnel_request(&mut self) -> Result<(), Error> { + self.server_outbuf.extend(b"CONNECT "); + self.server_outbuf + .extend(self.destination.to_string().as_bytes()); + self.server_outbuf.extend(b" HTTP/1.1\r\nHost: "); + self.server_outbuf + .extend(self.destination.to_string().as_bytes()); + self.server_outbuf.extend(b"\r\n"); + + self.send_auth_data(if self.digest_state.borrow().is_none() { + AuthenticationScheme::Basic + } else { + AuthenticationScheme::Digest + })?; + + self.server_outbuf.extend(b"\r\n"); + Ok(()) + } + + fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<(), Error> { + let Some(credentials) = &self.credentials else { + return Ok(()); + }; + + match scheme { + AuthenticationScheme::Digest => { + let uri = format!("{}:{}", self.destination.host, self.destination.port); + + let context = digest_auth::AuthContext::new_with_method( + &credentials.username, + &credentials.password, + &uri, + Option::<&'_ [u8]>::None, + digest_auth::HttpMethod::CONNECT, + ); + + let mut state = self.digest_state.borrow_mut(); + let response = state.as_mut().unwrap().respond(&context)?; + + self.server_outbuf.extend( + format!( + "{}: {}\r\n", + PROXY_AUTHORIZATION, + response.to_header_string() + ) + .as_bytes(), + ); + } + AuthenticationScheme::Basic => { + let cred = format!("{}:{}", credentials.username, credentials.password); + let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred); + self.server_outbuf + .extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes()); + } + AuthenticationScheme::None => {} } + + Ok(()) } fn state_change(&mut self) -> Result<(), Error> { - let http_len = "HTTP/1.1 200".len(); match self.state { - HttpState::ExpectStatusCode if self.server_inbuf.len() > http_len => { - let status_line: Vec = - self.server_inbuf.range(0..http_len + 1).copied().collect(); - let slice = &status_line.as_slice()[0.."HTTP/1.1 2".len()]; - if slice != b"HTTP/1.1 2" && slice != b"HTTP/1.0 2" - || self.server_inbuf[http_len] != b' ' - { - let status_str = String::from_utf8_lossy(&status_line.as_slice()[0..http_len]); - let e = - format!("Expected success status code. Server replied with {status_str}."); - return Err(e.into()); - } - self.state = HttpState::ExpectResponse; - return self.state_change(); - } - HttpState::ExpectResponse => { - let mut counter = 0usize; - for b_ref in self.server_inbuf.iter() { - let b = *b_ref; + HttpState::ExpectResponseHeaders => { + while self.counter < self.server_inbuf.len() { + let b = self.server_inbuf[self.counter]; if b == b'\n' { self.crlf_state += 1; } else if b != b'\r' { self.crlf_state = 0; } - counter += 1; + self.counter += 1; if self.crlf_state == 2 { - self.server_inbuf.drain(0..counter); + break; + } + } + + if self.crlf_state != 2 { + // Waiting for the end of the headers yet + return Ok(()); + } + + self.counter = 0; + self.crlf_state = 0; + + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut res = Response::new(&mut headers); + + // First make the buffer contiguous + let slice = self.server_inbuf.make_contiguous(); + let status = res.parse(slice)?; + if status.is_partial() { + // TODO: Optimize in order to detect 200 + return Ok(()); + } + let len = status.unwrap(); + let status_code = res.code.unwrap(); + let version = res.version.unwrap(); + + if status_code == 200 { + // Connection successful + self.state = HttpState::Established; + self.server_inbuf.clear(); + + self.server_outbuf.append(&mut self.data_buf); + self.data_buf.clear(); + + return self.state_change(); + } + + if status_code != 407 { + let e = + format!("Expected success status code. Server replied with {status_code} [Reason: {}].", res.reason.unwrap()); + return Err(e.into()); + } + + let headers_map: HashMap, &[u8], RandomState> = + HashMap::from_iter(headers.map(|x| (UniCase::new(x.name), x.value))); + + let Some(auth_data) = headers_map.get(&UniCase::new(PROXY_AUTHENTICATE)) else { + return Err("Proxy requires auth but doesn't send it datails".into()); + }; + + if !auth_data[..6].eq_ignore_ascii_case(b"digest") { + // Fail to auth and the scheme isn't in the + // supported auth method schemes + return Err("Bad credentials".into()); + } + + // Analize challenge params + let data = str::from_utf8(auth_data)?; + let state = digest_auth::parse(data)?; + if self.before && !state.stale { + return Err("Bad credentials".into()); + } + + // Update the digest state + self.digest_state.replace(Some(state)); + self.before = true; + + let closed = match headers_map.get(&UniCase::new(CONNECTION)) { + Some(conn_header) => conn_header.eq_ignore_ascii_case(b"close"), + None => false, + }; - self.server_outbuf.append(&mut self.data_buf); - self.data_buf.clear(); + if closed || version == 0 { + // Close mio stream connection and reset it + // Reset all the buffers + self.server_inbuf.clear(); + self.server_outbuf.clear(); + self.send_tunnel_request()?; - self.state = HttpState::Established; - return self.state_change(); + self.state = HttpState::Reset; + return Ok(()); + } + + // The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must + // compute the lenght of the response in order to detect the next frame (response) + // [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length) + + // Transfer-Encoding isn't supported yet + if let Some(_) = headers_map.get(&UniCase::new(TRANSFER_ENCODING)) { + unimplemented!("Header Transfer-Encoding not supported"); + } + + let content_length = match headers_map.get(&UniCase::new(CONTENT_LENGTH)) { + Some(v) => { + let value = str::from_utf8(v)?; + + // https://www.rfc-editor.org/rfc/rfc9110#section-5.6.1 + match value.parse::() { + Ok(x) => x, + Err(_) => { + let mut it = value.split(',').map(|x| x.parse::()); + let f = it.next().unwrap()?; + for k in it { + if k? != f { + return Err("Malformed response".into()); + } + } + f + } + } + } + None => { + // Close the connection by information miss + self.server_inbuf.clear(); + self.server_outbuf.clear(); + self.send_tunnel_request()?; + + self.state = HttpState::Reset; + return Ok(()); } + }; + + // Handshake state + self.state = HttpState::ExpectResponse; + self.skip = content_length + len; + + return self.state_change(); + } + HttpState::ExpectResponse => { + if self.skip > 0 { + let cnt = self.skip.min(self.server_inbuf.len()); + self.server_inbuf.drain(..cnt); + self.skip -= cnt; } - self.server_inbuf.drain(0..counter); + if self.skip == 0 { + // Expected to the server_inbuff to be empty + + // self.server_outbuf.append(&mut self.data_buf); + // self.data_buf.clear(); + self.send_tunnel_request()?; + self.state = HttpState::ExpectResponseHeaders; + + return self.state_change(); + } } HttpState::Established => { self.client_outbuf.extend(self.server_inbuf.iter()); @@ -110,6 +309,9 @@ impl HttpConnection { self.server_inbuf.clear(); self.client_inbuf.clear(); } + HttpState::Reset => { + self.state = HttpState::ExpectResponseHeaders; + } _ => {} } Ok(()) @@ -175,11 +377,16 @@ impl TcpProxy for HttpConnection { }, } } + + fn reset_connection(&self) -> bool { + self.state == HttpState::Reset + } } pub(crate) struct HttpManager { server: SocketAddr, credentials: Option, + digest_state: Rc>>, } impl ConnectionManager for HttpManager { @@ -195,7 +402,11 @@ impl ConnectionManager for HttpManager { if connection.proto != IpProtocol::Tcp { return Ok(None); } - Ok(Some(Box::new(HttpConnection::new(connection, manager)))) + Ok(Some(Box::new(HttpConnection::new( + connection, + manager, + self.digest_state.clone(), + )?))) } fn close_connection(&self, _: &Connection) {} @@ -214,6 +425,7 @@ impl HttpManager { Rc::new(Self { server, credentials, + digest_state: Rc::new(RefCell::new(None)), }) } } diff --git a/src/lib.rs b/src/lib.rs index 1c8bb00..6d65302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,15 +110,15 @@ impl Options { #[derive(Default, Clone, Debug)] pub struct Credentials { - pub(crate) username: Vec, - pub(crate) password: Vec, + pub(crate) username: String, + pub(crate) password: String, } impl Credentials { pub fn new(username: &str, password: &str) -> Self { Self { - username: username.as_bytes().to_vec(), - password: password.as_bytes().to_vec(), + username: String::from(username), + password: String::from(password), } } } diff --git a/src/socks.rs b/src/socks.rs index 69b6cab..ed1b477 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -156,10 +156,10 @@ impl SocksConnection { } self.server_outbuf.extend(ip_vec); if let Some(credentials) = credentials { - self.server_outbuf.extend(&credentials.username); + self.server_outbuf.extend(credentials.username.as_bytes()); if !credentials.password.is_empty() { self.server_outbuf.push_back(b':'); - self.server_outbuf.extend(&credentials.password); + self.server_outbuf.extend(credentials.password.as_bytes()); } } self.server_outbuf.push_back(0); @@ -250,10 +250,10 @@ impl SocksConnection { let credentials = self.credentials.as_ref().unwrap_or(&tmp); self.server_outbuf .extend(&[1u8, credentials.username.len() as u8]); - self.server_outbuf.extend(&credentials.username); + self.server_outbuf.extend(credentials.username.as_bytes()); self.server_outbuf .extend(&[credentials.password.len() as u8]); - self.server_outbuf.extend(&credentials.password); + self.server_outbuf.extend(credentials.password.as_bytes()); self.state = SocksState::ReceiveAuthResponse; self.state_change() } @@ -424,6 +424,10 @@ impl TcpProxy for SocksConnection { }, } } + + fn reset_connection(&self) -> bool { + false + } } pub struct SocksManager { diff --git a/src/tun2proxy.rs b/src/tun2proxy.rs index ec984b4..876377e 100644 --- a/src/tun2proxy.rs +++ b/src/tun2proxy.rs @@ -228,6 +228,7 @@ pub(crate) trait TcpProxy { fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; fn connection_established(&self) -> bool; fn have_data(&mut self, dir: Direction) -> bool; + fn reset_connection(&self) -> bool; } pub(crate) trait ConnectionManager { @@ -291,7 +292,7 @@ impl<'a> TunToProxy<'a> { let mut virt = VirtualTunDevice::new(tun.capabilities()); let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?; let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?; - let mut iface = Interface::new(config, &mut virt); + let mut iface = Interface::new(config, &mut virt, smoltcp::time::Instant::now()); iface.update_ip_addrs(|ip_addrs| { ip_addrs.push(IpCidr::new(gateway4.into(), 0)).unwrap(); ip_addrs.push(IpCidr::new(gateway6.into(), 0)).unwrap() @@ -699,6 +700,10 @@ impl<'a> TunToProxy<'a> { return Ok(()); } let connection = conn_ref.unwrap().clone(); + let server = self + .get_connection_manager(&connection) + .unwrap() + .get_server(); (|| -> Result<(), Error> { if event.is_readable() || event.is_read_closed() { @@ -737,6 +742,30 @@ impl<'a> TunToProxy<'a> { return Ok(()); } + // The handler request for reset the server connection + if state.handler.reset_connection() { + // Closes the connection with the proxy + state.mio_stream.shutdown(Both)?; + + info!("RESETED {}", connection); + + // TODO: Improve the call upstairs + state.mio_stream = TcpStream::connect(server)?; + + _ = self.poll.registry().deregister(&mut state.mio_stream); + self.poll.registry().register( + &mut state.mio_stream, + state.token, + Interest::WRITABLE, + )?; + + state.wait_read = true; + state.wait_write = true; + state.close_state = 0; + + return Ok(()); + } + if read == 0 || event.is_read_closed() { state.wait_read = false; state.close_state |= SERVER_WRITE_CLOSED;