diff --git a/src/client/mod.rs b/src/client/mod.rs index da3a15d..567a186 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -275,6 +275,7 @@ async fn client_main( async fn spawn_ssh( server: &str, + sudo: bool, ) -> Result<(tokio::process::Child, u16), std::io::Error> { let socks_port = { let listener = TcpListener::bind("127.0.0.1:0").await?; @@ -285,9 +286,11 @@ async fn spawn_ssh( cmd.arg("-T") .arg("-D") .arg(socks_port.to_string()) - .arg(server) - .arg("fwd") - .arg("--server"); + .arg(server); + if sudo { + cmd.arg("sudo"); + } + cmd.arg("fwd").arg("--server"); cmd.stdout(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped()); @@ -314,12 +317,16 @@ fn is_sigint(status: std::process::ExitStatus) -> bool { } } -async fn client_connect_loop(remote: &str, events: mpsc::Sender) { +async fn client_connect_loop( + remote: &str, + sudo: bool, + events: mpsc::Sender, +) { loop { _ = events.send(ui::UIEvent::Disconnected).await; let (mut child, socks_port) = - spawn_ssh(remote).await.expect("failed to spawn"); + spawn_ssh(remote, sudo).await.expect("failed to spawn"); let mut stderr = child .stderr @@ -377,7 +384,7 @@ async fn client_connect_loop(remote: &str, events: mpsc::Sender) { } } -pub async fn run_client(remote: &str) { +pub async fn run_client(remote: &str, sudo: bool) { let (event_sender, event_receiver) = mpsc::channel(1024); _ = log::set_boxed_logger(ui::Logger::new(event_sender.clone())); log::set_max_level(LevelFilter::Info); @@ -395,7 +402,7 @@ pub async fn run_client(remote: &str) { // Start the reconnect loop. tokio::select! { _ = ui.run() => (), - _ = client_connect_loop(remote, event_sender) => () + _ = client_connect_loop(remote, sudo, event_sender) => () } } diff --git a/src/main.rs b/src/main.rs index 243f4f4..667ff1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); fn usage() { println!(indoc! {" -usage: fwd [--version] ( | browse ) +usage: fwd [options] ( | browse ) To connect a client to a server that has an `fwd` installed in its path, run `fwd ` on the client, where is the name of the server to @@ -13,6 +13,15 @@ connect to. On a server that already has a client connected to it you can use `fwd browse ` to open `` in the default browser of the client. + +Options: + --version Print the version of fwd and exit + --sudo, -s Run the server side of fwd with `sudo`. This allows the + client to forward ports that are open by processes being + run under other accounts (e.g., docker containers being + run as root), but requires sudo access on the server and + *might* end up forwarding ports that you do not want + forwarded (e.g., port 22 for sshd, or port 53 for systemd.) "}); } @@ -21,33 +30,46 @@ enum Args { Help, Version, Server, - Client(String), + Client(String, bool), Browse(String), Error, } fn parse_args(args: Vec) -> Args { - // Look for help; allow it to come anywhere because sometimes you just - // want to jam it on the end of an existing command line. - for arg in &args { + let mut server = None; + let mut sudo = None; + let mut rest = Vec::new(); + + for arg in args.into_iter().skip(1) { if arg == "--help" || arg == "-?" || arg == "-h" { return Args::Help; + } else if arg == "--version" { + return Args::Version; + } else if arg == "--server" { + server = Some(true) + } else if arg == "--sudo" || arg == "-s" { + sudo = Some(true) + } else { + rest.push(arg) } } - // No help, parse for reals. - if args.len() >= 2 && args[1] == "--version" { - Args::Version - } else if args.len() == 2 && &args[1] == "--server" { - Args::Server - } else if args.len() == 3 && args[1] == "browse" { - Args::Browse(args[2].to_string()) - } else { - if args.len() != 2 { - Args::Error + if server.unwrap_or(false) { + if rest.len() == 0 && sudo.is_none() { + Args::Server } else { - Args::Client(args[1].to_string()) + Args::Error } + } else if rest.len() > 1 && rest[0] == "browse" { + if rest.len() == 2 { + Args::Browse(rest[1].to_string()) + } else { + Args::Error + } + } else if rest.len() == 1 { + Args::Client(rest[0].to_string(), sudo.unwrap_or(false)) + } else { + Args::Error } } @@ -66,8 +88,8 @@ async fn main() { Args::Browse(url) => { fwd::browse_url(&url).await; } - Args::Client(server) => { - fwd::run_client(&server).await; + Args::Client(server, sudo) => { + fwd::run_client(&server, sudo).await; } Args::Error => { usage(); @@ -111,13 +133,18 @@ mod tests { assert_arg_parse!(&["browse", "google.com", "what"], Args::Error); assert_arg_parse!(&["a", "b"], Args::Error); assert_arg_parse!(&["--server", "something"], Args::Error); + assert_arg_parse!(&["--server", "--sudo"], Args::Error); + assert_arg_parse!(&["--server", "-s"], Args::Error); } #[test] fn client() { - assert_arg_parse!(&["foo.com"], Args::Client(_)); - assert_arg_parse!(&["a"], Args::Client(_)); - assert_arg_parse!(&["browse"], Args::Client(_)); + assert_arg_parse!(&["foo.com"], Args::Client(_, false)); + assert_arg_parse!(&["a"], Args::Client(_, false)); + assert_arg_parse!(&["browse"], Args::Client(_, false)); + assert_arg_parse!(&["foo.com", "--sudo"], Args::Client(_, true)); + assert_arg_parse!(&["a", "-s"], Args::Client(_, true)); + assert_arg_parse!(&["-s", "browse"], Args::Client(_, true)); } #[test]