diff --git a/sshproto/src/async_behaviour.rs b/sshproto/src/async_behaviour.rs index 5fac8f1c19c52771c7703ccbadbb9c2fd03ff1ec..f64e8747f6cc84ebe4516b7b197cf04797b342af 100644 --- a/sshproto/src/async_behaviour.rs +++ b/sshproto/src/async_behaviour.rs @@ -11,6 +11,7 @@ use { use async_trait::async_trait; use crate::{*, conn::RespPackets}; +use packets::{ForwardedTcpip,DirectTcpip}; use behaviour::*; pub(crate) enum AsyncCliServ<'a> { @@ -95,9 +96,11 @@ pub trait AsyncCliBehaviour: Sync+Send { // TODO: postauth channel callbacks // TODO: do we want this to be async? probably not. - fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened; - fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened; } // #[async_trait(?Send)] @@ -126,7 +129,9 @@ pub trait AsyncServBehaviour: Sync+Send { /// Returns whether a session can be opened fn open_session(&self, chan: u32) -> channel::ChanOpened; - fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened; - fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_direct(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened; } diff --git a/sshproto/src/behaviour.rs b/sshproto/src/behaviour.rs index 8d8a3a5724bb65643e626ad488cf8e05bc14da10..a2c2259adbd28ae571e42789d45a9efc2f94430b 100644 --- a/sshproto/src/behaviour.rs +++ b/sshproto/src/behaviour.rs @@ -13,7 +13,7 @@ use core::fmt; use heapless::spsc::{Queue,Producer,Consumer}; use crate::*; -use packets::{self,Packet}; +use packets::{self,Packet,ForwardedTcpip,DirectTcpip}; use runner::{self,Runner}; use channel::ChanMsg; use conn::RespPackets; @@ -82,20 +82,22 @@ impl<'a> Behaviour<'a> { } /// Calls either client or server - pub(crate) fn open_tcp_forwarded(&mut self, chan: u32) -> channel::ChanOpened { + pub(crate) fn open_tcp_forwarded(&mut self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { if self.is_client() { - self.client().unwrap().open_tcp_forwarded(chan) + self.client().unwrap().open_tcp_forwarded(chan, t) } else { - self.server().unwrap().open_tcp_forwarded(chan) + self.server().unwrap().open_tcp_forwarded(chan, t) } } /// Calls either client or server - pub(crate) fn open_tcp_direct(&mut self, chan: u32) -> channel::ChanOpened { + pub(crate) fn open_tcp_direct(&mut self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { if self.is_client() { - self.client().unwrap().open_tcp_direct(chan) + self.client().unwrap().open_tcp_direct(chan, t) } else { - self.server().unwrap().open_tcp_direct(chan) + self.server().unwrap().open_tcp_direct(chan, t) } } } @@ -134,20 +136,22 @@ impl<'a> Behaviour<'a> } /// Calls either client or server - pub(crate) fn open_tcp_forwarded(&mut self, chan: u32) -> channel::ChanOpened { + pub(crate) fn open_tcp_forwarded(&mut self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { if self.is_client() { - self.client().unwrap().open_tcp_forwarded(chan) + self.client().unwrap().open_tcp_forwarded(chan, t) } else { - self.server().unwrap().open_tcp_forwarded(chan) + self.server().unwrap().open_tcp_forwarded(chan, t) } } /// Calls either client or server - pub(crate) fn open_tcp_direct(&mut self, chan: u32) -> channel::ChanOpened { + pub(crate) fn open_tcp_direct(&mut self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { if self.is_client() { - self.client().unwrap().open_tcp_direct(chan) + self.client().unwrap().open_tcp_direct(chan, t) } else { - self.server().unwrap().open_tcp_direct(chan) + self.server().unwrap().open_tcp_direct(chan, t) } } } @@ -190,12 +194,14 @@ impl<'a> CliBehaviour<'a> { Ok(()) } - pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_forwarded(chan) + pub(crate) fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { + self.inner.open_tcp_forwarded(chan, t) } - pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_direct(chan) + pub(crate) fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { + self.inner.open_tcp_direct(chan, t) } } @@ -231,12 +237,14 @@ impl<'a> CliBehaviour<'a> { Ok(()) } - pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_forwarded(chan) + pub(crate) fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { + self.inner.open_tcp_forwarded(chan, t) } - pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_direct(chan) + pub(crate) fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { + self.inner.open_tcp_direct(chan, t) } } @@ -271,12 +279,14 @@ impl<'a> ServBehaviour<'a> { self.inner.open_session(chan) } - pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_forwarded(chan) + pub(crate) fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { + self.inner.open_tcp_forwarded(chan, t) } - pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_direct(chan) + pub(crate) fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { + self.inner.open_tcp_direct(chan, t) } } @@ -291,12 +301,14 @@ impl<'a> ServBehaviour<'a> { self.inner.open_session(chan) } - pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_forwarded(chan) + pub(crate) fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened { + self.inner.open_tcp_forwarded(chan, t) } - pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened { - self.inner.open_tcp_direct(chan) + pub(crate) fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened { + self.inner.open_tcp_direct(chan, t) } } diff --git a/sshproto/src/block_behaviour.rs b/sshproto/src/block_behaviour.rs index fb72694d7ee3dfb51e7de39dc5ba8275b8824c35..85e87575bf6a1a2d9c04939f5a812ec69e788e4f 100644 --- a/sshproto/src/block_behaviour.rs +++ b/sshproto/src/block_behaviour.rs @@ -8,6 +8,7 @@ use { }; use crate::{conn::RespPackets, *}; +use packets::{ForwardedTcpip,DirectTcpip}; use behaviour::*; pub(crate) enum BlockCliServ<'a> { @@ -83,9 +84,11 @@ pub trait BlockCliBehaviour { } // TODO: postauth channel callbacks - fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened; - fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened; } pub trait BlockServBehaviour { @@ -101,7 +104,9 @@ pub trait BlockServBehaviour { /// Returns whether a session can be opened fn open_session(&self, chan: u32) -> channel::ChanOpened; - fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_forwarded(&self, chan: u32, + t: &ForwardedTcpip) -> channel::ChanOpened; - fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened; + fn open_tcp_direct(&self, chan: u32, + t: &DirectTcpip) -> channel::ChanOpened; } diff --git a/sshproto/src/channel.rs b/sshproto/src/channel.rs index 3c08cc8be6551534fffa47210028ee4744d3c10d..9522b8e7223e05545dbb8230935e41e74a2efe7b 100644 --- a/sshproto/src/channel.rs +++ b/sshproto/src/channel.rs @@ -8,7 +8,7 @@ use core::mem; use heapless::{Deque, String, Vec}; -use crate::{conn::RespPackets, *}; +use crate::{conn::RespPackets, *, packets::ChannelOpenFailure}; use config::*; use packets::{ChannelReqType, ChannelRequest, Packet, ChannelOpen, ChannelOpenType, ChannelData, ChannelDataExt}; use sshwire::{BinString, TextString}; @@ -32,6 +32,27 @@ pub(crate) struct Channels { pub(crate) type InitReqs = Vec<ReqDetails, MAX_INIT_REQS>; +// for dispatch_open_inner() +enum DispatchOpenError { + Error(Error), + Failure(ChanFail), +} + +impl From<Error> for DispatchOpenError { + fn from(e: Error) -> Self { + match e { + Error::NoChannels => Self::Failure(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE), + e => Self::Error(e) + } + } +} + +impl From<ChanFail> for DispatchOpenError { + fn from(f: ChanFail) -> Self { + Self::Failure(f) + } +} + impl Channels { pub fn new() -> Self { Channels { @@ -174,82 +195,82 @@ impl Channels { self.get(num).map_or(Some(0), |c| c.send_allowed()) } - pub fn channel_open(&mut self, p: &ChannelOpen<'_>, + fn dispatch_open(&mut self, p: &ChannelOpen<'_>, resp: &mut RespPackets<'_>, b: &mut Behaviour<'_>, ) -> Result<()> { - let mut failure = None; - let open_res = match &p.ty { - ChannelOpenType::Session => { - // only server should receive session opens - let bserv = b.server().map_err(|_| Error::SSHProtoError)?; - match self.reserve_chan(p) { - Ok(ch) => { - let r = bserv.open_session(ch.recv.num); - Some((ch, r)) - } - Err(_) => { - failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE); - None - }, - } + match self.dispatch_open_inner(p, resp, b) { + Err(DispatchOpenError::Failure(f)) => { + let r = packets::ChannelOpenFailure { + num: p.num, + reason: f as u32, + desc: "".into(), + lang: "", + }; + let r: Packet = r.into(); + resp.push(r.into()).trap()?; + Ok(()) + }, + Err(DispatchOpenError::Error(e)) => Err(e), + Ok(()) => Ok(()) + } + } + + // the caller will send failure messages if required + fn dispatch_open_inner(&mut self, p: &ChannelOpen<'_>, + resp: &mut RespPackets<'_>, + b: &mut Behaviour<'_>, + ) -> Result<(), DispatchOpenError> { + + if b.is_client() && matches!(p.ty, ChannelOpenType::Session) { + // only server should receive session opens + return Err(Error::SSHProtoError.into()); + } + + // get a channel + let ch = match &p.ty { + ChannelOpenType::Unknown(u) => { + debug!("Rejecting unknown channel type '{u}'"); + return Err(ChanFail::SSH_OPEN_UNKNOWN_CHANNEL_TYPE.into()); } - ChannelOpenType::ForwardedTcpip(t) => { - match self.reserve_chan(p) { - Ok(ch) => { - let r = b.open_tcp_forwarded(ch.recv.num); - Some((ch, r)) - } - Err(_) => { - failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE); - None - }, - } + _ => { + self.reserve_chan(p)? + } + }; + // beware that a reserved channel must be cleaned up on failure + + // run the Behaviour function + let r = match &p.ty { + ChannelOpenType::Session => { + // unwrap: earlier test ensures b.server() succeeds + let bserv = b.server().unwrap(); + bserv.open_session(ch.recv.num) + } + ChannelOpenType::ForwardedTcpip(t) => { + b.open_tcp_forwarded(ch.recv.num, t) } ChannelOpenType::DirectTcpip(t) => { - match self.reserve_chan(p) { - Ok(ch) => { - let r = b.open_tcp_direct(ch.recv.num); - Some((ch, r)) - } - Err(_) => { - failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE); - None - }, - } + b.open_tcp_direct(ch.recv.num, t) } - ChannelOpenType::Unknown(u) => { - debug!("Rejecting unknown channel type '{u}'"); - failure = Some(ChanFail::SSH_OPEN_UNKNOWN_CHANNEL_TYPE); - None + ChannelOpenType::Unknown(_) => { + unreachable!() } }; - if let Some((ch, r)) = open_res { - match r { - ChanOpened::Success => { - ch.open_done(); - }, - ChanOpened::Failure(f) => { - failure = Some(f); - } - ChanOpened::Defer => { - // application will reply later - } + match r { + ChanOpened::Success => { + resp.push(ch.open_done().into()).trap()? + }, + ChanOpened::Failure(f) => { + let n = ch.recv.num; + self.remove(n); + return Err(f.into()) + } + ChanOpened::Defer => { + // application will reply later } - } - - if let Some(reason) = failure { - let r = packets::ChannelOpenFailure { - num: p.num, - reason: reason as u32, - desc: "".into(), - lang: "", - }; - let r: Packet = r.into(); - resp.push(r.into()).trap()?; } Ok(()) @@ -267,7 +288,7 @@ impl Channels { trace!("chan dispatch"); let r = match packet { Packet::ChannelOpen(p) => { - self.channel_open(&p, resp, b) + self.dispatch_open(&p, resp, b) .map(|_| None) } @@ -490,8 +511,9 @@ pub struct ChanDir { } enum ChanState { - /// An incoming channel open request that has not yet been responded to, - /// should not be used + /// An incoming channel open request that has not yet been responded to. + /// + /// Not to be used for normal channel messages InOpen, /// `init_req` are the request messages to be sent once the ChannelOpenConfirmation /// is received @@ -515,7 +537,7 @@ pub(crate) struct Channel { last_req: heapless::Deque<ReqKind, MAX_OUTSTANDING_REQS>, recv: ChanDir, - // filled after confirmation when we initiate the channel + /// populated in all states except `Opening` send: Option<ChanDir>, /// Accumulated bytes for the next window adjustment (inbound data direction) @@ -554,9 +576,19 @@ impl Channel { self.recv.num } - fn open_done(&mut self) { + /// Returns an open confirmation reply packet to send. + /// Must be called with state of `InOpen`. + fn open_done<'p>(&mut self) -> Packet<'p> { debug_assert!(matches!(self.state, ChanState::InOpen)); - self.state = ChanState::Normal + + self.state = ChanState::Normal; + packets::ChannelOpenConfirmation { + num: self.recv.num, + // unwrap: state is InOpen + sender_num: self.send.as_ref().unwrap().num, + initial_window: self.recv.window as u32, + max_packet: self.recv.max_packet as u32, + }.into() } fn finished_input(&mut self, len: usize ) { diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs index 4b719793e061b5fd66bfc11bd6a14f0d6e86ff66..bfbb14b2affd80ef11ae51fcd7579cd1dbd017d8 100644 --- a/sshproto/src/error.rs +++ b/sshproto/src/error.rs @@ -42,7 +42,7 @@ pub enum Error { /// Signature doesn't match key type SigMismatch, - /// Error in received SSH protocol + /// Error in received SSH protocol. Will disconnect. SSHProtoError, /// Remote peer isn't SSH