use std::io::{self, Read, Write};
use std::net::{Shutdown, SocketAddr};
use super::{PROXY_ENV, TOKEN_ENV, socks};
pub const COMMAND: &str = "__proxy-connect";
pub fn run(args: &[String]) -> i32 {
match connect_and_relay(args) {
Ok(()) => 0,
Err(message) => {
eprintln!("gitaway: {message}");
1
}
}
}
fn connect_and_relay(args: &[String]) -> Result<(), String> {
let (host, port, original) = match args {
[host, port] => (host, port, None),
[host, port, original] => (host, port, Some(original)),
_ => {
return Err(format!(
"usage: gitaway {COMMAND} <host> <port> [<original name>]"
));
}
};
let port: u16 = port
.parse()
.map_err(|_| format!("`{port}` isn't a port number"))?;
let proxy: SocketAddr = std::env::var(PROXY_ENV)
.ok()
.and_then(|value| value.parse().ok())
.ok_or_else(|| {
format!("{COMMAND} only works inside a command gitaway runs ({PROXY_ENV} isn't set)")
})?;
let token = std::env::var(TOKEN_ENV).map_err(|_| format!("{TOKEN_ENV} isn't set"))?;
let unreachable = |name: &str, e: io::Error| format!("couldn't reach {name}:{port}: {e}");
let home_by_original_name = match original.filter(|name| !name.eq_ignore_ascii_case(host)) {
Some(name) => {
socks::connect_home_only(proxy, &token, name, port).map_err(|e| unreachable(name, e))?
}
None => None,
};
let stream = match home_by_original_name {
Some(stream) => stream,
None => socks::connect(proxy, &token, host, port).map_err(|e| unreachable(host, e))?,
};
relay(stream).map_err(|e| format!("connection to {host}:{port}: {e}"))
}
fn relay(stream: std::net::TcpStream) -> io::Result<()> {
let mut to_server = stream.try_clone()?;
std::thread::spawn(move || {
let _ = io::copy(&mut io::stdin().lock(), &mut to_server);
let _ = to_server.shutdown(Shutdown::Write);
});
let mut from_server = stream;
let mut stdout = io::stdout().lock();
let mut buf = vec![0; 64 * 1024];
loop {
let n = from_server.read(&mut buf)?;
if n == 0 {
return Ok(());
}
stdout.write_all(&buf[..n])?;
stdout.flush()?;
}
}