You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
3.5 KiB
127 lines
3.5 KiB
use clap::{Args, Parser};
|
|
use color_eyre::eyre::{OptionExt, Result};
|
|
|
|
use sthp::proxy::auth::Auth;
|
|
use sthp::proxy_request;
|
|
use tracing::{error, info};
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
|
|
|
use base64::engine::general_purpose;
|
|
use base64::Engine;
|
|
use hyper::header::HeaderValue;
|
|
use tokio::net::TcpListener;
|
|
|
|
#[derive(Debug, Args)]
|
|
#[group()]
|
|
struct AuthParams {
|
|
/// Socks5 username
|
|
#[arg(short = 'u', long, required = false)]
|
|
username: String,
|
|
|
|
/// Socks5 password
|
|
#[arg(short = 'P', long, required = false)]
|
|
password: String,
|
|
}
|
|
|
|
fn socket_addr(s: &str) -> Result<SocketAddr> {
|
|
let mut address = s.to_socket_addrs()?;
|
|
let address = address.next();
|
|
address.ok_or_eyre("no IP address found for the hostname".to_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 = "127.0.0.1")]
|
|
listen_ip: IpAddr,
|
|
|
|
#[command(flatten)]
|
|
auth: Option<AuthParams>,
|
|
|
|
/// Socks5 proxy address
|
|
#[arg(short, long, default_value = "127.0.0.1:1080", value_parser=socket_addr)]
|
|
socks_address: SocketAddr,
|
|
|
|
/// Comma-separated list of allowed domains
|
|
#[arg(long, value_delimiter = ',')]
|
|
allowed_domains: Option<Vec<String>>,
|
|
|
|
/// HTTP Basic Auth credentials in the format "user:passwd"
|
|
#[arg(long)]
|
|
http_basic: Option<String>,
|
|
|
|
#[cfg(unix)]
|
|
/// Run process in background
|
|
#[arg(short, long, default_value_t = false)]
|
|
detached: bool,
|
|
}
|
|
|
|
fn main() {
|
|
let args = Cli::parse();
|
|
#[cfg(unix)]
|
|
{
|
|
if args.detached {
|
|
let daemonize = daemonize::Daemonize::new();
|
|
if let Err(e) = daemonize.start() {
|
|
eprintln!("Error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("Failed to build tokio runtime")
|
|
.block_on(main_entry(args))
|
|
.expect("Failed to run program")
|
|
}
|
|
|
|
async fn main_entry(args: Cli) -> 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 socks_addr = args.socks_address;
|
|
let port = args.port;
|
|
let auth_details = args
|
|
.auth
|
|
.map(|auth| Auth::new(auth.username, auth.password));
|
|
let auth_details = &*Box::leak(Box::new(auth_details));
|
|
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)))
|
|
.map(|hb| HeaderValue::from_str(&hb))
|
|
.transpose()?;
|
|
let http_basic = &*Box::leak(Box::new(http_basic));
|
|
|
|
let listener = TcpListener::bind(addr).await?;
|
|
|
|
info!("Listening on http://{}", addr);
|
|
|
|
loop {
|
|
let (stream, client_addr) = listener.accept().await?;
|
|
tokio::task::spawn(async move {
|
|
if let Err(e) = proxy_request(
|
|
stream,
|
|
client_addr,
|
|
socks_addr,
|
|
auth_details.as_ref(),
|
|
allowed_domains.as_ref(),
|
|
http_basic.as_ref(),
|
|
)
|
|
.await
|
|
{
|
|
error!("Error proxying request: {}", e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|