use ssh2::{self, Session};
use std::error::Error;
use std::fs;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::path::Path;
struct Watcher {
pattern: String, response: String, case_sensitive: bool, }
pub struct RunOptions {
watchers: Vec<Watcher>,
}
impl RunOptions {
pub fn new() -> RunOptions {
RunOptions { watchers: Vec::new() }
}
pub fn set_watcher(&mut self, pattern: &str, response: &str, case_sensitive: bool) {
self.watchers.push(Watcher { pattern: pattern.to_string(), response: response.to_string(), case_sensitive });
}
}
pub struct SudoOptions {
sudo_user: String,
sudo_password: String,
sudo_pattern: String,
run: RunOptions,
}
impl SudoOptions {
pub fn new(sudo_user: &str, sudo_password: &str, sudo_pattern: &str) -> SudoOptions {
SudoOptions {
sudo_user: sudo_user.to_string(),
sudo_password: sudo_password.to_string(),
sudo_pattern: sudo_pattern.to_string(),
run: RunOptions { watchers: Vec::new() },
}
}
pub fn set_watcher(&mut self, pattern: &str, response: &str, case_sensitive: bool) {
self.run.watchers.push(Watcher { pattern: pattern.to_string(), response: response.to_string(), case_sensitive });
}
}
pub enum Auth {
Password(String),
Privatekey(String),
Privatekeyfile(String),
}
impl Clone for Auth {
fn clone(&self) -> Self {
match self {
Self::Password(arg0) => Self::Password(arg0.clone()),
Self::Privatekey(arg0) => Self::Privatekey(arg0.clone()),
Self::Privatekeyfile(arg0) => Self::Privatekeyfile(arg0.clone()),
}
}
}
pub struct Connection {
host: String,
port: u16,
user: String,
auth: Auth,
timeout: u32,
session: Session,
}
impl Connection {
pub fn new(host: String, port: u16, user: String, auth: Auth) -> Result<Connection, Box<dyn Error>> {
let timeout: u32 = 60000;
let addr = format!("{}:{}", host, port);
let tcp = TcpStream::connect(addr)?;
let mut session = Session::new().unwrap();
session.set_tcp_stream(tcp);
session.handshake().unwrap();
session.set_timeout(timeout);
let mut conn = Connection {
host,
port,
user,
auth,
timeout,
session,
};
conn.authenticated()?;
conn.session.authenticated();
Ok(conn)
}
fn authenticated(&mut self) -> Result<(), ssh2::Error> {
match &self.auth {
Auth::Password(password) => self.session.userauth_password(&self.user, &password),
Auth::Privatekey(privatekey) => self.session.userauth_pubkey_memory(&self.user, None, &privatekey, None),
Auth::Privatekeyfile(privatekey_file) => {
let privatekey_path = Path::new(&privatekey_file);
self.session.userauth_pubkey_file(&self.user, None, privatekey_path, None)
}
}
}
pub fn get_host(&mut self) -> String {
self.host.to_string()
}
pub fn get_port(&mut self) -> u16 {
self.port
}
pub fn get_user(&mut self) -> String {
self.user.to_string()
}
pub fn get_auth(&mut self) -> Auth {
self.auth.clone()
}
pub fn get_timeout(&mut self) -> u32 {
self.timeout
}
pub fn set_timeout(&mut self, timeout: u32) {
self.timeout = timeout;
self.session.set_timeout(self.timeout);
}
fn run_watcher(&mut self, channel: &mut ssh2::Channel, watchers: Vec<Watcher>, stdout: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
loop {
let mut buf = [0; 1024];
let n = channel.read(&mut buf)?;
if n == 0 {
break;
}
let slice = &buf[0..n];
stdout.extend_from_slice(slice);
let s = String::from_utf8_lossy(slice);
for w in watchers.iter() {
if w.case_sensitive {
if s.contains(&w.pattern) {
channel.write_all(format!("{}\n", w.response).as_bytes())?;
channel.flush()?;
}
} else {
if s.to_uppercase().contains(&w.pattern.to_uppercase()) {
channel.write_all(format!("{}\n", w.response).as_bytes())?;
channel.flush()?;
}
}
}
}
Ok(())
}
pub fn run_with_options(&mut self, cmd: &str, options: Option<RunOptions>) -> Result<(String, String, i32), Box<dyn Error>> {
let command = format!("PATH=$PATH:/usr/bin:/usr/sbin {}", cmd);
let mut channel = self.session.channel_session()?;
channel.handle_extended_data(ssh2::ExtendedData::Merge)?;
channel.exec(&command)?;
let mut stdout = Vec::new();
match options {
Some(opt) => {
if opt.watchers.len() != 0 {
self.run_watcher(&mut channel, opt.watchers, &mut stdout)?
}
}
None => (),
}
let mut stdout = String::from_utf8_lossy(&stdout);
let mut out = String::new();
channel.read_to_string(&mut out)?;
stdout.to_mut().push_str(&out);
let mut stderr = String::new();
channel.stderr().read_to_string(&mut stderr).unwrap();
channel.wait_close().unwrap();
let status: i32 = channel.exit_status()?;
Ok((stdout.to_string(), stderr.to_string(), status))
}
pub fn sudo_with_options(&mut self, cmd: &str, options: Option<SudoOptions>) -> Result<(String, String, i32), Box<dyn Error>> {
let mut opt = match options {
Some(options) => options,
None => SudoOptions::new("root", "", "[sudo] password:"),
};
if opt.sudo_user == "" {
opt.sudo_user = "root".to_string();
}
if opt.sudo_password == "" {
match self.get_auth() {
Auth::Password(pwd) => opt.sudo_password = pwd.to_string(),
Auth::Privatekey(_) => {},
Auth::Privatekeyfile(_) => {},
}
}
if opt.sudo_pattern == "" {
opt.sudo_pattern = "[sudo] password:".to_string()
}
opt.set_watcher(&opt.sudo_pattern.clone(), &opt.sudo_password.clone(), true);
let cmd = format!("sudo -S -p '{}' -H -u {} /bin/bash -l -c \"cd; {}\"", opt.sudo_pattern, opt.sudo_user, cmd);
self.run_with_options(&cmd, Some(opt.run))
}
pub fn run(&mut self, cmd: &str) -> Result<(String, String, i32), Box<dyn Error>> {
self.run_with_options(cmd, None)
}
pub fn sudo(&mut self, cmd: &str) -> Result<(String, String, i32), Box<dyn Error>> {
self.sudo_with_options(cmd, None)
}
pub fn scp(&mut self, source: &str, target: &str) -> Result<(), Box<dyn Error>> {
let source_file = fs::read(source)?;
let mut target_file = self.session.scp_send(Path::new(&target), 0o755, source_file.len() as u64, None)?;
target_file.write(&source_file)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::*;
#[test]
fn it_works() {
let myhost = env::var("MYHOST").expect("$MYHOST is not defined");
let myport = env::var("MYPORT").expect("$MYPORT is not defined");
let myusername = env::var("MYUSERNAME").expect("$MYUSERNAME is not defined");
let mypassword = env::var("MYPASSWORD").expect("$MYPASSWORD is not defined");
let port: u16 = myport.parse().unwrap();
let mut conn = Connection::new(myhost, port, myusername, Auth::Password(mypassword)).unwrap();
let (stdout, stderr, status) = conn.run("ls").unwrap();
println!("The output of run is: {}, The outerr of run is: {}, The status code of run is: {}, end.", stdout, stderr, status);
let (stdout, stderr, status) = conn.sudo("ls -l /root/").unwrap();
println!("The output of sudo is: {}, The outerr of sudo is: {}, The status code of sudo is: {}, end.", stdout, stderr, status);
conn.scp("/home/xxx/x.xml", "/home/xxx/xx.xml").unwrap();
}
}