1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
use crate::{opt_cstring_to_cstr, opt_str_to_cstring, Error, SessionHolder, SshResult};
use libssh_rs_sys as sys;
use std::convert::TryInto;
use std::ffi::CString;
use std::os::raw::c_int;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;

/// Represents a channel in a `Session`.
///
/// A `Session` can have multiple channels; there is typically one
/// for the shell/program being run, but additional channels can
/// be opened to forward TCP or other connections.
///
/// [open_session](#method.open_session) is often the first
/// thing you will call on the `Channel` after creating it; this establishes
/// the channel for executing commands.
///
/// Then you will typically use either [request_exec](#method.request_exec)
/// to run a non-interactive command, or [request_pty](#method.request_pty)
/// followed [request_shell](#method.request_shell) to set up an interactive
/// remote shell.
///
/// # Thread Safety
///
/// `Channel` is strongly associated with the `Session` to which it belongs.
/// libssh doesn't allow using anything associated with a given `Session`
/// from multiple threads concurrently.  These Rust bindings encapsulate
/// the underlying `Session` in an internal mutex, which allows you to
/// safely operate on the various elements of the session and even move
/// them to other threads, but you need to be aware that calling methods
/// on any of those structs will attempt to lock the underlying session,
/// and this can lead to blocking in surprising situations.
pub struct Channel {
    pub(crate) sess: Arc<Mutex<SessionHolder>>,
    pub(crate) chan_inner: sys::ssh_channel,
}

unsafe impl Send for Channel {}

impl Drop for Channel {
    fn drop(&mut self) {
        let (_sess, chan) = self.lock_session();
        unsafe {
            sys::ssh_channel_free(chan);
        }
    }
}

impl Channel {
    /// Accept an X11 forwarding channel.
    /// Returns a newly created `Channel`, or `None` if no X11 request from the server.
    pub fn accept_x11(&self, timeout: std::time::Duration) -> Option<Self> {
        let (_sess, chan) = self.lock_session();
        let timeout = timeout.as_millis();
        let chan = unsafe { sys::ssh_channel_accept_x11(chan, timeout.try_into().unwrap()) };
        if chan.is_null() {
            None
        } else {
            Some(Self {
                sess: Arc::clone(&self.sess),
                chan_inner: chan,
            })
        }
    }

    fn lock_session(&self) -> (MutexGuard<SessionHolder>, sys::ssh_channel) {
        (self.sess.lock().unwrap(), self.chan_inner)
    }

