From 67cae613b4d28f897e205e8853ded830c5edf41d Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Mon, 31 May 2021 22:20:49 +0100 Subject: [PATCH] otter cli: Implement remote ssh connection Signed-off-by: Ian Jackson --- src/bin/otter.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++-- src/prelude.rs | 1 + 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 90e485c1..4de91e30 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -64,6 +64,7 @@ impl<'x, T, F: FnMut(&str) -> Result> #[derive(Debug)] enum ServerLocation { Socket(String), + Ssh(String), } use ServerLocation as SL; @@ -79,6 +80,12 @@ struct MainOpts { superuser: bool, spec_dir: String, game: Option, + ssh_command: String, + ssh_proxy_command: String, +} + +fn default_ssh_proxy_command() -> String { + format!("{} {}", DEFAULT_SSH_PROXY_CMD, SSH_PROXY_SUBCMD) } impl MainOpts { @@ -314,6 +321,8 @@ fn main() { subargs: Vec, spec_dir: Option, game: Option, + ssh_command: Option, + ssh_proxy_command: Option, } let (subcommand, subargs, mo) = parse_args::( env::args().collect(), @@ -384,11 +393,19 @@ fn main() { let mut server = ap.refer(&mut rma.server); server + .metavar("S") // one matavar for all the options, bah .add_option(&["--socket"], MapStore(|path| Ok(Some( SL::Socket(path.to_string()) ))), - "connect to server via this socket socket path"); + "connect to server via this socket path S"); + server + .add_option(&["--ssh"], + MapStore(|userhost| Ok(Some( + SL::Ssh(userhost.to_string()) + ))), + "connect to server via ssh, S is [USER@]HOST"); + ap.refer(&mut rma.config_filename) .add_option(&["-C","--config"], StoreOption, "specify server config file (used for finding socket)"); @@ -402,6 +419,20 @@ fn main() { .add_option(&["--super"], StoreTrue, "enable game server superuser access"); + ap.refer(&mut rma.ssh_command) + .metavar("SSH") + .add_option(&["--ssh-command"], StoreOption, + "command to run instead of `ssh` for remote Otter \ + (shell syntax, followex `exec`, \ + interporeted by the local shell)"); + ap.refer(&mut rma.ssh_proxy_command) + .metavar("OTTER-PROXY-COMMAND") + .add_option(&["--ssh-remote-proxy-command"], StoreOption, + Box::leak(Box::new(/* bug in argparse */ format!( + "command to run instead of `{}` for remote Otter \ + (shell syntax, interpreted by the remote shell)", + default_ssh_proxy_command())))); + ap.refer(&mut rma.spec_dir) .add_option(&["--spec-dir"], StoreOption, "directory for table and game specs"); @@ -411,6 +442,7 @@ fn main() { account, nick, timezone, access, server, verbose, config_filename, superuser, subcommand, subargs, spec_dir, layout, game, + ssh_command, ssh_proxy_command }|{ env_logger::Builder::new() .filter_level(log::LevelFilter::Info) @@ -436,6 +468,11 @@ fn main() { )) }); + let ssh_proxy_command = ssh_proxy_command.unwrap_or_else( + default_ssh_proxy_command + ); + let ssh_command = ssh_command.unwrap_or_else(|| "ssh".to_owned()); + let spec_dir = spec_dir.map(Ok::<_,APE>).unwrap_or_else(||{ let cfgf = config.clone().map(|(_c,f)| f).map_err(Into::into); let spec_dir = in_basedir(verbose > 1, @@ -474,6 +511,8 @@ fn main() { superuser, spec_dir, game, + ssh_command, + ssh_proxy_command, })) }, Some(&|w|{ writeln!(w, "\nSubcommands:")?; @@ -561,8 +600,40 @@ impl Conn { #[throws(E)] fn connect(ma: &MainOpts) -> Conn { let chan = match &ma.server { - SL::Socket(socket) => MgmtChannel::connect(socket)?, + + SL::Socket(socket) => { + MgmtChannel::connect(socket)? + }, + + SL::Ssh(user_host) => { + + let user_host = { + let (user,host) = + user_host.split_once('@') + .unwrap_or_else(|| ("Otter", user_host)); + format!("{}@{}", user, host) + }; + + let mut cmd = Command::new("sh"); + cmd.arg(if ma.verbose > 2 { "-xec" } else { "-ec" }); + cmd.arg(format!(r#"exec {} "$@""#, &ma.ssh_command)); + cmd.arg("x"); + let args = [ + &user_host, + &ma.ssh_proxy_command, + ]; + cmd.args(args); + + let desc = (&ma.ssh_command, &args).to_debug(); + + let (w,r) = childio::run_pair(cmd, desc.clone()) + .with_context(|| desc.clone()) + .context("run remote command")?; + MgmtChannel::new_boxed(r,w) + }, + }; + let mut chan = Conn { chan }; if ma.superuser { chan.cmd(&MC::SetSuperuser(true))?; diff --git a/src/prelude.rs b/src/prelude.rs index ef162842..4d49923c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -131,6 +131,7 @@ pub use crate::authproofs::{self, Authorisation, Unauthorised}; pub use crate::authproofs::AuthorisationSuperuser; pub use crate::asseturl::*; pub use crate::bundles::{self, InstanceBundles, MgmtBundleListExt}; +pub use crate::childio; pub use crate::commands::{AccessTokenInfo, AccessTokenReport, MgmtError}; pub use crate::commands::{MgmtCommand, MgmtResponse}; pub use crate::commands::{MgmtGameInstruction, MgmtGameResponse}; -- 2.30.2