ssh/session/
session_local.rs

1use std::{
2    cell::RefCell,
3    io::{Read, Write},
4    rc::Rc,
5    time::Duration,
6};
7use tracing::*;
8
9#[cfg(feature = "scp")]
10use crate::channel::LocalScp;
11use crate::{
12    channel::{LocalChannel, LocalExec, LocalShell},
13    client::Client,
14    constant::{size, ssh_channel_fail_code, ssh_connection_code, ssh_str},
15    error::{SshError, SshResult},
16    model::TerminalSize,
17    model::{Data, Packet, RcMut, SecPacket, U32Iter},
18};
19
20pub struct LocalSession<S>
21where
22    S: Read + Write,
23{
24    client: RcMut<Client>,
25    stream: RcMut<S>,
26    channel_num: U32Iter,
27}
28
29impl<S> LocalSession<S>
30where
31    S: Read + Write,
32{
33    pub(crate) fn new(client: Client, stream: S) -> Self {
34        Self {
35            client: Rc::new(RefCell::new(client)),
36            stream: Rc::new(RefCell::new(stream)),
37            channel_num: U32Iter::default(),
38        }
39    }
40
41    /// close the local session and consume it
42    ///
43    pub fn close(self) {
44        info!("Client close");
45        drop(self)
46    }
47
48    /// Modify the timeout setting
49    /// in case the user wants to change the timeout during ssh operations.
50    ///
51    pub fn set_timeout(&mut self, timeout: Option<Duration>) {
52        self.client.borrow_mut().set_timeout(timeout)
53    }
54
55    /// open a [LocalExec] channel which can excute commands
56    ///
57    pub fn open_exec(&mut self) -> SshResult<LocalExec<S>> {
58        let channel = self.open_channel()?;
59        channel.exec()
60    }
61
62    /// open a [LocalScp] channel which can download/upload files/directories
63    ///
64    #[cfg(feature = "scp")]
65    pub fn open_scp(&mut self) -> SshResult<LocalScp<S>> {
66        let channel = self.open_channel()?;
67        channel.scp()
68    }
69
70    /// open a [LocalShell] channel which can download/upload files/directories
71    ///
72    pub fn open_shell(&mut self) -> SshResult<LocalShell<S>> {
73        self.open_shell_terminal(TerminalSize::from(80, 24))
74    }
75
76    /// open a [LocalShell] channel
77    ///
78    /// custom terminal dimensions
79    ///
80    pub fn open_shell_terminal(&mut self, tv: TerminalSize) -> SshResult<LocalShell<S>> {
81        let channel = self.open_channel()?;
82        channel.shell(tv)
83    }
84
85    pub fn get_raw_io(&mut self) -> RcMut<S> {
86        self.stream.clone()
87    }
88
89    /// open a raw channel
90    ///
91    /// need call `.exec()`, `.shell()`, `.scp()` and so on to convert it to a specific channel
92    ///
93    pub fn open_channel(&mut self) -> SshResult<LocalChannel<S>> {
94        info!("channel opened.");
95
96        let client_channel_no = self.channel_num.next().unwrap();
97        self.send_open_channel(client_channel_no)?;
98        let (server_channel_no, remote_window_size) = self.receive_open_channel()?;
99
100        Ok(LocalChannel::new(
101            server_channel_no,
102            client_channel_no,
103            remote_window_size,
104            self.client.clone(),
105            self.stream.clone(),
106        ))
107    }
108
109    // open channel request
110    fn send_open_channel(&mut self, client_channel_no: u32) -> SshResult<()> {
111        let mut data = Data::new();
112        data.put_u8(ssh_connection_code::CHANNEL_OPEN)
113            .put_str(ssh_str::SESSION)
114            .put_u32(client_channel_no)
115            .put_u32(size::LOCAL_WINDOW_SIZE)
116            .put_u32(size::BUF_SIZE as u32);
117        data.pack(&mut self.client.borrow_mut())
118            .write_stream(&mut *self.stream.borrow_mut())
119    }
120
121    // get the response of the channel request
122    fn receive_open_channel(&mut self) -> SshResult<(u32, u32)> {
123        loop {
124            let mut data = Data::unpack(SecPacket::from_stream(
125                &mut *self.stream.borrow_mut(),
126                &mut self.client.borrow_mut(),
127            )?)?;
128
129            let message_code = data.get_u8();
130            match message_code {
131                // Successfully open a channel
132                ssh_connection_code::CHANNEL_OPEN_CONFIRMATION => {
133                    data.get_u32();
134                    let server_channel_no = data.get_u32();
135                    let remote_window_size = data.get_u32();
136                    // remote packet size, currently don't need it
137                    data.get_u32();
138                    return Ok((server_channel_no, remote_window_size));
139                }
140                /*
141                    byte CHANNEL_OPEN_FAILURE
142                    uint32 recipient channel
143                    uint32 reason code
144                    string description,ISO-10646 UTF-8 [RFC3629]
145                    string language tag,[RFC3066]
146                */
147                // Fail to open a channel
148                ssh_connection_code::CHANNEL_OPEN_FAILURE => {
149                    data.get_u32();
150                    // error code
151                    let code = data.get_u32();
152                    // error detail: By default is utf-8
153                    let description =
154                        String::from_utf8(data.get_u8s()).unwrap_or_else(|_| String::from("error"));
155                    // language tag, assume to be en-US
156                    data.get_u8s();
157
158                    let err_msg = match code {
159                        ssh_channel_fail_code::ADMINISTRATIVELY_PROHIBITED => {
160                            format!("ADMINISTRATIVELY_PROHIBITED: {}", description)
161                        }
162                        ssh_channel_fail_code::CONNECT_FAILED => {
163                            format!("CONNECT_FAILED: {}", description)
164                        }
165                        ssh_channel_fail_code::UNKNOWN_CHANNEL_TYPE => {
166                            format!("UNKNOWN_CHANNEL_TYPE: {}", description)
167                        }
168                        ssh_channel_fail_code::RESOURCE_SHORTAGE => {
169                            format!("RESOURCE_SHORTAGE: {}", description)
170                        }
171                        _ => description,
172                    };
173                    return Err(SshError::GeneralError(err_msg));
174                }
175                ssh_connection_code::GLOBAL_REQUEST => {
176                    let mut data = Data::new();
177                    data.put_u8(ssh_connection_code::REQUEST_FAILURE);
178                    data.pack(&mut self.client.borrow_mut())
179                        .write_stream(&mut *self.stream.borrow_mut())?;
180                    continue;
181                }
182                x => {
183                    debug!("Ignore ssh msg {}", x);
184                    continue;
185                }
186            }
187        }
188    }
189}