1use ssh2::{self, Session};
52use std::error::Error;
53use std::fs;
54use std::io::{Read, Write};
55use std::net::TcpStream;
56use std::path::Path;
57struct Watcher {
60 pattern: String, response: String, case_sensitive: bool, }
65
66pub struct RunOptions {
67 watchers: Vec<Watcher>,
68}
69
70impl RunOptions {
71 pub fn new() -> RunOptions {
72 RunOptions { watchers: Vec::new() }
73 }
74
75 pub fn set_watcher(&mut self, pattern: &str, response: &str, case_sensitive: bool) {
76 self.watchers.push(Watcher { pattern: pattern.to_string(), response: response.to_string(), case_sensitive });
77 }
78}
79
80pub struct SudoOptions {
81 sudo_user: String,
82 sudo_password: String,
83 sudo_pattern: String,
84 run: RunOptions,
85}
86
87impl SudoOptions {
88 pub fn new(sudo_user: &str, sudo_password: &str, sudo_pattern: &str) -> SudoOptions {
89 SudoOptions {
90 sudo_user: sudo_user.to_string(),
91 sudo_password: sudo_password.to_string(),
92 sudo_pattern: sudo_pattern.to_string(),
93 run: RunOptions { watchers: Vec::new() },
94 }
95 }
96
97 pub fn set_watcher(&mut self, pattern: &str, response: &str, case_sensitive: bool) {
98 self.run.watchers.push(Watcher { pattern: pattern.to_string(), response: response.to_string(), case_sensitive });
99 }
100}
101
102pub enum Auth {
103 Password(String),
104 Privatekey(String),
105 Privatekeyfile(String),
106}
107
108impl Clone for Auth {
109 fn clone(&self) -> Self {
110 match self {
111 Self::Password(arg0) => Self::Password(arg0.clone()),
112 Self::Privatekey(arg0) => Self::Privatekey(arg0.clone()),
113 Self::Privatekeyfile(arg0) => Self::Privatekeyfile(arg0.clone()),
114 }
115 }
116}
117
118pub struct Connection {
119 host: String,
120 port: u16,
121 user: String,
122 auth: Auth,
123 timeout: u32,
124 session: Session,
125}
126
127impl Connection {
128 pub fn new(host: String, port: u16, user: String, auth: Auth) -> Result<Connection, Box<dyn Error>> {
129 let timeout: u32 = 60000;
130
131 let addr = format!("{}:{}", host, port);
132 let tcp = TcpStream::connect(addr)?;
133
134 let mut session = Session::new().unwrap();
135 session.set_tcp_stream(tcp);
136 session.handshake().unwrap();
137 session.set_timeout(timeout);
138
139 let mut conn = Connection {
140 host,
141 port,
142 user,
143 auth,
144 timeout,
145 session,
146 };
147 conn.authenticated()?;
148 conn.session.authenticated();
149
150 Ok(conn)
151 }
152
153 fn authenticated(&mut self) -> Result<(), ssh2::Error> {
154 match &self.auth {
155 Auth::Password(password) => self.session.userauth_password(&self.user, &password),
156 Auth::Privatekey(privatekey) => self.session.userauth_pubkey_memory(&self.user, None, &privatekey, None),
157 Auth::Privatekeyfile(privatekey_file) => {
158 let privatekey_path = Path::new(&privatekey_file);
159 self.session.userauth_pubkey_file(&self.user, None, privatekey_path, None)
160 }
161 }
162 }
163
164 pub fn get_host(&mut self) -> String {
165 self.host.to_string()
166 }
167
168 pub fn get_port(&mut self) -> u16 {
169 self.port
170 }
171
172 pub fn get_user(&mut self) -> String {
173 self.user.to_string()
174 }
175
176 pub fn get_auth(&mut self) -> Auth {
177 self.auth.clone()
178 }
179
180 pub fn get_timeout(&mut self) -> u32 {
181 self.timeout
182 }
183
184 pub fn set_timeout(&mut self, timeout: u32) {
185 self.timeout = timeout;
186 self.session.set_timeout(self.timeout);
187 }
188
189 fn run_watcher(&mut self, channel: &mut ssh2::Channel, watchers: Vec<Watcher>, stdout: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
190 loop {
191 let mut buf = [0; 1024];
192
193 let n = channel.read(&mut buf)?;
194 if n == 0 {
195 break;
196 }
197
198 let slice = &buf[0..n];
199 stdout.extend_from_slice(slice);
200
201 let s = String::from_utf8_lossy(slice);
202
203 for w in watchers.iter() {
204 if w.case_sensitive {
205 if s.contains(&w.pattern) {
206 channel.write_all(format!("{}\n", w.response).as_bytes())?;
207 channel.flush()?;
208 }
209
210 } else {
211 if s.to_uppercase().contains(&w.pattern.to_uppercase()) {
212 channel.write_all(format!("{}\n", w.response).as_bytes())?;
213 channel.flush()?;
214 }
215 }
216 }
217 }
218 Ok(())
219 }
220
221 pub fn run_with_options(&mut self, cmd: &str, options: Option<RunOptions>) -> Result<(String, String, i32), Box<dyn Error>> {
222 let command = format!("PATH=$PATH:/usr/bin:/usr/sbin {}", cmd);
223
224 let mut channel = self.session.channel_session()?;
225
226 channel.handle_extended_data(ssh2::ExtendedData::Merge)?;
234 channel.exec(&command)?;
235
236 let mut stdout = Vec::new();
237
238 match options {
239 Some(opt) => {
240 if opt.watchers.len() != 0 {
241 self.run_watcher(&mut channel, opt.watchers, &mut stdout)?
242 }
243 }
244 None => (),
245 }
246
247 let mut stdout = String::from_utf8_lossy(&stdout);
248
249 let mut out = String::new();
251 channel.read_to_string(&mut out)?;
252 stdout.to_mut().push_str(&out);
253
254 let mut stderr = String::new();
255 channel.stderr().read_to_string(&mut stderr).unwrap();
256
257 channel.wait_close().unwrap();
258
259 let status: i32 = channel.exit_status()?;
260
261 Ok((stdout.to_string(), stderr.to_string(), status))
262 }
263
264 pub fn sudo_with_options(&mut self, cmd: &str, options: Option<SudoOptions>) -> Result<(String, String, i32), Box<dyn Error>> {
265
266 let mut opt = match options {
267 Some(options) => options,
268 None => SudoOptions::new("root", "", "[sudo] password:"),
269 };
270
271 if opt.sudo_user == "" {
272 opt.sudo_user = "root".to_string();
273 }
274
275 if opt.sudo_password == "" {
276 match self.get_auth() {
277 Auth::Password(pwd) => opt.sudo_password = pwd.to_string(),
278 Auth::Privatekey(_) => {},
279 Auth::Privatekeyfile(_) => {},
280 }
281 }
282
283 if opt.sudo_pattern == "" {
284 opt.sudo_pattern = "[sudo] password:".to_string()
285 }
286
287 opt.set_watcher(&opt.sudo_pattern.clone(), &opt.sudo_password.clone(), true);
288
289 let cmd = format!("sudo -S -p '{}' -H -u {} /bin/bash -l -c \"cd; {}\"", opt.sudo_pattern, opt.sudo_user, cmd);
290
291 self.run_with_options(&cmd, Some(opt.run))
292
293 }
294
295 pub fn run(&mut self, cmd: &str) -> Result<(String, String, i32), Box<dyn Error>> {
296 self.run_with_options(cmd, None)
297 }
298
299 pub fn sudo(&mut self, cmd: &str) -> Result<(String, String, i32), Box<dyn Error>> {
300 self.sudo_with_options(cmd, None)
301 }
302
303 pub fn scp(&mut self, source: &str, target: &str) -> Result<(), Box<dyn Error>> {
304 let source_file = fs::read(source)?;
306 let mut target_file = self.session.scp_send(Path::new(&target), 0o755, source_file.len() as u64, None)?;
307
308 target_file.write(&source_file)?;
309
310 Ok(())
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use std::env;
317
318 use super::*;
319
320 #[test]
321 fn it_works() {
322 let myhost = env::var("MYHOST").expect("$MYHOST is not defined");
323 let myport = env::var("MYPORT").expect("$MYPORT is not defined");
324 let myusername = env::var("MYUSERNAME").expect("$MYUSERNAME is not defined");
325 let mypassword = env::var("MYPASSWORD").expect("$MYPASSWORD is not defined");
326
327 let port: u16 = myport.parse().unwrap();
328
329 let mut conn = Connection::new(myhost, port, myusername, Auth::Password(mypassword)).unwrap();
330
331 let (stdout, stderr, status) = conn.run("ls").unwrap();
332 println!("The output of run is: {}, The outerr of run is: {}, The status code of run is: {}, end.", stdout, stderr, status);
333
334 let (stdout, stderr, status) = conn.sudo("ls -l /root/").unwrap();
335 println!("The output of sudo is: {}, The outerr of sudo is: {}, The status code of sudo is: {}, end.", stdout, stderr, status);
336
337 conn.scp("/home/xxx/x.xml", "/home/xxx/xx.xml").unwrap();
338 }
339}