Skip to content

Commit 9b92bfc

Browse files
committed
Add binding for channel_open_request_auth_agent_callback
This callback is required for implementing ssh agent forward as unlike X11 forward, there is no other way to establish a forwarding channel. In libssh: 1. Callback is triggered while handling protocol packets in other libssh call. 2. The callback creates a new channel and prepare for bidirectional forwarding between it and ssh agent. 3. The callback then returns a borrow of the newly created channel for libssh to make reply to the remote side. However, the callback-based flow does not really fit our Rust binding design: during callback we have SessionHolder locked, so it's really hard to do anything without introducing lock re-entrancy issues, plus that it demands us to return a temporary borrow of something owned by Rust side whose lifetime is tricky to model. Instead, we try to turn the callback-based style back to something resembling `ssh_channel_accept_x11` by buffering pending channels and let users fetch them later in a normal context.
1 parent 5c135f7 commit 9b92bfc

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

libssh-rs/src/lib.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ pub(crate) struct SessionHolder {
7575
sess: sys::ssh_session,
7676
callbacks: sys::ssh_callbacks_struct,
7777
auth_callback: Option<Box<dyn FnMut(&str, bool, bool, Option<String>) -> SshResult<String>>>,
78+
channel_open_request_auth_agent_callback: Option<Box<dyn FnMut() -> bool>>,
79+
pending_agent_forward_channels: Vec<sys::ssh_channel>,
7880
}
7981
unsafe impl Send for SessionHolder {}
8082

@@ -87,6 +89,12 @@ impl std::ops::Deref for SessionHolder {
8789

8890
impl Drop for SessionHolder {
8991
fn drop(&mut self) {
92+
for chan in self.pending_agent_forward_channels.drain(..) {
93+
unsafe {
94+
// We have no callbacks on these channels, no need to cleanup.
95+
sys::ssh_channel_free(chan);
96+
}
97+
}
9098
unsafe {
9199
sys::ssh_free(self.sess);
92100
}
@@ -201,6 +209,8 @@ impl Session {
201209
sess,
202210
callbacks,
203211
auth_callback: None,
212+
channel_open_request_auth_agent_callback: None,
213+
pending_agent_forward_channels: Vec::new(),
204214
}));
205215

206216
{
@@ -274,6 +284,48 @@ impl Session {
274284
}
275285
}
276286

287+
unsafe extern "C" fn bridge_channel_open_request_auth_agent_callback(
288+
session: sys::ssh_session,
289+
userdata: *mut ::std::os::raw::c_void,
290+
) -> sys::ssh_channel {
291+
let result = std::panic::catch_unwind(|| -> SshResult<sys::ssh_channel> {
292+
let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder);
293+
assert!(
294+
std::ptr::eq(session, sess.sess),
295+
"invalid callback invocation: session mismatch"
296+
);
297+
let cb = sess
298+
.channel_open_request_auth_agent_callback
299+
.as_mut()
300+
.unwrap();
301+
if !cb() {
302+
return Err(Error::RequestDenied(
303+
"callback denied agent forward".to_owned(),
304+
));
305+
}
306+
let chan = unsafe { sys::ssh_channel_new(session) };
307+
if chan.is_null() {
308+
return Err(sess
309+
.last_error()
310+
.unwrap_or_else(|| Error::fatal("ssh_channel_new failed")));
311+
}
312+
// We are guarenteed to be holding a session lock here.
313+
sess.pending_agent_forward_channels.push(chan);
314+
Ok(chan)
315+
});
316+
match result {
317+
Err(err) => {
318+
eprintln!("Panic in request auth agent callback: {:?}", err);
319+
std::ptr::null_mut()
320+
}
321+
Ok(Err(err)) => {
322+
eprintln!("Error in request auth agent callback: {:#}", err);
323+
std::ptr::null_mut()
324+
}
325+
Ok(Ok(chan)) => chan,
326+
}
327+
}
328+
277329
/// Sets a callback that is used by libssh when it needs to prompt
278330
/// for the passphrase during public key authentication.
279331
/// This is NOT used for password or keyboard interactive authentication.
@@ -326,6 +378,41 @@ impl Session {
326378
sess.callbacks.auth_function = Some(Self::bridge_auth_callback);
327379
}
328380

381+
/// Sets a callback that is used by libssh when the remote side requests a new channel
382+
/// for SSH agent forwarding.
383+
/// The callback has the signature:
384+
///
385+
/// ```no_run
386+
/// fn callback() -> bool {
387+
/// unimplemented!()
388+
/// }
389+
/// ```
390+
///
391+
/// The callback should decide whether agent forwarding is allowed and possible now.
392+
/// If the callback returns `true`, a new channel will be created and bound to remote side.
393+
/// Since it is not possible to do anything with the channel in the callback, it is not passed
394+
/// to the callback directly, and you are supposed to retrieve it later by using the
395+
/// `accept_agent_forward` function and connect it to an agent.
396+
pub fn set_channel_open_request_auth_agent_callback<F>(&self, callback: F)
397+
where
398+
F: FnMut() -> bool + 'static,
399+
{
400+
let mut sess = self.lock_session();
401+
sess.channel_open_request_auth_agent_callback
402+
.replace(Box::new(callback));
403+
sess.callbacks.channel_open_request_auth_agent_function =
404+
Some(Self::bridge_channel_open_request_auth_agent_callback);
405+
}
406+
407+
// Accept an auth agent forward channel.
408+
// Returns a `Channel` bound to the remote side SSH agent client, or `None` if no pending
409+
// request from the server.
410+
pub fn accept_agent_forward(&self) -> Option<Channel> {
411+
let mut sess = self.lock_session();
412+
let chan = sess.pending_agent_forward_channels.pop()?;
413+
Some(Channel::new(&self.sess, chan))
414+
}
415+
329416
/// Create a new channel.
330417
/// Channels are used to handle I/O for commands and forwarded streams.
331418
pub fn new_channel(&self) -> SshResult<Channel> {

0 commit comments

Comments
 (0)