diff --git a/src/builder.rs b/src/builder.rs index 8ff2dda94..21f7e7bf8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -11,7 +11,7 @@ use std::{fs, io}; use dirs::state_dir; use once_cell::sync::OnceCell; use tempfile::{Builder, TempDir}; -use tokio::process; +use tokio::process::Command; /// The returned `&'static Path` can be coreced to any lifetime. fn get_default_control_dir<'a>() -> Result<&'a Path, Error> { @@ -60,6 +60,7 @@ pub struct SessionBuilder { user: Option, port: Option, keyfile: Option, + password: Option, connect_timeout: Option, server_alive_interval: Option, known_hosts_check: KnownHosts, @@ -78,6 +79,7 @@ impl Default for SessionBuilder { user: None, port: None, keyfile: None, + password: None, connect_timeout: None, server_alive_interval: None, known_hosts_check: KnownHosts::Add, @@ -106,8 +108,8 @@ impl SessionBuilder { /// Set the ssh user (`ssh -l`). /// /// Defaults to `None`. - pub fn user(&mut self, user: String) -> &mut Self { - self.user = Some(user); + pub fn user(&mut self, user: impl AsRef) -> &mut Self { + self.user = Some(user.as_ref().to_string()); self } @@ -127,6 +129,14 @@ impl SessionBuilder { self } + /// Set the ssh password. + /// + /// Defaults to `None`. + pub fn password(&mut self, p: impl AsRef) -> &mut Self { + self.password = Some(p.as_ref().to_string()); + self + } + /// See [`KnownHosts`]. /// /// Default `KnownHosts::Add`. @@ -337,7 +347,7 @@ impl SessionBuilder { let mut with_overrides = self.clone(); if let Some(user) = user { - with_overrides.user(user.to_owned()); + with_overrides.user(user); } if let Some(port) = port { @@ -347,6 +357,54 @@ impl SessionBuilder { (Cow::Owned(with_overrides), destination) } + /// Create ssh master Command + fn init_command(&self, log: &PathBuf, master: &PathBuf) -> Command { + if let Some(pass) = &self.password { + let mut init = Command::new("sshpass"); + + init.stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("-p") + .arg(pass) + .arg("ssh") + .arg("-E") + .arg(log) + .arg("-S") + .arg(master) + .arg("-M") + .arg("-f") + .arg("-N") + .arg("-o") + .arg("ControlPersist=yes") + .arg("-o") + .arg("BatchMode=no") + .arg("-o") + .arg(self.known_hosts_check.as_option()); + return init; + } + + let mut init = Command::new("ssh"); + + init.stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .arg("-E") + .arg(log) + .arg("-S") + .arg(master) + .arg("-M") + .arg("-f") + .arg("-N") + .arg("-o") + .arg("ControlPersist=yes") + .arg("-o") + .arg("BatchMode=yes") + .arg("-o") + .arg(self.known_hosts_check.as_option()); + init + } + /// Create ssh master session and return [`TempDir`] which /// contains the ssh control socket. pub async fn launch_master(&self, destination: &str) -> Result { @@ -368,25 +426,9 @@ impl SessionBuilder { .map_err(Error::Master)?; let log = dir.path().join("log"); + let master = dir.path().join("master"); - let mut init = process::Command::new("ssh"); - - init.stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .arg("-E") - .arg(&log) - .arg("-S") - .arg(dir.path().join("master")) - .arg("-M") - .arg("-f") - .arg("-N") - .arg("-o") - .arg("ControlPersist=yes") - .arg("-o") - .arg("BatchMode=yes") - .arg("-o") - .arg(self.known_hosts_check.as_option()); + let mut init = self.init_command(&log, &master); if let Some(ref timeout) = self.connect_timeout { init.arg("-o").arg(format!("ConnectTimeout={}", timeout)); diff --git a/src/session.rs b/src/session.rs index d023cc8bc..b95902699 100644 --- a/src/session.rs +++ b/src/session.rs @@ -180,9 +180,10 @@ impl Session { /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. /// + /// The function provide keypair-based authentication. + /// /// If connecting requires interactive authentication based on `STDIN` (such as reading a - /// password), the connection will fail. Consider setting up keypair-based authentication - /// instead. + /// password), consider use [`Session::connect_with_pass`] function instead. /// /// For more options, see [`SessionBuilder`]. #[cfg(feature = "process-mux")] @@ -194,6 +195,35 @@ impl Session { .await } + /// Connect to the host at the given `host` over SSH using process impl, which will + /// spawn a new ssh process for each `Child` created. + /// + /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be + /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. + /// + /// The function provide interactive authentication based on `STDIN` (such as reading a + /// password). + /// + /// Warning ⚠️: current use sshpass non-interactive ssh password auth. + /// See detail: + /// + /// The current implementation of sshpass is only temporary, and the functionality will be + /// optimized later. If using this feature, pay attention to security risks. + /// + /// For more options, see [`SessionBuilder`]. + #[cfg(feature = "process-mux")] + #[cfg_attr(docsrs, doc(cfg(feature = "process-mux")))] + pub async fn connect_with_pass>( + destination: S, + pass: impl AsRef, + ) -> Result { + SessionBuilder::default() + .password(pass) + .known_hosts_check(KnownHosts::Accept) + .connect(destination) + .await + } + /// Connect to the host at the given `host` over SSH using native mux impl, which /// will create a new socket connection for each `Child` created. /// @@ -202,9 +232,10 @@ impl Session { /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. /// + /// The function provide keypair-based authentication. + /// /// If connecting requires interactive authentication based on `STDIN` (such as reading a - /// password), the connection will fail. Consider setting up keypair-based authentication - /// instead. + /// password), consider use [`Session::connect_mux_with_pass`] function instead. /// /// For more options, see [`SessionBuilder`]. #[cfg(feature = "native-mux")] @@ -219,6 +250,37 @@ impl Session { .await } + /// Connect to the host at the given `host` over SSH using native mux impl, which + /// will create a new socket connection for each `Child` created. + /// + /// See the crate-level documentation for more details on the difference between native and process-based mux. + /// + /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be + /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. + /// + /// The function provide interactive authentication based on `STDIN` (such as reading a + /// password). + /// + /// Warning ⚠️: current use sshpass non-interactive ssh password auth. + /// See detail: + /// + /// The current implementation of sshpass is only temporary, and the functionality will be + /// optimized later. If using this feature, pay attention to security risks. + /// + /// For more options, see [`SessionBuilder`]. + #[cfg(feature = "native-mux")] + #[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))] + pub async fn connect_mux_with_pass>( + destination: S, + pass: impl AsRef, + ) -> Result { + SessionBuilder::default() + .password(pass) + .known_hosts_check(KnownHosts::Accept) + .connect_mux(destination) + .await + } + /// Check the status of the underlying SSH connection. #[cfg(not(windows))] #[cfg_attr(docsrs, doc(cfg(not(windows))))]