1use crate::{opt_cstring_to_cstr, opt_str_to_cstring, Error, SessionHolder, SshResult};
2use libssh_rs_sys as sys;
3use std::convert::TryInto;
4use std::ffi::{CStr, CString};
5use std::os::raw::c_int;
6use std::sync::{Arc, Mutex, MutexGuard};
7use std::time::Duration;
8
9pub struct Channel {
35 pub(crate) sess: Arc<Mutex<SessionHolder>>,
36 pub(crate) chan_inner: sys::ssh_channel,
37 _callbacks: Box<sys::ssh_channel_callbacks_struct>,
38 callback_state: Box<CallbackState>,
39}
40
41unsafe impl Send for Channel {}
42
43impl Drop for Channel {
44 fn drop(&mut self) {
45 unsafe {
46 sys::ssh_remove_channel_callbacks(self.chan_inner, self._callbacks.as_mut());
48 }
49 let (_sess, chan) = self.lock_session();
50 unsafe {
51 sys::ssh_channel_free(chan);
52 }
53 }
54}
55
56struct CallbackState {
58 signal_state: Mutex<Option<SignalState>>,
59}
60
61#[derive(Clone, Debug)]
62pub struct SignalState {
63 pub signal_name: Option<String>,
64 pub core_dumped: bool,
65 pub error_message: Option<String>,
66 pub language: Option<String>,
67}
68
69fn cstr_to_opt_string(cstr: *const ::std::os::raw::c_char) -> Option<String> {
70 if cstr.is_null() {
71 return None;
72 }
73
74 Some(
75 unsafe { CStr::from_ptr(cstr) }
76 .to_string_lossy()
77 .to_string(),
78 )
79}
80
81unsafe extern "C" fn handle_exit_signal(
82 _session: sys::ssh_session,
83 _channel: sys::ssh_channel,
84 signal: *const ::std::os::raw::c_char,
85 core_dumped: ::std::os::raw::c_int,
86 errmsg: *const ::std::os::raw::c_char,
87 lang: *const ::std::os::raw::c_char,
88 userdata: *mut ::std::os::raw::c_void,
89) {
90 let callback_state: &CallbackState = &*(userdata as *const CallbackState);
91
92 let signal_name = cstr_to_opt_string(signal);
93 let error_message = cstr_to_opt_string(errmsg);
94 let language = cstr_to_opt_string(lang);
95
96 callback_state
97 .signal_state
98 .lock()
99 .unwrap()
100 .replace(SignalState {
101 signal_name,
102 core_dumped: if core_dumped == 0 { false } else { true },
103 error_message,
104 language,
105 });
106}
107
108impl Channel {
109 pub fn accept_x11(&self, timeout: std::time::Duration) -> Option<Self> {
112 let (_sess, chan) = self.lock_session();
113 let timeout = timeout.as_millis();
114 let chan = unsafe { sys::ssh_channel_accept_x11(chan, timeout.try_into().unwrap()) };
115 if chan.is_null() {
116 None
117 } else {
118 Some(Self::new(&self.sess, chan))
119 }
120 }
121
122 pub(crate) fn new(sess: &Arc<Mutex<SessionHolder>>, chan: sys::ssh_channel) -> Self {
123 let callback_state = Box::new(CallbackState {
124 signal_state: Mutex::new(None),
125 });
126
127 let callbacks = Box::new(sys::ssh_channel_callbacks_struct {
128 size: std::mem::size_of::<sys::ssh_channel_callbacks_struct>(),
129 userdata: callback_state.as_ref() as *const CallbackState as *mut _,
130 channel_data_function: None,
131 channel_eof_function: None,
132 channel_close_function: None,
133 channel_signal_function: None,
134 channel_exit_status_function: None,
135 channel_exit_signal_function: Some(handle_exit_signal),
136 channel_pty_request_function: None,
137 channel_shell_request_function: None,
138 channel_auth_agent_req_function: None,
139 channel_x11_req_function: None,
140 channel_pty_window_change_function: None,
141 channel_exec_request_function: None,
142 channel_env_request_function: None,
143 channel_subsystem_request_function: None,
144 channel_write_wontblock_function: None,
145 channel_open_response_function: None,
146 channel_request_response_function: None,
147 });
148
149 unsafe { sys::ssh_set_channel_callbacks(chan, callbacks.as_ref() as *const _ as *mut _) };
150
151 Self {
152 sess: Arc::clone(&sess),
153 chan_inner: chan,
154 callback_state,
155 _callbacks: callbacks,
156 }
157 }
158
159 fn lock_session(&self) -> (MutexGuard<SessionHolder>, sys::ssh_channel) {
160 (self.sess.lock().unwrap(), self.chan_inner)
161 }
162
163 pub fn close(&self) -> SshResult<()> {
168 let (sess, chan) = self.lock_session();
169 let res = unsafe { sys::ssh_channel_close(chan) };
170 sess.basic_status(res, "error closing channel")
171 }
172
173 pub fn get_exit_status(&self) -> Option<c_int> {
178 let (_sess, chan) = self.lock_session();
179 let res = unsafe { sys::ssh_channel_get_exit_status(chan) };
180 if res == -1 {
181 None
182 } else {
183 Some(res)
184 }
185 }
186
187 pub fn get_exit_signal(&self) -> Option<SignalState> {
192 self.callback_state.signal_state.lock().unwrap().clone()
193 }
194
195 pub fn is_closed(&self) -> bool {
197 let (_sess, chan) = self.lock_session();
198 unsafe { sys::ssh_channel_is_closed(chan) != 0 }
199 }
200
201 pub fn is_eof(&self) -> bool {
203 let (_sess, chan) = self.lock_session();
204 unsafe { sys::ssh_channel_is_eof(chan) != 0 }
205 }
206
207 pub fn send_eof(&self) -> SshResult<()> {
215 let (sess, chan) = self.lock_session();
216 let res = unsafe { sys::ssh_channel_send_eof(chan) };
217 sess.basic_status(res, "ssh_channel_send_eof failed")
218 }
219
220 pub fn is_open(&self) -> bool {
222 let (_sess, chan) = self.lock_session();
223 unsafe { sys::ssh_channel_is_open(chan) != 0 }
224 }
225
226 pub fn open_auth_agent(&self) -> SshResult<()> {
232 let (sess, chan) = self.lock_session();
233 let res = unsafe { sys::ssh_channel_open_auth_agent(chan) };
234 sess.basic_status(res, "ssh_channel_open_auth_agent failed")
235 }
236
237 pub fn request_auth_agent(&self) -> SshResult<()> {
244 let (sess, chan) = self.lock_session();
245 let res = unsafe { sys::ssh_channel_request_auth_agent(chan) };
246 sess.basic_status(res, "ssh_channel_request_auth_agent failed")
247 }
248
249 pub fn request_env(&self, name: &str, value: &str) -> SshResult<()> {
252 let (sess, chan) = self.lock_session();
253 let name = CString::new(name)?;
254 let value = CString::new(value)?;
255 let res = unsafe { sys::ssh_channel_request_env(chan, name.as_ptr(), value.as_ptr()) };
256 sess.basic_status(res, "ssh_channel_request_env failed")
257 }
258
259 pub fn request_shell(&self) -> SshResult<()> {
265 let (sess, chan) = self.lock_session();
266 let res = unsafe { sys::ssh_channel_request_shell(chan) };
267 sess.basic_status(res, "ssh_channel_request_shell failed")
268 }
269
270 pub fn request_exec(&self, command: &str) -> SshResult<()> {
276 let (sess, chan) = self.lock_session();
277 let command = CString::new(command)?;
278 let res = unsafe { sys::ssh_channel_request_exec(chan, command.as_ptr()) };
279 sess.basic_status(res, "ssh_channel_request_exec failed")
280 }
281
282 pub fn request_subsystem(&self, subsys: &str) -> SshResult<()> {
286 let (sess, chan) = self.lock_session();
287 let subsys = CString::new(subsys)?;
288 let res = unsafe { sys::ssh_channel_request_subsystem(chan, subsys.as_ptr()) };
289 sess.basic_status(res, "ssh_channel_request_subsystem failed")
290 }
291
292 pub fn request_pty(&self, term: &str, columns: u32, rows: u32) -> SshResult<()> {
301 let (sess, chan) = self.lock_session();
302 let term = CString::new(term)?;
303 let res = unsafe {
304 sys::ssh_channel_request_pty_size(
305 chan,
306 term.as_ptr(),
307 columns.try_into().unwrap(),
308 rows.try_into().unwrap(),
309 )
310 };
311 sess.basic_status(res, "ssh_channel_request_pty_size failed")
312 }
313
314 pub fn change_pty_size(&self, columns: u32, rows: u32) -> SshResult<()> {
316 let (sess, chan) = self.lock_session();
317 let res = unsafe {
318 sys::ssh_channel_change_pty_size(
319 chan,
320 columns.try_into().unwrap(),
321 rows.try_into().unwrap(),
322 )
323 };
324 sess.basic_status(res, "ssh_channel_change_pty_size failed")
325 }
326
327 pub fn request_send_break(&self, length: Duration) -> SshResult<()> {
332 let (sess, chan) = self.lock_session();
333 let res = unsafe { sys::ssh_channel_request_send_break(chan, length.as_millis() as _) };
334 sess.basic_status(res, "ssh_channel_request_send_break failed")
335 }
336
337 pub fn request_send_signal(&self, signal: &str) -> SshResult<()> {
349 let (sess, chan) = self.lock_session();
350 let signal = CString::new(signal)?;
351 let res = unsafe { sys::ssh_channel_request_send_signal(chan, signal.as_ptr()) };
352 sess.basic_status(res, "ssh_channel_request_send_signal failed")
353 }
354
355 pub fn open_forward(
365 &self,
366 remote_host: &str,
367 remote_port: u16,
368 source_host: &str,
369 source_port: u16,
370 ) -> SshResult<()> {
371 let (sess, chan) = self.lock_session();
372 let remote_host = CString::new(remote_host)?;
373 let source_host = CString::new(source_host)?;
374 let res = unsafe {
375 sys::ssh_channel_open_forward(
376 chan,
377 remote_host.as_ptr(),
378 remote_port as i32,
379 source_host.as_ptr(),
380 source_port as i32,
381 )
382 };
383 sess.basic_status(res, "ssh_channel_open_forward failed")
384 }
385
386 pub fn open_forward_unix(
396 &self,
397 remote_path: &str,
398 source_host: &str,
399 source_port: u16,
400 ) -> SshResult<()> {
401 let (sess, chan) = self.lock_session();
402 let remote_path = CString::new(remote_path)?;
403 let source_host = CString::new(source_host)?;
404 let res = unsafe {
405 sys::ssh_channel_open_forward_unix(
406 chan,
407 remote_path.as_ptr(),
408 source_host.as_ptr(),
409 source_port as i32,
410 )
411 };
412 sess.basic_status(res, "ssh_channel_open_forward_unix failed")
413 }
414
415 pub fn request_x11(
419 &self,
420 single_connection: bool,
421 protocol: Option<&str>,
422 cookie: Option<&str>,
423 screen_number: c_int,
424 ) -> SshResult<()> {
425 let (sess, chan) = self.lock_session();
426 let protocol = opt_str_to_cstring(protocol);
427 let cookie = opt_str_to_cstring(cookie);
428 let res = unsafe {
429 sys::ssh_channel_request_x11(
430 chan,
431 if single_connection { 1 } else { 0 },
432 opt_cstring_to_cstr(&protocol),
433 opt_cstring_to_cstr(&cookie),
434 screen_number,
435 )
436 };
437
438 sess.basic_status(res, "ssh_channel_open_forward failed")
439 }
440
441 pub fn open_session(&self) -> SshResult<()> {
443 let (sess, chan) = self.lock_session();
444 let res = unsafe { sys::ssh_channel_open_session(chan) };
445 sess.basic_status(res, "ssh_channel_open_session failed")
446 }
447
448 pub fn poll_timeout(
452 &self,
453 is_stderr: bool,
454 timeout: Option<Duration>,
455 ) -> SshResult<PollStatus> {
456 let (sess, chan) = self.lock_session();
457 let timeout = match timeout {
458 Some(t) => t.as_millis() as c_int,
459 None => -1,
460 };
461 let res =
462 unsafe { sys::ssh_channel_poll_timeout(chan, if is_stderr { 1 } else { 0 }, timeout) };
463 match res {
464 sys::SSH_ERROR => {
465 if let Some(err) = sess.last_error() {
466 Err(err)
467 } else {
468 Err(Error::fatal("ssh_channel_poll failed"))
469 }
470 }
471 sys::SSH_EOF => Ok(PollStatus::EndOfFile),
472 n if n >= 0 => Ok(PollStatus::AvailableBytes(n as u32)),
473 n => Err(Error::Fatal(format!(
474 "ssh_channel_poll returned unexpected {} value",
475 n
476 ))),
477 }
478 }
479
480 pub fn read_timeout(
483 &self,
484 buf: &mut [u8],
485 is_stderr: bool,
486 timeout: Option<Duration>,
487 ) -> SshResult<usize> {
488 let (sess, chan) = self.lock_session();
489
490 let timeout = match timeout {
491 Some(t) => t.as_millis() as c_int,
492 None => -1,
493 };
494 let res = unsafe {
495 sys::ssh_channel_read_timeout(
496 chan,
497 buf.as_mut_ptr() as _,
498 buf.len() as u32,
499 if is_stderr { 1 } else { 0 },
500 timeout,
501 )
502 };
503 match res {
504 sys::SSH_ERROR => {
505 if let Some(err) = sess.last_error() {
506 Err(err)
507 } else {
508 Err(Error::fatal("ssh_channel_read_timeout failed"))
509 }
510 }
511 sys::SSH_AGAIN => Err(Error::TryAgain),
512 n if n < 0 => Err(Error::Fatal(format!(
513 "ssh_channel_read_timeout returned unexpected {} value",
514 n
515 ))),
516 0 if !sess.is_blocking() => Err(Error::TryAgain),
517 n => Ok(n as usize),
518 }
519 }
520
521 pub fn read_nonblocking(&self, buf: &mut [u8], is_stderr: bool) -> SshResult<usize> {
524 let (sess, chan) = self.lock_session();
525
526 let res = unsafe {
527 sys::ssh_channel_read_nonblocking(
528 chan,
529 buf.as_mut_ptr() as _,
530 buf.len() as u32,
531 if is_stderr { 1 } else { 0 },
532 )
533 };
534 match res {
535 sys::SSH_ERROR => {
536 if let Some(err) = sess.last_error() {
537 Err(err)
538 } else {
539 Err(Error::fatal("ssh_channel_read_timeout failed"))
540 }
541 }
542 sys::SSH_EOF => Ok(0 as usize),
543 n if n < 0 => Err(Error::Fatal(format!(
544 "ssh_channel_read_timeout returned unexpected value: {n}"
545 ))),
546 n => Ok(n as usize),
547 }
548 }
549
550 pub fn window_size(&self) -> usize {
558 let (_sess, chan) = self.lock_session();
559 unsafe { sys::ssh_channel_window_size(chan).try_into().unwrap() }
560 }
561
562 fn read_impl(&self, buf: &mut [u8], is_stderr: bool) -> std::io::Result<usize> {
563 Ok(self.read_timeout(buf, is_stderr, None)?)
564 }
565
566 fn write_impl(&self, buf: &[u8], is_stderr: bool) -> SshResult<usize> {
567 let (sess, chan) = self.lock_session();
568
569 let res = unsafe {
570 (if is_stderr {
571 sys::ssh_channel_write_stderr
572 } else {
573 sys::ssh_channel_write
574 })(chan, buf.as_ptr() as _, buf.len() as _)
575 };
576
577 match res {
578 sys::SSH_ERROR => {
579 if let Some(err) = sess.last_error() {
580 Err(err)
581 } else {
582 Err(Error::fatal("ssh_channel_read_timeout failed"))
583 }
584 }
585 sys::SSH_AGAIN => Err(Error::TryAgain),
586 n if n < 0 => Err(Error::Fatal(format!(
587 "ssh_channel_read_timeout returned unexpected {} value",
588 n
589 ))),
590 n => Ok(n as usize),
591 }
592 }
593
594 pub fn stdout(&self) -> impl std::io::Read + '_ {
597 ChannelStdout { chan: self }
598 }
599
600 pub fn stderr(&self) -> impl std::io::Read + '_ {
603 ChannelStderr { chan: self }
604 }
605
606 pub fn stdin(&self) -> impl std::io::Write + '_ {
609 ChannelStdin { chan: self }
610 }
611}
612
613struct ChannelStdin<'a> {
617 chan: &'a Channel,
618}
619
620impl<'a> std::io::Write for ChannelStdin<'a> {
621 fn flush(&mut self) -> std::io::Result<()> {
622 Ok(self.chan.sess.lock().unwrap().blocking_flush(None)?)
623 }
624
625 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
626 Ok(self.chan.write_impl(buf, false)?)
627 }
628}
629
630struct ChannelStdout<'a> {
634 chan: &'a Channel,
635}
636
637impl<'a> std::io::Read for ChannelStdout<'a> {
638 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
639 self.chan.read_impl(buf, false)
640 }
641}
642
643struct ChannelStderr<'a> {
647 chan: &'a Channel,
648}
649
650impl<'a> std::io::Read for ChannelStderr<'a> {
651 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
652 self.chan.read_impl(buf, true)
653 }
654}
655
656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
658pub enum PollStatus {
659 AvailableBytes(u32),
661 EndOfFile,
663}