    /// Close a channel.
    /// This sends an end of file and then closes the channel.
    /// You won't be able to recover any data the server was going
    /// to send or was in buffers.
    pub fn close(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_close(chan) };
        sess.basic_status(res, "error closing channel")
    }

    /// Get the exit status of the channel
    /// (error code from the executed instruction).
    /// This function may block until a timeout (or never) if the other
    /// side is not willing to close the channel.
    pub fn get_exit_status(&self) -> Option<c_int> {
        let (_sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_get_exit_status(chan) };
        if res == -1 {
            None
        } else {
            Some(res)
        }
    }

    /// Check if the channel is closed or not.
    pub fn is_closed(&self) -> bool {
        let (_sess, chan) = self.lock_session();
        unsafe { sys::ssh_channel_is_closed(chan) != 0 }
    }

    /// Check if remote has sent an EOF.
    pub fn is_eof(&self) -> bool {
        let (_sess, chan) = self.lock_session();
        unsafe { sys::ssh_channel_is_eof(chan) != 0 }
    }

    /// Send an end of file on the channel.
    ///
    /// You should call this when you have no additional data to send
    /// to the channel to signal that information to the remote host.
    ///
    /// This doesn't close the channel.
    /// You may still read from it but not write.
    pub fn send_eof(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_send_eof(chan) };
        sess.basic_status(res, "ssh_channel_send_eof failed")
    }

    /// Check if the channel is open or not.
    pub fn is_open(&self) -> bool {
        let (_sess, chan) = self.lock_session();
        unsafe { sys::ssh_channel_is_open(chan) != 0 }
    }

    /// Open an agent authentication forwarding channel.
    /// This type of channel can be opened by a *server* towards a
    /// client in order to provide SSH-Agent services to the server-side
    /// process. This channel can only be opened if the client claimed
    /// support by sending a channel request beforehand.
    pub fn open_auth_agent(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_open_auth_agent(chan) };
        sess.basic_status(res, "ssh_channel_open_auth_agent failed")
    }

    /// Send an `"auth-agent-req"` channel request over an existing session channel.
    ///
    /// This client-side request will enable forwarding the agent
    /// over a secure tunnel. When the server is ready to open one
    /// authentication agent channel, an
    /// ssh_channel_open_request_auth_agent_callback event will be generated.
    pub fn request_auth_agent(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_request_auth_agent(chan) };
        sess.basic_status(res, "ssh_channel_request_auth_agent failed")
    }

    /// Set environment variable.
    /// Some environment variables may be refused by security reasons.
    pub fn request_env(&self, name: &str, value: &str) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let name = CString::new(name)?;
        let value = CString::new(value)?;
        let res = unsafe { sys::ssh_channel_request_env(chan, name.as_ptr(), value.as_ptr()) };
        sess.basic_status(res, "ssh_channel_request_env failed")
    }

    /// Requests a shell; asks the server to spawn the user's shell,
    /// rather than directly executing a command specified by the client.
    ///
    /// The channel must be a session channel; you need to have called
    /// [open_session](#method.open_session) before this will succeed.
    pub fn request_shell(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_request_shell(chan) };
        sess.basic_status(res, "ssh_channel_request_shell failed")
    }

    /// Run a shell command without an interactive shell.
    /// This is similar to 'sh -c command'.
    ///
    /// The channel must be a session channel; you need to have called
    /// [open_session](#method.open_session) before this will succeed.
    pub fn request_exec(&self, command: &str) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let command = CString::new(command)?;
        let res = unsafe { sys::ssh_channel_request_exec(chan, command.as_ptr()) };
        sess.basic_status(res, "ssh_channel_request_exec failed")
    }

    /// Request a subsystem.
    ///
    /// You probably don't need this unless you know what you are doing!
    pub fn request_subsystem(&self, subsys: &str) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let subsys = CString::new(subsys)?;
        let res = unsafe { sys::ssh_channel_request_subsystem(chan, subsys.as_ptr()) };
        sess.basic_status(res, "ssh_channel_request_subsystem failed")
    }

    /// Request a PTY with a specific type and size.
    /// A PTY is useful when you want to run an interactive program on
    /// the remote host.
    ///
    /// `term` is the initial value for the `TERM` environment variable.
    /// If you're not sure what to fill for the values,
    /// `term = "xterm"`, `columns = 80` and `rows = 24` are reasonable
    /// defaults.
    pub fn request_pty(&self, term: &str, columns: u32, rows: u32) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let term = CString::new(term)?;
        let res = unsafe {
            sys::ssh_channel_request_pty_size(
                chan,
                term.as_ptr(),
                columns.try_into().unwrap(),
                rows.try_into().unwrap(),
            )
        };
        sess.basic_status(res, "ssh_channel_request_pty_size failed")
    }

    /// Informs the server that the local size of the PTY has changed
    pub fn change_pty_size(&self, columns: u32, rows: u32) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe {
            sys::ssh_channel_change_pty_size(
                chan,
                columns.try_into().unwrap(),
                rows.try_into().unwrap(),
            )
        };
        sess.basic_status(res, "ssh_channel_change_pty_size failed")
    }

    /// Send a break signal to the server (as described in RFC 4335).
    /// Sends a break signal to the remote process. Note, that remote
    /// system may not support breaks. In such a case this request will
    /// be silently ignored.
    pub fn request_send_break(&self, length: Duration) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_request_send_break(chan, length.as_millis() as _) };
        sess.basic_status(res, "ssh_channel_request_send_break failed")
    }

    /// Send a signal to remote process (as described in RFC 4254, section 6.9).
    /// Sends a signal to the remote process.
    /// Note, that remote system may not support signals concept.
    /// In such a case this request will be silently ignored.
    ///
    /// `signal` is the name of the signal, without the `"SIG"` prefix.
    /// For example, `"ABRT"`, `"INT"`, `"KILL"` and so on.
    ///
    /// The OpenSSH server has only supported signals since OpenSSH version 8.1,
    /// released in 2019.
    /// <https://bugzilla.mindrot.org/show_bug.cgi?id=1424>
    pub fn request_send_signal(&self, signal: &str) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let signal = CString::new(signal)?;
        let res = unsafe { sys::ssh_channel_request_send_signal(chan, signal.as_ptr()) };
        sess.basic_status(res, "ssh_channel_request_send_signal failed")
    }

    /// Open a TCP/IP forwarding channel.
    /// `remote_host`, `remote_port` identify the destination for the
    /// connection.
    /// `source_host`, `source_port` identify the origin of the connection
    /// on the client side; these are used primarily for logging purposes.
    ///
    /// This function does not bind the source port and does not
    /// automatically forward the content of a socket to the channel.
    /// You still have to read/write this channel object to achieve that.
    pub fn open_forward(
        &self,
        remote_host: &str,
        remote_port: u16,
        source_host: &str,
        source_port: u16,
    ) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let remote_host = CString::new(remote_host)?;
        let source_host = CString::new(source_host)?;
        let res = unsafe {
            sys::ssh_channel_open_forward(
                chan,
                remote_host.as_ptr(),
                remote_port as i32,
                source_host.as_ptr(),
                source_port as i32,
            )
        };
        sess.basic_status(res, "ssh_channel_open_forward failed")
    }

    /// Open a UNIX domain socket forwarding channel.
    /// `remote_path` is the path to the unix socket to open on the remote
    /// machine.
    /// `source_host` and `source_port` identify the originating connection
    /// from the client machine and are used for logging purposes.
    ///
    /// This function does not bind the source and does not
    /// automatically forward the content of a socket to the channel.
    /// You still have to read/write this channel object to achieve that.
    pub fn open_forward_unix(
        &self,
        remote_path: &str,
        source_host: &str,
        source_port: u16,
    ) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let remote_path = CString::new(remote_path)?;
        let source_host = CString::new(source_host)?;
        let res = unsafe {
            sys::ssh_channel_open_forward_unix(
                chan,
                remote_path.as_ptr(),
                source_host.as_ptr(),
                source_port as i32,
            )
        };
        sess.basic_status(res, "ssh_channel_open_forward_unix failed")
    }

    /// Sends the `"x11-req"` channel request over an existing session channel.
    /// This will enable redirecting the display of the remote X11 applications
    /// to local X server over an secure tunnel.
    pub fn request_x11(
        &self,
        single_connection: bool,
        protocol: Option<&str>,
        cookie: Option<&str>,
        screen_number: c_int,
    ) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let protocol = opt_str_to_cstring(protocol);
        let cookie = opt_str_to_cstring(cookie);
        let res = unsafe {
            sys::ssh_channel_request_x11(
                chan,
                if single_connection { 1 } else { 0 },
                opt_cstring_to_cstr(&protocol),
                opt_cstring_to_cstr(&cookie),
                screen_number,
            )
        };

        sess.basic_status(res, "ssh_channel_open_forward failed")
    }

    /// Open a session channel (suited for a shell, not TCP forwarding).
    pub fn open_session(&self) -> SshResult<()> {
        let (sess, chan) = self.lock_session();
        let res = unsafe { sys::ssh_channel_open_session(chan) };
        sess.basic_status(res, "ssh_channel_open_session failed")
    }

    /// Polls a channel for data to read.
    /// Returns the number of bytes available for reading.
    /// If `timeout` is None, then blocks until data is available.
    pub fn poll_timeout(
        &self,
        is_stderr: bool,
        timeout: Option<Duration>,
    ) -> SshResult<PollStatus> {
        let (sess, chan) = self.lock_session();
        let timeout = match timeout {
            Some(t) => t.as_millis() as c_int,
            None => -1,
        };
        let res =
            unsafe { sys::ssh_channel_poll_timeout(chan, if is_stderr { 1 } else { 0 }, timeout) };
        match res {
            sys::SSH_ERROR => {
                if let Some(err) = sess.last_error() {
                    Err(err)
                } else {
                    Err(Error::fatal("ssh_channel_poll failed"))
                }
            }
            sys::SSH_EOF => Ok(PollStatus::EndOfFile),
            n if n >= 0 => Ok(PollStatus::AvailableBytes(n as u32)),
            n => Err(Error::Fatal(format!(
                "ssh_channel_poll returned unexpected {} value",
                n
            ))),
        }
    }

    /// Reads data from a channel.
    /// This function may fewer bytes than the buf size.
    pub fn read_timeout(
        &self,
        buf: &mut [u8],
        is_stderr: bool,
        timeout: Option<Duration>,
    ) -> SshResult<usize> {
        let (sess, chan) = self.lock_session();

        let timeout = match timeout {
            Some(t) => t.as_millis() as c_int,
            None => -1,
        };
        let res = unsafe {
            sys::ssh_channel_read_timeout(
                chan,
                buf.as_mut_ptr() as _,
                buf.len() as u32,
                if is_stderr { 1 } else { 0 },
                timeout,
            )
        };
        match res {
            sys::SSH_ERROR => {
                if let Some(err) = sess.last_error() {
                    Err(err)
                } else {
                    Err(Error::fatal("ssh_channel_read_timeout failed"))
                }
            }
            sys::SSH_AGAIN => Err(Error::TryAgain),
            n if n < 0 => Err(Error::Fatal(format!(
                "ssh_channel_read_timeout returned unexpected {} value",
                n
            ))),
            0 if !sess.is_blocking() => Err(Error::TryAgain),
            n => Ok(n as usize),
        }
    }

    /// Get the remote window size.
    /// This is the maximum amounts of bytes the remote side expects us to send
    /// before growing the window again.
    /// A nonzero return value does not guarantee the socket is ready to send that much data.
    /// Buffering may happen in the local SSH packet buffer, so beware of really big window sizes.
    /// A zero return value means that a write will block (if the session is in blocking mode)
    /// until the window grows back.
    pub fn window_size(&self) -> usize {
        let (_sess, chan) = self.lock_session();
        unsafe { sys::ssh_channel_window_size(chan).try_into().unwrap() }
    }

    fn read_impl(&self, buf: &mut [u8], is_stderr: bool) -> std::io::Result<usize> {
        Ok(self.read_timeout(buf, is_stderr, None)?)
    }

    fn write_impl(&self, buf: &[u8], is_stderr: bool) -> SshResult<usize> {
        let (sess, chan) = self.lock_session();

        let res = unsafe {
            (if is_stderr {
                sys::ssh_channel_write_stderr
            } else {
                sys::ssh_channel_write
            })(chan, buf.as_ptr() as _, buf.len() as _)
        };

        match res {
            sys::SSH_ERROR => {
                if let Some(err) = sess.last_error() {
                    Err(err)
                } else {
                    Err(Error::fatal("ssh_channel_read_timeout failed"))
                }
            }
            sys::SSH_AGAIN => Err(Error::TryAgain),
            n if n < 0 => Err(Error::Fatal(format!(
                "ssh_channel_read_timeout returned unexpected {} value",
                n
            ))),
            n => Ok(n as usize),
        }
    }

    /// Returns a struct that implements `std::io::Read`
    /// and that will read data from the stdout channel.
    pub fn stdout(&self) -> impl std::io::Read + '_ {
        ChannelStdout { chan: self }
    }

    /// Returns a struct that implements `std::io::Read`
    /// and that will read data from the stderr channel.
    pub fn stderr(&self) -> impl std::io::Read + '_ {
        ChannelStderr { chan: self }
    }

    /// Returns a struct that implements `std::io::Write`
    /// and that will write data to the stdin channel
    pub fn stdin(&self) -> impl std::io::Write + '_ {
        ChannelStdin { chan: self }
    }
}

