diff --git a/README.md b/README.md index 921c67d5..a1fa36c6 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti * `direct-tcpip` (local port forwarding) * `forward-tcpip` (remote port forwarding) ✨ * `direct-streamlocal` (local UNIX socket forwarding, client only) ✨ +* `forward-streamlocal` (remote UNIX socket forwarding) ✨ * Ciphers: * `chacha20-poly1305@openssh.com` * `aes256-gcm@openssh.com` ✨ diff --git a/russh/src/client/encrypted.rs b/russh/src/client/encrypted.rs index 8f66078c..ea8b782e 100644 --- a/russh/src/client/encrypted.rs +++ b/russh/src/client/encrypted.rs @@ -805,6 +805,17 @@ impl Session { ) .await? } + ChannelType::ForwardedStreamLocal(d) => { + confirm(); + let channel = self.accept_server_initiated_channel(id, &msg); + client + .server_channel_open_forwarded_streamlocal( + channel, + &d.socket_path, + self, + ) + .await?; + } ChannelType::AgentForward => { confirm(); client.server_channel_open_agent_forward(id, self).await? @@ -848,6 +859,12 @@ impl Session { Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => { let _ = return_channel.send(true); } + Some(GlobalRequestResponse::StreamLocalForward(return_channel)) => { + let _ = return_channel.send(true); + } + Some(GlobalRequestResponse::CancelStreamLocalForward(return_channel)) => { + let _ = return_channel.send(true); + } None => { error!("Received global request failure for unknown request!") } @@ -866,6 +883,12 @@ impl Session { Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => { let _ = return_channel.send(false); } + Some(GlobalRequestResponse::StreamLocalForward(return_channel)) => { + let _ = return_channel.send(false); + } + Some(GlobalRequestResponse::CancelStreamLocalForward(return_channel)) => { + let _ = return_channel.send(false); + } None => { error!("Received global request failure for unknown request!") } diff --git a/russh/src/client/mod.rs b/russh/src/client/mod.rs index a2f9d8c0..447ca041 100644 --- a/russh/src/client/mod.rs +++ b/russh/src/client/mod.rs @@ -160,6 +160,16 @@ pub enum Msg { address: String, port: u32, }, + StreamLocalForward { + /// Provide a channel for the reply result to request a reply from the server + reply_channel: Option>, + socket_path: String, + }, + CancelStreamLocalForward { + /// Provide a channel for the reply result to request a reply from the server + reply_channel: Option>, + socket_path: String, + }, Close { id: ChannelId, }, @@ -571,6 +581,7 @@ impl Handle { } } + // Requests the server to close a TCP/IP forward channel pub async fn cancel_tcpip_forward>( &self, address: A, @@ -596,6 +607,54 @@ impl Handle { } } + // Requests the server to open a UDS forward channel + pub async fn streamlocal_forward>( + &mut self, + socket_path: A, + ) -> Result<(), crate::Error> { + let (reply_send, reply_recv) = oneshot::channel(); + self.sender + .send(Msg::StreamLocalForward { + reply_channel: Some(reply_send), + socket_path: socket_path.into(), + }) + .await + .map_err(|_| crate::Error::SendError)?; + + match reply_recv.await { + Ok(true) => Ok(()), + Ok(false) => Err(crate::Error::RequestDenied), + Err(e) => { + error!("Unable to receive StreamLocalForward result: {e:?}"); + Err(crate::Error::Disconnect) + } + } + } + + // Requests the server to close a UDS forward channel + pub async fn cancel_streamlocal_forward>( + &self, + socket_path: A, + ) -> Result<(), crate::Error> { + let (reply_send, reply_recv) = oneshot::channel(); + self.sender + .send(Msg::CancelStreamLocalForward { + reply_channel: Some(reply_send), + socket_path: socket_path.into(), + }) + .await + .map_err(|_| crate::Error::SendError)?; + + match reply_recv.await { + Ok(true) => Ok(()), + Ok(false) => Err(crate::Error::RequestDenied), + Err(e) => { + error!("Unable to receive CancelStreamLocalForward result: {e:?}"); + Err(crate::Error::Disconnect) + } + } + } + /// Sends a disconnect message. pub async fn disconnect( &self, @@ -1050,6 +1109,14 @@ impl Session { address, port, } => self.cancel_tcpip_forward(reply_channel, &address, port), + Msg::StreamLocalForward { + reply_channel, + socket_path, + } => self.streamlocal_forward(reply_channel, &socket_path), + Msg::CancelStreamLocalForward { + reply_channel, + socket_path, + } => self.cancel_streamlocal_forward(reply_channel, &socket_path), Msg::Disconnect { reason, description, @@ -1551,6 +1618,17 @@ pub trait Handler: Sized + Send { Ok(()) } + // Called when the server opens a channel for a new remote UDS forwarding connection + #[allow(unused_variables)] + async fn server_channel_open_forwarded_streamlocal( + &mut self, + channel: Channel, + socket_path: &str, + session: &mut Session, + ) -> Result<(), Self::Error> { + Ok(()) + } + /// Called when the server opens an agent forwarding channel #[allow(unused_variables)] async fn server_channel_open_agent_forward( diff --git a/russh/src/client/session.rs b/russh/src/client/session.rs index e068d7e2..26f8a761 100644 --- a/russh/src/client/session.rs +++ b/russh/src/client/session.rs @@ -293,7 +293,7 @@ impl Session { /// Requests cancellation of TCP/IP forwarding from the server /// - /// If `want_reply` is `true`, returns a oneshot receiveing the server's reply: + /// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel, /// `true` for a success message, or `false` for failure pub fn cancel_tcpip_forward( &mut self, @@ -318,6 +318,58 @@ impl Session { } } + /// Requests a UDS forwarding from the server, `socket path` being the server side socket path. + /// + /// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel, + /// `true` for a success message, or `false` for failure + pub fn streamlocal_forward( + &mut self, + reply_channel: Option>, + socket_path: &str, + ) { + if let Some(ref mut enc) = self.common.encrypted { + let want_reply = reply_channel.is_some(); + if let Some(reply_channel) = reply_channel { + self.open_global_requests.push_back( + crate::session::GlobalRequestResponse::StreamLocalForward(reply_channel), + ); + } + push_packet!(enc.write, { + enc.write.push(msg::GLOBAL_REQUEST); + enc.write + .extend_ssh_string(b"streamlocal-forward@openssh.com"); + enc.write.push(want_reply as u8); + enc.write.extend_ssh_string(socket_path.as_bytes()); + }); + } + } + + /// Requests cancellation of UDS forwarding from the server + /// + /// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel, + /// `true` for a success message and `false` for failure. + pub fn cancel_streamlocal_forward( + &mut self, + reply_channel: Option>, + socket_path: &str, + ) { + if let Some(ref mut enc) = self.common.encrypted { + let want_reply = reply_channel.is_some(); + if let Some(reply_channel) = reply_channel { + self.open_global_requests.push_back( + crate::session::GlobalRequestResponse::CancelStreamLocalForward(reply_channel), + ); + } + push_packet!(enc.write, { + enc.write.push(msg::GLOBAL_REQUEST); + enc.write + .extend_ssh_string(b"cancel-streamlocal-forward@openssh.com"); + enc.write.push(want_reply as u8); + enc.write.extend_ssh_string(socket_path.as_bytes()); + }); + } + } + pub fn send_keepalive(&mut self, want_reply: bool) { self.open_global_requests .push_back(crate::session::GlobalRequestResponse::Keepalive); diff --git a/russh/src/parsing.rs b/russh/src/parsing.rs index 8144b6e0..fe80c974 100644 --- a/russh/src/parsing.rs +++ b/russh/src/parsing.rs @@ -32,6 +32,9 @@ impl OpenChannelMessage { } b"direct-tcpip" => ChannelType::DirectTcpip(TcpChannelInfo::new(r)?), b"forwarded-tcpip" => ChannelType::ForwardedTcpIp(TcpChannelInfo::new(r)?), + b"forwarded-streamlocal@openssh.com" => { + ChannelType::ForwardedStreamLocal(StreamLocalChannelInfo::new(r)?) + } b"auth-agent@openssh.com" => ChannelType::AgentForward, t => ChannelType::Unknown { typ: t.to_vec() }, }; @@ -91,6 +94,7 @@ pub enum ChannelType { }, DirectTcpip(TcpChannelInfo), ForwardedTcpIp(TcpChannelInfo), + ForwardedStreamLocal(StreamLocalChannelInfo), AgentForward, Unknown { typ: Vec, @@ -105,6 +109,21 @@ pub struct TcpChannelInfo { pub originator_port: u32, } +#[derive(Debug)] +pub struct StreamLocalChannelInfo { + pub socket_path: String, +} + +impl StreamLocalChannelInfo { + fn new(r: &mut Position) -> Result { + let socket_path = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?) + .map_err(crate::Error::from)? + .to_owned(); + + Ok(Self { socket_path }) + } +} + impl TcpChannelInfo { fn new(r: &mut Position) -> Result { let host_to_connect = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?) diff --git a/russh/src/server/encrypted.rs b/russh/src/server/encrypted.rs index db8a2e6a..9f790610 100644 --- a/russh/src/server/encrypted.rs +++ b/russh/src/server/encrypted.rs @@ -1023,6 +1023,40 @@ impl Session { } Ok(()) } + b"streamlocal-forward@openssh.com" => { + let server_socket_path = + std::str::from_utf8(r.read_string().map_err(crate::Error::from)?) + .map_err(crate::Error::from)?; + debug!("handler.streamlocal_forward {:?}", server_socket_path); + let result = handler + .streamlocal_forward(server_socket_path, self) + .await?; + if let Some(ref mut enc) = self.common.encrypted { + if result { + push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS)) + } else { + push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE)) + } + } + Ok(()) + } + b"cancel-streamlocal-forward@openssh.com" => { + let socket_path = + std::str::from_utf8(r.read_string().map_err(crate::Error::from)?) + .map_err(crate::Error::from)?; + debug!("handler.cancel_streamlocal_forward {:?}", socket_path); + let result = handler + .cancel_streamlocal_forward(socket_path, self) + .await?; + if let Some(ref mut enc) = self.common.encrypted { + if result { + push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS)) + } else { + push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE)) + } + } + Ok(()) + } _ => { if let Some(ref mut enc) = self.common.encrypted { push_packet!(enc.write, { @@ -1087,7 +1121,7 @@ impl Session { Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => { let _ = return_channel.send(true); } - None => { + _ => { error!("Received global request failure for unknown request!") } } @@ -1105,7 +1139,7 @@ impl Session { Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => { let _ = return_channel.send(false); } - None => { + _ => { error!("Received global request failure for unknown request!") } } @@ -1211,6 +1245,16 @@ impl Session { } result } + ChannelType::ForwardedStreamLocal(_) => { + if let Some(ref mut enc) = self.common.encrypted { + msg.fail( + &mut enc.write, + msg::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + b"Unsupported channel type", + ); + } + Ok(false) + } ChannelType::AgentForward => { if let Some(ref mut enc) = self.common.encrypted { msg.fail( diff --git a/russh/src/server/mod.rs b/russh/src/server/mod.rs index f3df15b3..c715dbbd 100644 --- a/russh/src/server/mod.rs +++ b/russh/src/server/mod.rs @@ -523,6 +523,24 @@ pub trait Handler: Sized { ) -> Result { Ok(false) } + + #[allow(unused_variables)] + async fn streamlocal_forward( + &mut self, + socket_path: &str, + session: &mut Session, + ) -> Result { + Ok(false) + } + + #[allow(unused_variables)] + async fn cancel_streamlocal_forward( + &mut self, + socket_path: &str, + session: &mut Session, + ) -> Result { + Ok(false) + } } #[async_trait] diff --git a/russh/src/server/session.rs b/russh/src/server/session.rs index a2b7210b..cac70d5e 100644 --- a/russh/src/server/session.rs +++ b/russh/src/server/session.rs @@ -42,6 +42,10 @@ pub enum Msg { originator_port: u32, channel_ref: ChannelRef, }, + ChannelOpenForwardedStreamLocal { + server_socket_path: String, + channel_ref: ChannelRef, + }, ChannelOpenX11 { originator_address: String, originator_port: u32, @@ -275,6 +279,25 @@ impl Handle { .await } + pub async fn channel_open_forwarded_streamlocal>( + &self, + server_socket_path: A, + ) -> Result, Error> { + let (sender, receiver) = unbounded_channel(); + let channel_ref = ChannelRef::new(sender); + let window_size_ref = channel_ref.window_size().clone(); + + self.sender + .send(Msg::ChannelOpenForwardedStreamLocal { + server_socket_path: server_socket_path.into(), + channel_ref, + }) + .await + .map_err(|_| Error::SendError)?; + self.wait_channel_confirmation(receiver, window_size_ref) + .await + } + pub async fn channel_open_x11>( &self, originator_address: A, @@ -523,6 +546,10 @@ impl Session { let id = self.channel_open_forwarded_tcpip(&connected_address, connected_port, &originator_address, originator_port)?; self.channels.insert(id, channel_ref); } + Some(Msg::ChannelOpenForwardedStreamLocal { server_socket_path, channel_ref }) => { + let id = self.channel_open_forwarded_streamlocal(&server_socket_path)?; + self.channels.insert(id, channel_ref); + } Some(Msg::ChannelOpenX11 { originator_address, originator_port, channel_ref }) => { let id = self.channel_open_x11(&originator_address, originator_port)?; self.channels.insert(id, channel_ref); @@ -919,6 +946,16 @@ impl Session { }) } + pub fn channel_open_forwarded_streamlocal( + &mut self, + socket_path: &str, + ) -> Result { + self.channel_open_generic(b"forwarded-streamlocal@openssh.com", |write| { + write.extend_ssh_string(socket_path.as_bytes()); + write.extend_ssh_string(b""); + }) + } + /// Open a new X11 channel, when a connection comes to a /// local port. See [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.3.2). /// TCP/IP packets can then be tunneled through the channel using `.data()`. diff --git a/russh/src/session.rs b/russh/src/session.rs index c8379824..0a1f633b 100644 --- a/russh/src/session.rs +++ b/russh/src/session.rs @@ -626,4 +626,7 @@ pub(crate) enum GlobalRequestResponse { TcpIpForward(oneshot::Sender>), /// request was for CancelTcpIpForward, sends true for success or false for failure CancelTcpIpForward(oneshot::Sender), + /// request was for StreamLocalForward, sends true for success or false for failure + StreamLocalForward(oneshot::Sender), + CancelStreamLocalForward(oneshot::Sender), }