diff --git a/libssh-rs/src/lib.rs b/libssh-rs/src/lib.rs index 4352ea5..9807c9a 100644 --- a/libssh-rs/src/lib.rs +++ b/libssh-rs/src/lib.rs @@ -75,6 +75,8 @@ pub(crate) struct SessionHolder { sess: sys::ssh_session, callbacks: sys::ssh_callbacks_struct, auth_callback: Option) -> SshResult>>, + channel_open_request_auth_agent_callback: Option bool>>, + pending_agent_forward_channels: Vec, } unsafe impl Send for SessionHolder {} @@ -87,6 +89,12 @@ impl std::ops::Deref for SessionHolder { impl Drop for SessionHolder { fn drop(&mut self) { + for chan in self.pending_agent_forward_channels.drain(..) { + unsafe { + // We have no callbacks on these channels, no need to cleanup. + sys::ssh_channel_free(chan); + } + } unsafe { sys::ssh_free(self.sess); } @@ -201,6 +209,8 @@ impl Session { sess, callbacks, auth_callback: None, + channel_open_request_auth_agent_callback: None, + pending_agent_forward_channels: Vec::new(), })); { @@ -274,6 +284,48 @@ impl Session { } } + unsafe extern "C" fn bridge_channel_open_request_auth_agent_callback( + session: sys::ssh_session, + userdata: *mut ::std::os::raw::c_void, + ) -> sys::ssh_channel { + let result = std::panic::catch_unwind(|| -> SshResult { + let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder); + assert!( + std::ptr::eq(session, sess.sess), + "invalid callback invocation: session mismatch" + ); + let cb = sess + .channel_open_request_auth_agent_callback + .as_mut() + .unwrap(); + if !cb() { + return Err(Error::RequestDenied( + "callback denied agent forward".to_owned(), + )); + } + let chan = unsafe { sys::ssh_channel_new(session) }; + if chan.is_null() { + return Err(sess + .last_error() + .unwrap_or_else(|| Error::fatal("ssh_channel_new failed"))); + } + // We are guarenteed to be holding a session lock here. + sess.pending_agent_forward_channels.push(chan); + Ok(chan) + }); + match result { + Err(err) => { + eprintln!("Panic in request auth agent callback: {:?}", err); + std::ptr::null_mut() + } + Ok(Err(err)) => { + eprintln!("Error in request auth agent callback: {:#}", err); + std::ptr::null_mut() + } + Ok(Ok(chan)) => chan, + } + } + /// Sets a callback that is used by libssh when it needs to prompt /// for the passphrase during public key authentication. /// This is NOT used for password or keyboard interactive authentication. @@ -326,6 +378,41 @@ impl Session { sess.callbacks.auth_function = Some(Self::bridge_auth_callback); } + /// Sets a callback that is used by libssh when the remote side requests a new channel + /// for SSH agent forwarding. + /// The callback has the signature: + /// + /// ```no_run + /// fn callback() -> bool { + /// unimplemented!() + /// } + /// ``` + /// + /// The callback should decide whether agent forwarding is allowed and possible now. + /// If the callback returns `true`, a new channel will be created and bound to remote side. + /// Since it is not possible to do anything with the channel in the callback, it is not passed + /// to the callback directly, and you are supposed to retrieve it later by using the + /// `pop_auth_agent_channel` function and connect it to an agent. + pub fn set_channel_open_request_auth_agent_callback(&self, callback: F) + where + F: FnMut() -> bool + 'static, + { + let mut sess = self.lock_session(); + sess.channel_open_request_auth_agent_callback + .replace(Box::new(callback)); + sess.callbacks.channel_open_request_auth_agent_function = + Some(Self::bridge_channel_open_request_auth_agent_callback); + } + + // Accept an auth agent forward channel. + // Returns a `Channel` bound to the remote side SSH agent client, or `None` if no pending + // request from the server. + pub fn accept_agent_forward(&self) -> Option { + let mut sess = self.lock_session(); + let chan = sess.pending_agent_forward_channels.pop()?; + Some(Channel::new(&self.sess, chan)) + } + /// Create a new channel. /// Channels are used to handle I/O for commands and forwarded streams. pub fn new_channel(&self) -> SshResult {