diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index c3c4964..b6925b0 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -581,6 +581,15 @@ impl FileTransfer for FtpFileTransfer { } } + /// ### exec + /// + /// Execute a command on remote host + fn exec(&mut self, _cmd: &str) -> Result { + Err(FileTransferError::new( + FileTransferErrorType::UnsupportedFeature, + )) + } + /// ### send_file /// /// Send file to remote @@ -1068,6 +1077,19 @@ mod tests { assert!(ftp.disconnect().is_ok()); }*/ + #[test] + fn test_filetransfer_ftp_exec() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(ftp + .connect(String::from("speedtest.tele2.net"), 21, None, None) + .is_ok()); + // Pwd + assert!(ftp.exec("echo 1;").is_err()); + // Disconnect + assert!(ftp.disconnect().is_ok()); + } + #[test] fn test_filetransfer_ftp_uninitialized() { let file: FsFile = FsFile { diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 3a561f4..8c2f1e3 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -189,6 +189,11 @@ pub trait FileTransfer { /// Stat file and return FsEntry fn stat(&mut self, path: &Path) -> Result; + /// ### exec + /// + /// Execute a command on remote host + fn exec(&mut self, cmd: &str) -> Result; + /// ### send_file /// /// Send file to remote diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 6452fbd..03d4f96 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -737,6 +737,27 @@ impl FileTransfer for ScpFileTransfer { } } + /// ### exec + /// + /// Execute a command on remote host + fn exec(&mut self, cmd: &str) -> Result { + match self.is_connected() { + true => { + let p: PathBuf = self.wrkdir.clone(); + match self.perform_shell_cmd_with_path(p.as_path(), cmd) { + Ok(output) => Ok(output), + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("{}", err), + )), + } + } + false => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), + } + } + /// ### send_file /// /// Send file to remote @@ -1016,6 +1037,25 @@ mod tests { } } + #[test] + fn test_filetransfer_scp_exec() { + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); + assert!(client + .connect( + String::from("test.rebex.net"), + 22, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Check session and scp + assert!(client.session.is_some()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Disconnect + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_scp_recv() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index c7ce56d..d0b7b18 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -32,7 +32,7 @@ use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::system::sshkey_storage::SshKeyStorage; // Includes -use ssh2::{FileStat, OpenFlags, OpenType, Session, Sftp}; +use ssh2::{Channel, FileStat, OpenFlags, OpenType, Session, Sftp}; use std::io::{BufReader, BufWriter, Read, Write}; use std::net::{SocketAddr, TcpStream, ToSocketAddrs}; use std::path::{Path, PathBuf}; @@ -189,6 +189,57 @@ impl SftpFileTransfer { }), } } + + /// ### perform_shell_cmd_with + /// + /// Perform a shell command, but change directory to specified path first + fn perform_shell_cmd_with_path(&mut self, cmd: &str) -> Result { + self.perform_shell_cmd(format!("cd \"{}\"; {}", self.wrkdir.display(), cmd).as_str()) + } + + /// ### perform_shell_cmd + /// + /// Perform a shell command and read the output from shell + /// This operation is, obviously, blocking. + fn perform_shell_cmd(&mut self, cmd: &str) -> Result { + match self.session.as_mut() { + Some(session) => { + // Create channel + let mut channel: Channel = match session.channel_session() { + Ok(ch) => ch, + Err(err) => { + return Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("Could not open channel: {}", err), + )) + } + }; + // Execute command + if let Err(err) = channel.exec(cmd) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("Could not execute command \"{}\": {}", cmd, err), + )); + } + // Read output + let mut output: String = String::new(); + match channel.read_to_string(&mut output) { + Ok(_) => { + // Wait close + let _ = channel.wait_close(); + Ok(output) + } + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("Could not read output: {}", err), + )), + } + } + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), + } + } } impl FileTransfer for SftpFileTransfer { @@ -547,6 +598,26 @@ impl FileTransfer for SftpFileTransfer { } } + /// ### exec + /// + /// Execute a command on remote host + fn exec(&mut self, cmd: &str) -> Result { + match self.is_connected() { + true => { + match self.perform_shell_cmd_with_path(cmd) { + Ok(output) => Ok(output), + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("{}", err), + )), + } + } + false => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), + } + } + /// ### send_file /// /// Send file to remote @@ -856,6 +927,25 @@ mod tests { } } + #[test] + fn test_filetransfer_sftp_exec() { + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); + assert!(client + .connect( + String::from("test.rebex.net"), + 22, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Check session and scp + assert!(client.session.is_some()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Disconnect + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_sftp_recv() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());