/// Represents the stdin stream for the channel.
/// Implements std::io::Write; writing to this struct
/// will write to the stdin of the channel.
struct ChannelStdin<'a> {
    chan: &'a Channel,
}

impl<'a> std::io::Write for ChannelStdin<'a> {
    fn flush(&mut self) -> std::io::Result<()> {
        Ok(self.chan.sess.lock().unwrap().blocking_flush(None)?)
    }

    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        Ok(self.chan.write_impl(buf, false)?)
    }
}

/// Represents the stdout stream for the channel.
/// Implements std::io::Read; reading from this struct
/// will read from the stdout of the channel.
struct ChannelStdout<'a> {
    chan: &'a Channel,
}

impl<'a> std::io::Read for ChannelStdout<'a> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.chan.read_impl(buf, false)
    }
}

/// Represents the stderr stream for the channel.
/// Implements std::io::Read; reading from this struct
/// will read from the stderr of the channel.
struct ChannelStderr<'a> {
    chan: &'a Channel,
}

impl<'a> std::io::Read for ChannelStderr<'a> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.chan.read_impl(buf, true)
    }
}

/// Indicates available data for the stdout or stderr on a `Channel`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PollStatus {
    /// The available bytes to read; may be 0
    AvailableBytes(u32),
    /// The channel is in the EOF state
    EndOfFile,
}