From 5e2f6f7c0e97a7cc08f619aa74a6d4e4f63504cc Mon Sep 17 00:00:00 2001 From: GoogleGeek Date: Mon, 10 Jun 2024 16:40:55 +0800 Subject: [PATCH] Revert "support HTTP Basic auth" This reverts commit ea036f2790a033b945377fdad533a2c2629cd43b. --- .idea/.gitignore | 5 - .idea/modules.xml | 8 -- .idea/socks-to-http-proxy.iml | 11 -- .idea/vcs.xml | 6 - Cargo.lock | 7 - Cargo.toml | 1 - src/main.rs | 45 +------ src/main2.rs | 180 -------------------------- src/main3.rs | 233 ---------------------------------- 9 files changed, 1 insertion(+), 495 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/modules.xml delete mode 100644 .idea/socks-to-http-proxy.iml delete mode 100644 .idea/vcs.xml delete mode 100644 src/main2.rs delete mode 100644 src/main3.rs diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c11f3c3..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/socks-to-http-proxy.iml b/.idea/socks-to-http-proxy.iml deleted file mode 100644 index cf84ae4..0000000 --- a/.idea/socks-to-http-proxy.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b8c3254..7d3f7f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,12 +89,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bytes" version = "1.5.0" @@ -573,7 +567,6 @@ dependencies = [ name = "sthp" version = "0.4.0" dependencies = [ - "base64", "bytes", "clap", "color-eyre", diff --git a/Cargo.toml b/Cargo.toml index 8396cae..276e99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,3 @@ http-body-util = "0.1.0-rc.2" tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } hyper-util = { git = "https://github.com/hyperium/hyper-util.git", rev = "229757e565e0935a7a3b1d0f9e9ab88d9310e779" } -base64 = "0.22.1" diff --git a/src/main.rs b/src/main.rs index fb4167e..576111c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,6 @@ use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::upgrade::Upgraded; use hyper::{Method, Request, Response}; -use hyper::header::{HeaderValue, PROXY_AUTHENTICATE}; -use base64::engine::general_purpose; -use base64::Engine; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; @@ -56,10 +53,6 @@ struct Cli { /// Comma-separated list of allowed domains #[arg(long, value_delimiter = ',')] allowed_domains: Option>, - - /// HTTP Basic Auth credentials in the format "user:passwd" - #[arg(long, default_value = "")] - http_basic: Option, } #[tokio::main] @@ -79,8 +72,6 @@ async fn main() -> Result<()> { let addr = SocketAddr::from((args.listen_ip, port)); let allowed_domains = args.allowed_domains; let allowed_domains = &*Box::leak(Box::new(allowed_domains)); - let http_basic = args.http_basic.map(|hb| format!("Basic {}", general_purpose::STANDARD.encode(hb))); - let http_basic = &*Box::leak(Box::new(http_basic)); let listener = TcpListener::bind(addr).await?; info!("Listening on http://{}", addr); @@ -89,7 +80,7 @@ async fn main() -> Result<()> { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); - let serve_connection = service_fn(move |req| proxy(req, socks_addr, auth, &http_basic, allowed_domains)); + let serve_connection = service_fn(move |req| proxy(req, socks_addr, auth, allowed_domains)); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new() @@ -109,43 +100,9 @@ async fn proxy( req: Request, socks_addr: SocketAddr, auth: &'static Option, - http_basic: &Option, allowed_domains: &Option>, ) -> Result>, hyper::Error> { let uri = req.uri(); - let mut http_authed = false; - let hm = req.headers(); - - if hm.contains_key("proxy-authorization") { - let config_auth = match http_basic { - Some(value) => value.clone(), - None => String::new(), - }; - let http_auth = hm.get("proxy-authorization").unwrap(); - if http_auth == &HeaderValue::from_str(&config_auth).unwrap() { - http_authed = true; - } - } else { - // When the request does not contain a Proxy-Authorization header, - // send a 407 response code and a Proxy-Authenticate header - let mut response = Response::new(full("Proxy authentication required")); - *response.status_mut() = http::StatusCode::PROXY_AUTHENTICATION_REQUIRED; - response.headers_mut().insert( - PROXY_AUTHENTICATE, - HeaderValue::from_static("Basic realm=\"proxy\""), - ); - return Ok(response); - } - - if !http_authed { - warn!("Failed to authenticate: {:?}", hm); - let mut resp = Response::new(full( - "Authorization failed, you are not allowed through the proxy.", - )); - *resp.status_mut() = http::StatusCode::FORBIDDEN; - return Ok(resp); - } - let method = req.method(); debug!("Proxying request: {} {}", method, uri); if let (Some(allowed_domains), Some(request_domain)) = (allowed_domains, req.uri().host()) { diff --git a/src/main2.rs b/src/main2.rs deleted file mode 100644 index a673fd5..0000000 --- a/src/main2.rs +++ /dev/null @@ -1,180 +0,0 @@ -mod auth; - -use crate::auth::Auth; -use clap::{Args, Parser}; -use color_eyre::eyre::Result; -use tokio_socks::tcp::Socks5Stream; -use tracing::{debug, info, warn}; -use tracing_subscriber::EnvFilter; - -use std::net::{Ipv4Addr, SocketAddr}; -use std::str::FromStr; - -use bytes::Bytes; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; -use hyper::client::conn::http1::Builder; -use hyper::server::conn::http1; -use hyper::service::service_fn; -use hyper::upgrade::Upgraded; -use hyper::{Method, Request, Response}; - -use hyper_util::rt::TokioIo; -use tokio::net::TcpListener; -use base64::encode; - -#[derive(Debug, Args)] -#[group()] -struct Auths { - /// Socks5 username - #[arg(short = 'u', long, required = false)] - username: String, - - /// Socks5 password - #[arg(short = 'P', long, required = false)] - password: String, -} - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Cli { - /// port where Http proxy should listen - #[arg(short, long, default_value_t = 8080)] - port: u16, - - #[arg(long, default_value = "0.0.0.0")] - listen_ip: Ipv4Addr, - - #[command(flatten)] - auth: Option, - - /// Socks5 proxy address - #[arg(short, long, default_value = "127.0.0.1:1080")] - socks_address: SocketAddr, - - /// Comma-separated list of allowed domains - #[arg(long, value_delimiter = ',')] - allowed_domains: Option>, - - /// HTTP Basic Auth in the format "user:passwd" - #[arg(long, required = false)] - httpbasic: Option, -} - -#[tokio::main] -async fn main() -> Result<()> { - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("sthp=debug")); - tracing_subscriber::fmt().with_env_filter(filter).init(); - color_eyre::install()?; - - let args = Cli::parse(); - - let socks_addr = args.socks_address; - let port = args.port; - let auth = args.auth.map(|a| Auth::new(a.username, a.password)); - let httpbasic = args.httpbasic.clone(); - - let listener = TcpListener::bind((args.listen_ip, port)).await?; - info!("Listening on {}:{}", args.listen_ip, port); - - loop { - let (stream, _) = listener.accept().await?; - let auth = auth.clone(); - let socks_addr = socks_addr; - let httpbasic = httpbasic.clone(); - - tokio::task::spawn(async move { - if let Err(err) = http1::Builder::new() - .serve_connection( - stream, - service_fn(|req| handle_request(req, socks_addr, &auth, &httpbasic)), - ) - .await - { - warn!("Error serving connection: {:?}", err); - } - }); - } -} - -async fn handle_request( - req: Request, - socks_addr: SocketAddr, - auth: &Option, - httpbasic: &Option, -) -> Result>, hyper::Error> { - let host = match host_addr(req.uri()) { - Some(host) => host, - None => return Ok(Response::builder().status(400).body(empty()).unwrap()), - }; - - let addr = format!("{}:{}", host, req.uri().port_u16().unwrap_or(80)); - debug!("Proxying request to {} via SOCKS5 proxy at {}", addr, socks_addr); - - let stream = match auth { - Some(auth) => Socks5Stream::connect_with_password(socks_addr, addr, &auth.username, &auth.password) - .await - .unwrap(), - None => Socks5Stream::connect(socks_addr, addr).await.unwrap(), - }; - - let io = TokioIo::new(stream); - - let (mut sender, conn) = Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .handshake(io) - .await?; - tokio::task::spawn(async move { - if let Err(err) = conn.await { - warn!("Connection failed: {:?}", err); - } - }); - - let mut req = req; - - if let Some(httpbasic) = httpbasic { - let encoded = encode(httpbasic); - let auth_header_value = format!("Basic {}", encoded); - req.headers_mut().insert("Authorization", auth_header_value.parse().unwrap()); - } - - let resp = sender.send_request(req).await?; - Ok(resp.map(|b| b.boxed())) -} - -fn host_addr(uri: &http::Uri) -> Option { - uri.authority().map(|auth| auth.to_string()) -} - -fn empty() -> BoxBody { - Empty::::new().map_err(|never| match never {}).boxed() -} - -fn full>(chunk: T) -> BoxBody { - Full::new(chunk.into()).map_err(|never| match never {}).boxed() -} - -async fn tunnel( - upgraded: Upgraded, - addr: String, - socks_addr: SocketAddr, - auth: &Option, -) -> Result<()> { - let mut stream = match auth { - Some(auth) => Socks5Stream::connect_with_password(socks_addr, addr, &auth.username, &auth.password).await?, - None => Socks5Stream::connect(socks_addr, addr).await?, - }; - - let mut upgraded = TokioIo::new(upgraded); - - // Proxying data - let (from_client, from_server) = - tokio::io::copy_bidirectional(&mut upgraded, &mut stream).await?; - - // Print message when done - debug!( - "client wrote {} bytes and received {} bytes", - from_client, from_server - ); - Ok(()) -} diff --git a/src/main3.rs b/src/main3.rs deleted file mode 100644 index 704ab8f..0000000 --- a/src/main3.rs +++ /dev/null @@ -1,233 +0,0 @@ -mod auth; - -use crate::auth::Auth; -use clap::{Args, Parser}; -use color_eyre::eyre::Result; - -use tokio_socks::tcp::Socks5Stream; -use tracing::{debug, info, warn}; -use tracing_subscriber::EnvFilter; - -use std::net::{Ipv4Addr, SocketAddr}; - -use bytes::Bytes; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; -use hyper::client::conn::http1::Builder; -use hyper::server::conn::http1; -use hyper::service::service_fn; -use hyper::upgrade::Upgraded; -use hyper::{Method, Request, Response}; -use hyper::header::{HeaderValue, PROXY_AUTHENTICATE}; - -use hyper_util::rt::TokioIo; -use tokio::net::TcpListener; - -#[derive(Debug, Args)] -#[group()] -struct Auths { - /// Socks5 username - #[arg(short = 'u', long, required = false)] - username: String, - - /// Socks5 password - #[arg(short = 'P', long, required = false)] - password: String, -} - -#[derive(Parser, Debug)] -#[command(author, version, about,long_about=None)] -struct Cli { - /// port where Http proxy should listen - #[arg(short, long, default_value_t = 8080)] - port: u16, - - #[arg(long, default_value = "0.0.0.0")] - listen_ip: Ipv4Addr, - - #[command(flatten)] - auth: Option, - - /// Socks5 proxy address - #[arg(short, long, default_value = "127.0.0.1:1080")] - socks_address: SocketAddr, - - /// Comma-separated list of allowed domains - #[arg(long, value_delimiter = ',')] - allowed_domains: Option>, - - /// HTTP Basic Auth in the format "user:passwd" - #[arg(long, required = false)] - httpbasic: Option, -} - -#[tokio::main] -async fn main() -> Result<()> { - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("sthp=debug")); - tracing_subscriber::fmt().with_env_filter(filter).init(); - color_eyre::install()?; - - let args = Cli::parse(); - - let socks_addr = args.socks_address; - let port = args.port; - let auth = args - .auth - .map(|auth| Auth::new(auth.username, auth.password)); - let auth = &*Box::leak(Box::new(auth)); - let addr = SocketAddr::from((args.listen_ip, port)); - let allowed_domains = args.allowed_domains; - let allowed_domains = &*Box::leak(Box::new(allowed_domains)); - let httpbasic = args.httpbasic.map(|hb| format!("Basic {}", base64::encode(hb))); - let httpbasic = &*Box::leak(Box::new(httpbasic)); - - let listener = TcpListener::bind(addr).await?; - info!("Listening on {}", addr); - - loop { - let (stream, _) = listener.accept().await?; - tokio::task::spawn(proxy( - stream, - socks_addr, - auth, - allowed_domains, - httpbasic, - )); - } -} - -async fn proxy( - stream: tokio::net::TcpStream, - socks_addr: SocketAddr, - auth: &Option, - allowed_domains: &Option>, - httpbasic: &Option, -) -> Result<()> { - let service = service_fn(move |req| { - let uri = req.uri(); - // hack for HTTP Basic Auth - let hm = req.headers(); - let mut authed = false; - - if let Some(config_auth) = httpbasic { - if hm.contains_key("proxy-authorization") { - let http_auth = hm.get("proxy-authorization").unwrap(); - if http_auth == config_auth { - authed = true; - } - } else { - // 当请求没有包含Proxy-Authorization头部时,发送407响应码和Proxy-Authenticate头部 - let mut response = Response::new(full("Proxy authentication required")); - *response.status_mut() = http::StatusCode::PROXY_AUTHENTICATION_REQUIRED; - response.headers_mut().insert( - PROXY_AUTHENTICATE, - HeaderValue::from_static("Basic realm=\"proxy\""), - ); - return Box::pin(async move { Ok(response) }); - } - - if !authed { - warn!("Failed to authenticate: {:?}", hm); - let mut resp = Response::new(full( - "Authorization failed, you are not allowed through the proxy.", - )); - *resp.status_mut() = http::StatusCode::FORBIDDEN; - return Box::pin(async move { Ok(resp) }); - } - } - - let method = req.method(); - debug!("Proxying request: {} {}", method, uri); - if let (Some(allowed_domains), Some(request_domain)) = (allowed_domains, req.uri().host()) { - if !allowed_domains.contains(&request_domain.to_string()) { - warn!("Domain not allowed: {}", request_domain); - return Box::pin(async move { - Ok(Response::builder().status(403).body(empty()).unwrap()) - }); - } - } - - let host = match host_addr(req.uri()) { - Some(host) => host, - None => return Box::pin(async move { - Ok(Response::builder().status(400).body(empty()).unwrap()) - }), - }; - - let addr = format!("{}:{}", host, req.uri().port_u16().unwrap_or(80)); - debug!("Proxying request to {} via SOCKS5 proxy at {}", addr, socks_addr); - - let stream = match auth { - Some(auth) => Socks5Stream::connect_with_password(socks_addr, addr, &auth.username, &auth.password) - .await - .unwrap(), - None => Socks5Stream::connect(socks_addr, addr).await.unwrap(), - }; - - let io = TokioIo::new(stream); - - let (mut sender, conn) = Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .handshake(io) - .await?; - tokio::task::spawn(async move { - if let Err(err) = conn.await { - warn!("Connection failed: {:?}", err); - } - }); - - let resp = sender.send_request(req).await?; - Ok(resp.map(|b| b.boxed())) - }); - - http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(stream, service) - .await?; - Ok(()) -} - -fn host_addr(uri: &http::Uri) -> Option { - uri.authority().map(|auth| auth.to_string()) -} - -fn empty() -> BoxBody { - Empty::::new() - .map_err(|never| match never {}) - .boxed() -} - -fn full>(chunk: T) -> BoxBody { - Full::new(chunk.into()) - .map_err(|never| match never {}) - .boxed() -} - -async fn tunnel( - upgraded: Upgraded, - addr: String, - socks_addr: SocketAddr, - auth: &Option, -) -> Result<()> { - let mut stream = match auth { - Some(auth) => { - Socks5Stream::connect_with_password(socks_addr, addr, &auth.username, &auth.password) - .await? - } - None => Socks5Stream::connect(socks_addr, addr).await?, - }; - - let mut upgraded = TokioIo::new(upgraded); - - // Proxying data - let (from_client, from_server) = - tokio::io::copy_bidirectional(&mut upgraded, &mut stream).await?; - - // Print message when done - debug!( - "client wrote {} bytes and received {} bytes", - from_client, from_server - ); - Ok(()) -}