Skip to content

Commit

Permalink
wip: wire up agent forward for libssh-rs
Browse files Browse the repository at this point in the history
  • Loading branch information
Riatre committed Apr 29, 2024
1 parent 22424c3 commit 4d8ac3f
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 22 deletions.
8 changes: 3 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions wezterm-ssh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions wezterm-ssh/examples/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())?;
Expand Down
15 changes: 15 additions & 0 deletions wezterm-ssh/src/channelwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* <https://github.com/libssh2/libssh2/issues/535> */
#[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")]
Expand Down
12 changes: 4 additions & 8 deletions wezterm-ssh/src/pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,13 @@ impl crate::sessioninner::SessionInner {

let mut channel = sess.open_session()?;

/* libssh2 doesn't properly support agent forwarding
* at this time:
* <https://github.com/libssh2/libssh2/issues/535>
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)?;

Expand Down
127 changes: 122 additions & 5 deletions wezterm-ssh/src/sessioninner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SessionEvent>,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Receiver<PendingChannelSplice>>,
) -> 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!(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -805,6 +874,47 @@ impl SessionInner {
}
}

fn connect_pending_slices(
&mut self,
rx: &Receiver<PendingChannelSplice>,
) -> 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
Expand Down Expand Up @@ -944,6 +1054,13 @@ impl SessionInner {
}
}
}

pub fn identity_agent(&self) -> Option<String> {
self.config
.get("identityagent")
.map(|s| s.to_owned())
.or_else(|| std::env::var("SSH_AUTH_SOCK").ok())
}
}

fn write_from_buf<W: Write>(w: &mut W, buf: &mut VecDeque<u8>) -> std::io::Result<()> {
Expand Down
8 changes: 6 additions & 2 deletions wezterm-ssh/src/sftpwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))
}
}
Expand Down

0 comments on commit 4d8ac3f

Please sign in to comment.