From 4d8ac3fa96d1eda8b9ab60896f43d94bb8901206 Mon Sep 17 00:00:00 2001 From: Riatre Foo Date: Thu, 11 Apr 2024 05:32:26 +0800 Subject: [PATCH] wip: wire up agent forward for libssh-rs --- Cargo.lock | 8 +- wezterm-ssh/Cargo.toml | 5 +- wezterm-ssh/examples/ssh.rs | 1 + wezterm-ssh/src/channelwrap.rs | 15 ++++ wezterm-ssh/src/pty.rs | 12 +-- wezterm-ssh/src/sessioninner.rs | 127 ++++++++++++++++++++++++++++++-- wezterm-ssh/src/sftpwrap.rs | 8 +- 7 files changed, 154 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 305d4f3bae20..047f117329de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3013,11 +3013,10 @@ dependencies = [ [[package]] name = "libssh-rs" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3fe324fb06b71d28abb81382ac547f25b4895e853a9968482dc5002fb3db08" +version = "0.3.0" dependencies = [ "bitflags 1.3.2", + "libc", "libssh-rs-sys", "openssl-sys", "thiserror", @@ -3026,8 +3025,6 @@ dependencies = [ [[package]] name = "libssh-rs-sys" version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af07827858d82a7b74d6f935ad4201ff764fb1de8efcc26aeaa33e5f9c89ca2" dependencies = [ "cc", "libz-sys", @@ -6615,6 +6612,7 @@ dependencies = [ "ssh2", "termwiz", "thiserror", + "uds_windows", "whoami", ] diff --git a/wezterm-ssh/Cargo.toml b/wezterm-ssh/Cargo.toml index 246288da6c63..6a3cbd7466b6 100644 --- a/wezterm-ssh/Cargo.toml +++ b/wezterm-ssh/Cargo.toml @@ -31,10 +31,11 @@ portable-pty = { version="0.8", path = "../pty" } regex = "1" smol = "1.2" ssh2 = {version="0.9.3", features=["openssl-on-win32"], optional = true} -libssh-rs = {version="0.2.1", features=["vendored"], optional = true} -#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored"], optional = true} +# libssh-rs = {version="0.2.1", features=["vendored"], optional = true} +libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored"], optional = true} thiserror = "1.0" socket2 = "0.5" +uds_windows = "1.1.0" # Not used directly, but is used to centralize the openssl vendor feature selection async_ossl = { path = "../async_ossl" } diff --git a/wezterm-ssh/examples/ssh.rs b/wezterm-ssh/examples/ssh.rs index 9391b00408db..ca6ad7f3f04e 100644 --- a/wezterm-ssh/examples/ssh.rs +++ b/wezterm-ssh/examples/ssh.rs @@ -58,6 +58,7 @@ fn main() { if let Some(user) = opts.user.as_ref() { config.insert("user".to_string(), user.to_string()); } + config.insert("forwardagent".to_string(), "yes".to_string()); let res = smol::block_on(async move { let (session, events) = Session::connect(config.clone())?; diff --git a/wezterm-ssh/src/channelwrap.rs b/wezterm-ssh/src/channelwrap.rs index f7409cb52f66..db79dbcdcde9 100644 --- a/wezterm-ssh/src/channelwrap.rs +++ b/wezterm-ssh/src/channelwrap.rs @@ -146,6 +146,21 @@ impl ChannelWrap { } } + pub fn request_auth_agent_forwarding(&mut self) -> anyhow::Result<()> { + match self { + /* libssh2 doesn't properly support agent forwarding + * at this time: + * */ + #[cfg(feature = "ssh2")] + Self::Ssh2(_chan) => Err(anyhow::anyhow!( + "ssh2 does not support request_auth_agent_forwarding" + )), + + #[cfg(feature = "libssh-rs")] + Self::LibSsh(chan) => Ok(chan.request_auth_agent()?), + } + } + pub fn resize_pty(&mut self, resize: &ResizePty) -> anyhow::Result<()> { match self { #[cfg(feature = "ssh2")] diff --git a/wezterm-ssh/src/pty.rs b/wezterm-ssh/src/pty.rs index 9136b29d8c3e..ef8ecbe6e753 100644 --- a/wezterm-ssh/src/pty.rs +++ b/wezterm-ssh/src/pty.rs @@ -218,17 +218,13 @@ impl crate::sessioninner::SessionInner { let mut channel = sess.open_session()?; - /* libssh2 doesn't properly support agent forwarding - * at this time: - * if let Some("yes") = self.config.get("forwardagent").map(|s| s.as_str()) { - log::info!("requesting agent forwarding"); - if let Err(err) = channel.request_auth_agent_forwarding() { - log::error!("Failed to establish agent forwarding: {:#}", err); + if let Some(_) = self.identity_agent() { + if let Err(err) = channel.request_auth_agent_forwarding() { + log::error!("Failed to request agent forwarding: {:#}", err); + } } - log::info!("agent forwarding OK!"); } - */ channel.request_pty(&newpty)?; diff --git a/wezterm-ssh/src/sessioninner.rs b/wezterm-ssh/src/sessioninner.rs index a0f30d6461ee..22690751c865 100644 --- a/wezterm-ssh/src/sessioninner.rs +++ b/wezterm-ssh/src/sessioninner.rs @@ -38,6 +38,11 @@ pub(crate) struct ChannelInfo { pub(crate) type ChannelId = usize; +pub(crate) struct PendingChannelSplice { + channel: ChannelWrap, + fd: FileDescriptor, +} + pub(crate) struct SessionInner { pub config: ConfigMap, pub tx_event: Sender, @@ -106,6 +111,8 @@ impl SessionInner { #[cfg(feature = "libssh-rs")] fn run_impl_libssh(&mut self) -> anyhow::Result<()> { + use smol::channel::unbounded; + let hostname = self .config .get("hostname") @@ -243,9 +250,56 @@ impl SessionInner { .try_send(SessionEvent::Authenticated) .context("notifying user that session is authenticated")?; + let rx_splice = if let Some("yes") = self.config.get("forwardagent").map(|s| s.as_str()) { + if let Some(identity_agent) = self.identity_agent() { + // Setup agent forward callback for session. + let (tx, rx) = unbounded(); + sess.set_channel_open_request_auth_agent_callback(move |channel| { + use libssh_rs::RequestAuthAgentError; + let fd = { + #[cfg(unix)] + { + use std::os::unix::net::UnixStream; + match UnixStream::connect(&identity_agent) { + Ok(uds) => FileDescriptor::new(uds), + Err(err) => return Err(RequestAuthAgentError(err.into(), channel)), + } + } + #[cfg(windows)] + unsafe { + use std::os::windows::io::{FromRawSocket, IntoRawSocket}; + use uds_windows::UnixStream; + match UnixStream::connect(&identity_agent) { + Ok(uds) => FileDescriptor::from_raw_socket(uds.into_raw_socket()), + Err(err) => return Err(RequestAuthAgentError(err.into(), channel)), + } + } + }; + tx.send_blocking(PendingChannelSplice { + channel: ChannelWrap::LibSsh(channel), + fd, + }) + .map_err(|e| { + RequestAuthAgentError( + libssh_rs::Error::Fatal("SendError".to_string()), + match e.0.channel { + ChannelWrap::LibSsh(c) => c, + _ => panic!("impossible channel type"), + }, + ) + }) + }); + Some(rx) + } else { + log::error!("ForwardAgent is set to yes, but IdentityAgent is not set"); + None + } + } else { + None + }; sess.set_blocking(false); let mut sess = SessionWrap::with_libssh(sess); - self.request_loop(&mut sess) + self.request_loop(&mut sess, rx_splice) } #[cfg(feature = "ssh2")] @@ -310,7 +364,7 @@ impl SessionInner { sess.set_blocking(false); let mut sess = SessionWrap::with_ssh2(sess); - self.request_loop(&mut sess) + self.request_loop(&mut sess, None) } /// Explicitly and directly connect to the requested host because @@ -398,13 +452,20 @@ impl SessionInner { } } - fn request_loop(&mut self, sess: &mut SessionWrap) -> anyhow::Result<()> { + fn request_loop( + &mut self, + sess: &mut SessionWrap, + rx_splice: Option>, + ) -> anyhow::Result<()> { let mut sleep_delay = Duration::from_millis(100); loop { self.tick_io()?; self.drain_request_pipe(); self.dispatch_pending_requests(sess)?; + if let Some(rx_splice) = rx_splice.as_ref() { + self.connect_pending_slices(rx_splice)?; + } if self.channels.is_empty() && self.session_was_dropped { log::trace!( @@ -517,8 +578,16 @@ impl SessionInner { let stdin = &mut chan.descriptors[0]; if stdin.fd.is_some() && !stdin.buf.is_empty() { - write_from_buf(&mut chan.channel.writer(), &mut stdin.buf) - .context("writing to channel")?; + if let Err(err) = write_from_buf(&mut chan.channel.writer(), &mut stdin.buf) + .context("writing to channel") + { + log::trace!( + "Failed to write data to channel {} stdin: {:#}, closing pipe", + id, + err + ); + stdin.fd.take(); + } } for (idx, out) in chan @@ -805,6 +874,47 @@ impl SessionInner { } } + fn connect_pending_slices( + &mut self, + rx: &Receiver, + ) -> anyhow::Result<()> { + match rx.try_recv() { + Err(TryRecvError::Closed) => anyhow::bail!("all clients are closed"), + Err(TryRecvError::Empty) => Ok(()), + Ok(mut req) => { + let channel_id = self.next_channel_id; + self.next_channel_id += 1; + + req.fd.set_non_blocking(true)?; + let read_from_agent = req.fd; + let write_to_agent = read_from_agent.try_clone()?; + + let info = ChannelInfo { + channel_id, + channel: req.channel, + exit: None, + exited: false, + descriptors: [ + DescriptorState { + fd: Some(read_from_agent), + buf: VecDeque::with_capacity(8192), + }, + DescriptorState { + fd: Some(write_to_agent), + buf: VecDeque::with_capacity(8192), + }, + DescriptorState { + fd: None, + buf: VecDeque::with_capacity(8192), + }, + ], + }; + self.channels.insert(channel_id, info); + Ok(()) + } + } + } + pub fn signal_channel(&mut self, info: &SignalChannel) -> anyhow::Result<()> { let chan_info = self .channels @@ -944,6 +1054,13 @@ impl SessionInner { } } } + + pub fn identity_agent(&self) -> Option { + self.config + .get("identityagent") + .map(|s| s.to_owned()) + .or_else(|| std::env::var("SSH_AUTH_SOCK").ok()) + } } fn write_from_buf(w: &mut W, buf: &mut VecDeque) -> std::io::Result<()> { diff --git a/wezterm-ssh/src/sftpwrap.rs b/wezterm-ssh/src/sftpwrap.rs index 11939ddecbb6..4335479e0267 100644 --- a/wezterm-ssh/src/sftpwrap.rs +++ b/wezterm-ssh/src/sftpwrap.rs @@ -38,6 +38,7 @@ impl SftpWrap { Self::LibSsh(sftp) => { use crate::sftp::types::WriteMode; use libc::{O_APPEND, O_RDONLY, O_RDWR, O_WRONLY}; + use libssh_rs::OpenFlags; use std::convert::TryInto; let accesstype = match (opts.write, opts.read) { (Some(WriteMode::Append), true) => O_RDWR | O_APPEND, @@ -47,8 +48,11 @@ impl SftpWrap { (None, true) => O_RDONLY, (None, false) => 0, }; - let file = - sftp.open(filename.as_str(), accesstype, opts.mode.try_into().unwrap())?; + let file = sftp.open( + filename.as_str(), + OpenFlags::from_bits_truncate(accesstype), + opts.mode.try_into().unwrap(), + )?; Ok(FileWrap::LibSsh(file)) } }