Skip to content

Commit f334115

Browse files
Riatrewez
authored andcommitted
Add binding for agent forwarding
channel_open_request_auth_agent_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 saner context.
1 parent 5c135f7 commit f334115

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

libssh-rs/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ 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+
pending_agent_forward_channels: Vec<sys::ssh_channel>,
7879
}
7980
unsafe impl Send for SessionHolder {}
8081

@@ -87,6 +88,7 @@ impl std::ops::Deref for SessionHolder {
8788

8889
impl Drop for SessionHolder {
8990
fn drop(&mut self) {
91+
self.clear_pending_agent_forward_channels();
9092
unsafe {
9193
sys::ssh_free(self.sess);
9294
}
@@ -157,6 +159,15 @@ impl SessionHolder {
157159
}
158160
}
159161
}
162+
163+
fn clear_pending_agent_forward_channels(&mut self) {
164+
for chan in self.pending_agent_forward_channels.drain(..) {
165+
unsafe {
166+
// We have no callbacks on these channels, no need to cleanup.
167+
sys::ssh_channel_free(chan);
168+
}
169+
}
170+
}
160171
}
161172

162173
/// A Session represents the state needed to make a connection to
@@ -201,6 +212,7 @@ impl Session {
201212
sess,
202213
callbacks,
203214
auth_callback: None,
215+
pending_agent_forward_channels: Vec::new(),
204216
}));
205217

206218
{
@@ -274,6 +286,21 @@ impl Session {
274286
}
275287
}
276288

289+
unsafe extern "C" fn channel_open_request_auth_agent_callback(
290+
session: sys::ssh_session,
291+
userdata: *mut ::std::os::raw::c_void,
292+
) -> sys::ssh_channel {
293+
let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder);
294+
let chan = sys::ssh_channel_new(session);
295+
if chan.is_null() {
296+
eprintln!("ssh_channel_new failed: {:?}", sess.last_error());
297+
return std::ptr::null_mut();
298+
}
299+
// We are guarenteed to be holding a session lock here.
300+
sess.pending_agent_forward_channels.push(chan);
301+
chan
302+
}
303+
277304
/// Sets a callback that is used by libssh when it needs to prompt
278305
/// for the passphrase during public key authentication.
279306
/// This is NOT used for password or keyboard interactive authentication.
@@ -326,6 +353,30 @@ impl Session {
326353
sess.callbacks.auth_function = Some(Self::bridge_auth_callback);
327354
}
328355

356+
/// Enable or disable creating channels when the remote side requests a new channel for SSH
357+
/// agent forwarding.
358+
/// You are supposed to periodically check whether there's pending channels (already bound to
359+
/// remote side's agent client) by using the `accept_agent_forward` function.
360+
pub fn enable_accept_agent_forward(&self, enable: bool) {
361+
let mut sess = self.lock_session();
362+
sess.callbacks.channel_open_request_auth_agent_function = if enable {
363+
Some(Self::channel_open_request_auth_agent_callback)
364+
} else {
365+
sess.clear_pending_agent_forward_channels();
366+
// libssh denies auth agent channel requests with no callback set.
367+
None
368+
}
369+
}
370+
371+
// Accept an auth agent forward channel.
372+
// Returns a `Channel` bound to the remote side SSH agent client, or `None` if no pending
373+
// request from the server.
374+
pub fn accept_agent_forward(&self) -> Option<Channel> {
375+
let mut sess = self.lock_session();
376+
let chan = sess.pending_agent_forward_channels.pop()?;
377+
Some(Channel::new(&self.sess, chan))
378+
}
379+
329380
/// Create a new channel.
330381
/// Channels are used to handle I/O for commands and forwarded streams.
331382
pub fn new_channel(&self) -> SshResult<Channel> {

0 commit comments

Comments
 (0)