diff --git a/async/src/cmdline_client.rs b/async/src/cmdline_client.rs index 8f9a540..5214892 100644 --- a/async/src/cmdline_client.rs +++ b/async/src/cmdline_client.rs @@ -315,6 +315,9 @@ impl CmdlineClient { } } } + CliEvent::Banner(b) => { + println!("Banner from server:\n{}", b.banner()?) + } CliEvent::Defunct => { trace!("break defunct"); break Ok::<_, Error>(()) diff --git a/src/client.rs b/src/client.rs index 19b4357..c9dd356 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,12 +30,4 @@ impl Client { parse_ctx.cli_auth_type = None; self.auth.success() } - - pub(crate) fn banner( - &mut self, - banner: &packets::UserauthBanner<'_>, - ) { - // TODO event banner - // b.show_banner(banner.message, banner.lang) - } } diff --git a/src/conn.rs b/src/conn.rs index 802396c..d83aba6 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -160,6 +160,10 @@ impl Conn { self.cliserv.is_client() } + pub fn is_server(&self) -> bool { + !self.is_client() + } + pub fn server(&self) -> Result<&server::Server> { match &self.cliserv { ClientServer::Server(s) => Ok(s), @@ -437,13 +441,12 @@ impl Conn { return error::SSHProto.fail() } } - Packet::UserauthBanner(p) => { - if let ClientServer::Client(cli) = &mut self.cliserv { - cli.banner(&p); - } else { + Packet::UserauthBanner(_) => { + if self.is_server() { debug!("Received banner as a server"); return error::SSHProto.fail() } + disp.event = DispatchEvent::CliEvent(CliEventId::Banner); } Packet::Userauth60(p) => { // TODO: client only @@ -547,6 +550,15 @@ impl Conn { CliSessionExit::new(&packet) } + pub(crate) fn fetch_cli_banner<'p>(&mut self, payload: &'p [u8]) -> Result> { + self.client()?; + if let Packet::UserauthBanner(b) = self.packet(payload)? { + Ok(Banner(b)) + } else { + Err(Error::bug()) + } + } + pub(crate) fn resume_servhostkeys(&mut self, payload: &[u8], s: &mut TrafSend, keys: &[&SignKey]) -> Result<()> { self.server()?; diff --git a/src/event.rs b/src/event.rs index e45043d..cd8379c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -51,6 +51,7 @@ impl<'g, 'a> Event<'g, 'a> { pub enum CliEvent<'g, 'a> { Hostkey(CheckHostkey<'g, 'a>), + Banner(Banner<'g>), Username(RequestUsername<'g, 'a>), Password(RequestPassword<'g, 'a>), Pubkey(RequestPubkey<'g, 'a>), @@ -78,6 +79,7 @@ impl Debug for CliEvent<'_, '_> { Self::SessionOpened(_) => "SessionOpened", Self::SessionExit(_) => "SessionExit", Self::AgentSign(_) => "AgentSign", + Self::Banner(_) => "Banner", Self::Defunct => "Defunct", }; write!(f, "CliEvent({e})") @@ -167,6 +169,19 @@ impl CheckHostkey<'_, '_> { } } +pub struct Banner<'a>(pub(crate) packets::UserauthBanner<'a>); + +impl Banner<'_> { + pub fn banner(&self) -> Result<&str> { + self.0.message.as_str() + } + + pub fn raw_banner(&self) -> TextString { + self.0.message + } +} + + // impl CliExit<''_, '_> { // pub fn @@ -182,11 +197,11 @@ pub(crate) enum CliEventId { Authenticated, SessionOpened(ChanNum), SessionExit, + Banner, Defunct // TODO: // Disconnected - // Banner, // OpenTCPForwarded (new session) // TCPDirectOpened (response) } @@ -222,6 +237,9 @@ impl CliEventId { Self::SessionExit => { Ok(CliEvent::SessionExit(runner.fetch_cli_session_exit()?)) } + Self::Banner => { + Ok(CliEvent::Banner(runner.fetch_cli_banner()?)) + } Self::Defunct => error::BadUsage.fail() } } @@ -233,6 +251,7 @@ impl CliEventId { | Self::Authenticated | Self::SessionOpened(_) | Self::SessionExit + | Self::Banner | Self::Defunct => false, | Self::Hostkey diff --git a/src/lib.rs b/src/lib.rs index 403ce59..d3a75e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ mod ssh_chapoly; mod traffic; use conn::DispatchEvent; +use event::{CliEventId, ServEventId}; // Application API pub use sshwire::TextString; diff --git a/src/packets.rs b/src/packets.rs index 9af9bb4..4c0be21 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -648,6 +648,9 @@ pub struct ChannelRequest<'a> { pub req: ChannelReqType<'a>, } +/// Channel Requests +/// +/// Most are specified in [RFC4335](https://datatracker.ietf.org/doc/html/rfc4335) #[derive(Debug, SSHEncode, SSHDecode)] pub enum ChannelReqType<'a> { #[sshwire(variant = "shell")] @@ -666,6 +669,9 @@ pub enum ChannelReqType<'a> { ExitStatus(ExitStatus), #[sshwire(variant = "exit-signal")] ExitSignal(ExitSignal<'a>), + /// Channel Break Request + /// + /// [RFC4335](https://datatracker.ietf.org/doc/html/rfc4335) #[sshwire(variant = "break")] Break(Break), // Other requests that aren't implemented at present: @@ -729,6 +735,7 @@ pub struct ExitSignal<'a> { #[derive(Debug, Clone, SSHEncode, SSHDecode)] pub struct Break { + /// Break length in milliseconds pub length: u32, } diff --git a/src/runner.rs b/src/runner.rs index 4084331..2929540 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -12,10 +12,11 @@ use pretty_hex::PrettyHex; use crate::{event::ChanRequest, packets::{Packet, Subsystem}, *}; use packets::{ChannelDataExt, ChannelData}; -use crate::channel::{ChanNum, ChanData}; +use channel::{ChanNum, ChanData}; +use channel::{CliSessionExit, CliSessionOpener}; use encrypt::KeyState; use traffic::{TrafIn, TrafOut}; -use event::{CliEvent, ServEvent, ServEventId, CliEventId}; +use event::{Event, CliEvent, ServEvent, ServEventId, CliEventId}; use conn::{Conn, Dispatched, DispatchEvent}; @@ -445,6 +446,21 @@ impl<'a> Runner<'a> { } } + /// Send a break to a session channel + /// + /// `length` is in milliseconds, or + /// pass 0 as a default (to be interpreted by the remote implementation). + /// Otherwise length will be clamped to the range [500, 3000] ms. + /// Only call on a client session. + pub fn term_break(&mut self, chan: &ChanHandle, length: u32) -> Result<()> { + if self.is_client() { + let mut s = self.traf_out.sender(&mut self.keys); + self.conn.channels.term_break(chan.0, length, &mut s) + } else { + error::BadChannelData.fail() + } + } + pub(crate) fn cli_session_opener(&mut self, ch: ChanNum) -> Result> { let ch = self.conn.channels.get(ch)?; let s = self.traf_out.sender(&mut self.keys); @@ -460,6 +476,11 @@ impl<'a> Runner<'a> { self.conn.fetch_cli_session_exit(payload) } + pub(crate) fn fetch_cli_banner(&mut self) -> Result { + let (payload, _seq) = self.traf_in.payload().trap()?; + self.conn.fetch_cli_banner(payload) + } + fn wake(&mut self) { if self.is_input_ready() { trace!("wake ready_input, waker {:?}", self.input_waker); diff --git a/src/sshnames.rs b/src/sshnames.rs index ffbdbf2..e92c315 100644 --- a/src/sshnames.rs +++ b/src/sshnames.rs @@ -1,6 +1,7 @@ //! Named SSH algorithms, methods, and extensions. //! -//! Some identifiers are also listed directly in `packet.rs` derive attributes. +//! Some identifiers are also listed directly in `packet.rs` derive attributes, +//! for example [channel request identifiers](packets::ChannelReqType). //! Packet numbers are listed in `packets.rs`. //! //! This module also serves as an index of SSH specifications.