1use std::io::Write;
2use std::net::ToSocketAddrs;
3use std::sync::Arc;
4use std::time::Duration;
5
6use tokio::{fs, time};
7
8use crate::error::ClientError;
9use crate::{Handler, Output};
10
11pub enum AuthMethod {
12 Password(String),
14 Key(String),
16}
17
18pub struct ClientBuilder {
19 username: String,
20 auth: Option<AuthMethod>,
21 connect_timeout: Duration,
22}
23
24impl ClientBuilder {
25 pub fn new() -> Self {
26 Self {
27 username: String::default(),
28 auth: None,
29 connect_timeout: Duration::from_secs(10),
30 }
31 }
32
33 pub fn username<S: ToString>(&mut self, username: S) -> &mut Self {
34 self.username = username.to_string();
35 self
36 }
37
38 pub fn auth(&mut self, auth: AuthMethod) -> &mut Self {
39 self.auth = Some(auth);
40 self
41 }
42
43 pub fn connect_timeout(&mut self, timeout: Duration) -> &mut Self {
44 self.connect_timeout = timeout;
45 self
46 }
47
48 pub async fn connect<T: ToSocketAddrs>(&self, addr: T) -> Result<Client, ClientError> {
49 let config = Arc::new(thrussh::client::Config::default());
50 match time::timeout(
51 self.connect_timeout,
52 thrussh::client::connect(config, addr, Handler::default()),
53 )
54 .await
55 {
56 Ok(Ok(handle)) => {
57 if self.username.is_empty() {
58 return Err(ClientError::UsernameEmpty);
59 }
60 let mut client = Client {
61 inner: handle,
62 username: self.username.clone(),
63 };
64 match &self.auth {
65 Some(AuthMethod::Password(pass)) => client.auth_with_password(&pass).await?,
66 Some(AuthMethod::Key(path)) => {
67 let secret: String = fs::read_to_string(path).await?;
68 let key_pair = thrussh_keys::decode_secret_key(&secret, None)?;
69 client.auth_with_key_pair(Arc::new(key_pair)).await?
70 }
71 None => {}
72 }
73 Ok(client)
74 }
75 Ok(Err(err)) => return Err(err),
76 Err(_) => return Err(ClientError::Timeout),
77 }
78 }
79}
80
81pub struct Client {
82 username: String,
83 inner: thrussh::client::Handle<Handler>,
84}
85
86impl Client {
87 pub fn builder() -> ClientBuilder {
88 ClientBuilder::new()
89 }
90
91 pub(crate) async fn auth_with_password(&mut self, password: &str) -> Result<(), ClientError> {
92 match self
93 .inner
94 .authenticate_password(&self.username, password)
95 .await
96 {
97 Ok(true) => Ok(()),
98 Ok(false) => Err(ClientError::AuthFailed(String::from(
99 "username or password is wrong!",
100 ))),
101 Err(e) => Err(ClientError::ClientFailed(e)),
102 }
103 }
104
105 pub(crate) async fn auth_with_key_pair(
106 &mut self,
107 key: Arc<thrussh_keys::key::KeyPair>,
108 ) -> Result<(), ClientError> {
109 match self.inner.authenticate_publickey(&self.username, key).await {
110 Ok(true) => Ok(()),
111 Ok(false) => Err(ClientError::AuthFailed(String::from(
112 "username or key is wrong!",
113 ))),
114 Err(e) => Err(ClientError::ClientFailed(e)),
115 }
116 }
117
118 #[allow(unused_variables)]
119 pub async fn output(&mut self, command: &str) -> Result<Output, ClientError> {
120 let mut channel = self.inner.channel_open_session().await?;
121 channel.exec(true, command).await?;
122 let mut res = Output::default();
123 while let Some(msg) = channel.wait().await {
124 match msg {
125 thrussh::ChannelMsg::Data { ref data } => {
126 res.stdout.write_all(&data)?;
127 }
128 thrussh::ChannelMsg::ExtendedData { ref data, ext } => {
129 res.stderr.write_all(&data)?;
130 }
131 thrussh::ChannelMsg::ExitStatus { exit_status } => {
132 res.code = Some(exit_status);
133 }
134 _ => {}
135 }
136 }
137 Ok(res)
138 }
139
140 pub async fn close(&mut self) -> Result<(), ClientError> {
141 self.inner
142 .disconnect(thrussh::Disconnect::ByApplication, "", "English")
143 .await?;
144 Ok(())
145 }
146}