diff --git a/src/main.rs b/src/main.rs index 1dbe849..ad36772 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use tokio::{ }; use tracing::{error, info}; -use ssh_transport::{ServerConnection, SshError, ThreadRngRand}; +use ssh_transport::{ServerConnection, SshStatus, ThreadRngRand}; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -67,13 +67,16 @@ async fn handle_connection(next: (TcpStream, SocketAddr)) -> Result<()> { if let Err(err) = state.recv_bytes(&buf[..read]) { match err { - SshError::ClientError(err) => { + SshStatus::ClientError(err) => { info!(?err, "disconnecting client after invalid operation"); return Ok(()); } - SshError::ServerError(err) => { + SshStatus::ServerError(err) => { return Err(err); } + SshStatus::Disconnect => { + return Ok(()); + } } } diff --git a/ssh-transport/src/channel.rs b/ssh-transport/src/channel.rs index 3bd89da..1e2472e 100644 --- a/ssh-transport/src/channel.rs +++ b/ssh-transport/src/channel.rs @@ -12,6 +12,8 @@ pub(crate) struct ServerChannelsState { } struct SessionChannel { + /// Whether our side has closed this channel. + we_closed: bool, peer_channel: u32, has_pty: bool, has_shell: bool, @@ -49,6 +51,7 @@ impl ServerChannelsState { confirm.u32(max_packet_size); self.channels.push(SessionChannel { + we_closed: false, peer_channel: sender_channel, has_pty: false, has_shell: false, @@ -67,7 +70,7 @@ impl ServerChannelsState { failure.u32(sender_channel); failure.u32(3); // SSH_OPEN_UNKNOWN_CHANNEL_TYPE failure.string(b"unknown channel type"); - failure.string(b"en_US"); + failure.string(b""); self.packets_to_send.push_back(Packet { payload: failure.finish(), @@ -80,15 +83,54 @@ impl ServerChannelsState { let data = payload.string()?; let channel = self.channel(our_channel)?; + let peer = channel.peer_channel; channel.recv_bytes(data); let mut reply = Writer::new(); reply.u8(Packet::SSH_MSG_CHANNEL_DATA); reply.u32(channel.peer_channel); - reply.string(data); + reply.string(data); // echo :3 self.packets_to_send.push_back(Packet { payload: reply.finish(), }); + + if data.contains(&0x03 /*EOF, Ctrl-C*/) { + debug!(?our_channel, "Received EOF, closing channel"); + // + let mut eof = Writer::new(); + eof.u8(Packet::SSH_MSG_CHANNEL_EOF); + eof.u32(peer); + self.packets_to_send.push_back(Packet { + payload: eof.finish(), + }); + + let mut close = Writer::new(); + close.u8(Packet::SSH_MSG_CHANNEL_CLOSE); + close.u32(peer); + self.packets_to_send.push_back(Packet { + payload: close.finish(), + }); + + let channel = self.channel(our_channel)?; + channel.we_closed = true; + } + } + Packet::SSH_MSG_CHANNEL_CLOSE => { + // + let our_channel = payload.u32()?; + let channel = self.channel(our_channel)?; + if !channel.we_closed { + let mut close = Writer::new(); + close.u8(Packet::SSH_MSG_CHANNEL_CLOSE); + close.u32(channel.peer_channel); + self.packets_to_send.push_back(Packet { + payload: close.finish(), + }); + } + + self.channels.remove(our_channel as usize); + + debug!("Channel has been closed"); } Packet::SSH_MSG_CHANNEL_REQUEST => { let our_channel = payload.u32()?; diff --git a/ssh-transport/src/lib.rs b/ssh-transport/src/lib.rs index 975fbd4..189aa5c 100644 --- a/ssh-transport/src/lib.rs +++ b/ssh-transport/src/lib.rs @@ -21,7 +21,10 @@ use x25519_dalek::{EphemeralSecret, PublicKey}; pub use packet::Msg; #[derive(Debug)] -pub enum SshError { +pub enum SshStatus { + /// The client has sent a disconnect request, close the connection. + /// This is not an error. + Disconnect, /// The client did something wrong. /// The connection should be closed and a notice may be logged, /// but this does not require operator intervention. @@ -32,9 +35,9 @@ pub enum SshError { ServerError(eyre::Report), } -pub type Result = std::result::Result; +pub type Result = std::result::Result; -impl From for SshError { +impl From for SshStatus { fn from(value: eyre::Report) -> Self { Self::ServerError(value) } @@ -140,6 +143,23 @@ impl ServerConnection { while let Some(packet) = self.packet_transport.recv_next_packet() { trace!(packet_type = ?packet.payload.get(0), packet_len = ?packet.payload.len(), "Received packet"); + + // Handle some packets ignoring the state. + match packet.payload.get(0).copied() { + Some(Packet::SSH_MSG_DISCONNECT) => { + // + let mut disconnect = Parser::new(&packet.payload[1..]); + let reason = disconnect.u32()?; + let description = disconnect.utf8_string()?; + let _language_tag = disconnect.utf8_string()?; + + info!(?reason, ?description, "Client disconnecting"); + + return Ok(()); + } + _ => {} + } + match &mut self.state { ServerState::ProtoExchange { .. } => unreachable!("handled above"), ServerState::KeyExchangeInit { @@ -407,7 +427,7 @@ impl ServerConnection { let mut banner = Writer::new(); banner.u8(Packet::SSH_MSG_USERAUTH_BANNER); banner.string(b"!! this system ONLY allows catgirls to enter !!\r\n!! all other attempts WILL be prosecuted to the full extent of the rawr !!\r\n"); - banner.string(b"en_US"); + banner.string(b""); self.packet_transport.queue_packet(Packet { payload: banner.finish(), }); @@ -442,9 +462,9 @@ impl ServerConnection { let mut failure = Writer::new(); failure.u8(Packet::SSH_MSG_REQUEST_FAILURE); - //self.packet_transport.queue_packet(Packet { - // payload: failure.finish(), - //}); + self.packet_transport.queue_packet(Packet { + payload: failure.finish(), + }); } _ => { todo!("packet: {packet_type}"); @@ -485,7 +505,7 @@ const PRIVKEY_BYTES: &[u8; 32] = &[ macro_rules! client_error { ($($tt:tt)*) => { - $crate::SshError::ClientError(::std::format!($($tt)*)) + $crate::SshStatus::ClientError(::std::format!($($tt)*)) }; } use client_error;