diff --git a/bin/fakesshd/src/main.rs b/bin/fakesshd/src/main.rs index 77db2a7..d5cae60 100644 --- a/bin/fakesshd/src/main.rs +++ b/bin/fakesshd/src/main.rs @@ -208,7 +208,10 @@ async fn handle_connection( state.do_operation(update.number.construct_op(ChannelOperationKind::Close)); } } - ChannelUpdateKind::ExtendedData { .. } | ChannelUpdateKind::Eof => { /* ignore */ } + ChannelUpdateKind::ExtendedData { .. } + | ChannelUpdateKind::Eof + | ChannelUpdateKind::Success + | ChannelUpdateKind::Failure => { /* ignore */ } ChannelUpdateKind::Closed => { session_channels.remove(&update.number); } diff --git a/bin/ssh/src/main.rs b/bin/ssh/src/main.rs index b97c18c..440b17c 100644 --- a/bin/ssh/src/main.rs +++ b/bin/ssh/src/main.rs @@ -4,7 +4,7 @@ use clap::Parser; use eyre::{bail, Context, ContextCompat, OptionExt}; use rand::RngCore; -use ssh_transport::{key::PublicKey, numbers, parse::Writer}; +use ssh_transport::{key::PublicKey, numbers, parse::Writer, peer_error}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream, @@ -12,8 +12,11 @@ use tokio::{ use tracing::{debug, error, info}; use ssh_protocol::{ + connection::{ + ChannelNumber, ChannelOpen, ChannelOperation, ChannelOperationKind, ChannelRequest, + }, transport::{self}, - SshStatus, + ChannelUpdate, ChannelUpdateKind, SshStatus, }; use tracing_subscriber::EnvFilter; @@ -43,6 +46,13 @@ enum Operation { }, } +// TODO: state machine everything including auth +enum ClientState { + Start, + WaitingForOpen(ChannelNumber), + WaitingForPty(ChannelNumber), +} + #[tokio::main] async fn main() -> eyre::Result<()> { let args = Args::parse(); @@ -78,17 +88,13 @@ async fn main() -> eyre::Result<()> { ssh_protocol::auth::ClientAuth::new(username.as_bytes().to_vec()), ); + let mut client_state = ClientState::Start; + let (send_op, mut recv_op) = tokio::sync::mpsc::channel::(10); let mut buf = [0; 1024]; loop { - while let Some(msg) = state.next_msg_to_send() { - conn.write_all(&msg.to_bytes()) - .await - .wrap_err("writing response")?; - } - if let Some(auth) = state.auth() { for req in auth.user_requests() { match req { @@ -159,6 +165,53 @@ async fn main() -> eyre::Result<()> { } } + if let Some(channels) = state.channels() { + if let ClientState::Start = client_state { + let number = channels.create_channel(ChannelOpen::Session); + client_state = ClientState::WaitingForOpen(number); + } + + while let Some(update) = channels.next_channel_update() { + match &update.kind { + ChannelUpdateKind::Open(_) => match client_state { + ClientState::WaitingForOpen(number) => { + if number != update.number { + bail!("unexpected channel opened by server"); + } + client_state = ClientState::WaitingForPty(update.number); + channels.do_operation(number.construct_op( + ChannelOperationKind::Request(ChannelRequest::PtyReq { + want_reply: true, + term: "xterm-256color".to_owned(), + width_chars: 70, + height_rows: 10, + width_px: 0, + height_px: 0, + term_modes: vec![], + }), + )); + } + _ => bail!("unexpected channel opened by server"), + }, + ChannelUpdateKind::Success => {} + ChannelUpdateKind::Failure => bail!("operation failed"), + ChannelUpdateKind::Request(_) => todo!(), + ChannelUpdateKind::Data { .. } => todo!(), + ChannelUpdateKind::ExtendedData { .. } => todo!(), + ChannelUpdateKind::Eof => todo!(), + ChannelUpdateKind::Closed => todo!(), + } + } + } + + // Make sure that we send all queues messages before going into the select, waiting for things to happen. + state.progress(); + while let Some(msg) = state.next_msg_to_send() { + conn.write_all(&msg.to_bytes()) + .await + .wrap_err("writing response")?; + } + tokio::select! { read = conn.read(&mut buf) => { let read = read.wrap_err("reading from connection")?; diff --git a/lib/ssh-connection/src/lib.rs b/lib/ssh-connection/src/lib.rs index 02ea4f2..85e5d1b 100644 --- a/lib/ssh-connection/src/lib.rs +++ b/lib/ssh-connection/src/lib.rs @@ -20,12 +20,23 @@ pub struct ChannelsState { packets_to_send: VecDeque, channel_updates: VecDeque, - channels: HashMap, + channels: HashMap, next_channel_id: ChannelNumber, is_server: bool, } +enum ChannelState { + AwaitingConfirmation { + /// For validation only. + our_window_size: u32, + /// For validation only. + our_max_packet_size: u32, + update_message: ChannelOpen, + }, + Open(Channel), +} + struct Channel { /// Whether our side has closed this channel. we_closed: bool, @@ -58,6 +69,8 @@ pub struct ChannelUpdate { } #[derive(Debug)] pub enum ChannelUpdateKind { + Success, + Failure, Open(ChannelOpen), Request(ChannelRequest), Data { data: Vec }, @@ -65,7 +78,7 @@ pub enum ChannelUpdateKind { Eof, Closed, } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ChannelOpen { Session, } @@ -138,12 +151,12 @@ impl ChannelsState { pub fn recv_packet(&mut self, packet: Packet) -> Result<()> { // TODO: window - let mut packet = packet.payload_parser(); - let packet_type = packet.u8()?; + let mut p = packet.payload_parser(); + let packet_type = p.u8()?; match packet_type { numbers::SSH_MSG_GLOBAL_REQUEST => { - let request_name = packet.utf8_string()?; - let want_reply = packet.bool()?; + let request_name = p.utf8_string()?; + let want_reply = p.bool()?; debug!(%request_name, %want_reply, "Received global request"); self.packets_to_send @@ -151,12 +164,12 @@ impl ChannelsState { } numbers::SSH_MSG_CHANNEL_OPEN => { // - let channel_type = packet.utf8_string()?; - let sender_channel = packet.u32()?; - let initial_window_size = packet.u32()?; - let max_packet_size = packet.u32()?; + let channel_type = p.utf8_string()?; + let sender_channel = p.u32()?; + let initial_window_size = p.u32()?; + let max_packet_size = p.u32()?; - debug!(%channel_type, %sender_channel, "Opening channel"); + debug!(%channel_type, %sender_channel, "Receving channel open"); let update_message = match channel_type { "session" => ChannelOpen::Session, @@ -188,7 +201,7 @@ impl ChannelsState { self.channels.insert( our_number, - Channel { + ChannelState::Open(Channel { we_closed: false, peer_channel: sender_channel, peer_max_packet_size: max_packet_size, @@ -198,7 +211,7 @@ impl ChannelsState { our_window_size_increase_step: initial_window_size, queued_data: Vec::new(), - }, + }), ); self.channel_updates.push_back(ChannelUpdate { @@ -208,10 +221,48 @@ impl ChannelsState { debug!(%channel_type, %our_number, "Successfully opened channel"); } + numbers::SSH_MSG_CHANNEL_OPEN_CONFIRMATION => { + let our_channel = p.u32()?; + let our_number = ChannelNumber(our_channel); + let Some(&ChannelState::AwaitingConfirmation { + our_window_size, + our_max_packet_size, + ref update_message, + }) = self.channels.get(&our_number) + else { + return Err(peer_error!("unknown channel: {our_channel}")); + }; + + let peer_channel = p.u32()?; + let peer_window_size = p.u32()?; + let peer_max_packet_size = p.u32()?; + + self.channel_updates.push_back(ChannelUpdate { + number: our_number, + kind: ChannelUpdateKind::Open(update_message.clone()), + }); + + self.channels.insert( + our_number, + ChannelState::Open(Channel { + we_closed: false, + peer_channel, + peer_max_packet_size, + peer_window_size, + our_max_packet_size, + our_window_size, + our_window_size_increase_step: our_window_size, + + queued_data: Vec::new(), + }), + ); + + debug!(channel_type = %"session", %our_number, "Successfully opened channel"); + } numbers::SSH_MSG_CHANNEL_WINDOW_ADJUST => { - let our_channel = packet.u32()?; + let our_channel = p.u32()?; let our_channel = self.validate_channel(our_channel)?; - let bytes_to_add = packet.u32()?; + let bytes_to_add = p.u32()?; let channel = self.channel(our_channel)?; channel.peer_window_size = channel @@ -227,9 +278,9 @@ impl ChannelsState { } } numbers::SSH_MSG_CHANNEL_DATA => { - let our_channel = packet.u32()?; + let our_channel = p.u32()?; let our_channel = self.validate_channel(our_channel)?; - let data = packet.string()?; + let data = p.string()?; let channel = self.channel(our_channel)?; channel.our_window_size = channel @@ -270,7 +321,7 @@ impl ChannelsState { } numbers::SSH_MSG_CHANNEL_EOF => { // - let our_channel = packet.u32()?; + let our_channel = p.u32()?; let our_channel = self.validate_channel(our_channel)?; self.channel_updates.push_back(ChannelUpdate { @@ -280,7 +331,7 @@ impl ChannelsState { } numbers::SSH_MSG_CHANNEL_CLOSE => { // - let our_channel = packet.u32()?; + let our_channel = p.u32()?; let our_channel = self.validate_channel(our_channel)?; let channel = self.channel(our_channel)?; if !channel.we_closed { @@ -299,10 +350,10 @@ impl ChannelsState { debug!("Channel has been closed"); } numbers::SSH_MSG_CHANNEL_REQUEST => { - let our_channel = packet.u32()?; + let our_channel = p.u32()?; let our_channel = self.validate_channel(our_channel)?; - let request_type = packet.utf8_string()?; - let want_reply = packet.bool()?; + let request_type = p.utf8_string()?; + let want_reply = p.bool()?; debug!(channel = %our_channel, %request_type, "Got channel request"); @@ -315,12 +366,12 @@ impl ChannelsState { return Err(peer_error!("server tried to open pty")); } - let term = packet.utf8_string()?; - let width_chars = packet.u32()?; - let height_rows = packet.u32()?; - let width_px = packet.u32()?; - let height_px = packet.u32()?; - let term_modes = packet.string()?; + let term = p.utf8_string()?; + let width_chars = p.u32()?; + let height_rows = p.u32()?; + let width_px = p.u32()?; + let height_px = p.u32()?; + let term_modes = p.string()?; debug!( channel = %our_channel, @@ -353,7 +404,7 @@ impl ChannelsState { return Err(peer_error!("server tried to execute command")); } - let command = packet.string()?; + let command = p.string()?; info!(channel = %our_channel, command = %String::from_utf8_lossy(command), "Executing command"); ChannelRequest::Exec { want_reply, @@ -365,8 +416,8 @@ impl ChannelsState { return Err(peer_error!("server tried to set environment var")); } - let name = packet.utf8_string()?; - let value = packet.string()?; + let name = p.utf8_string()?; + let value = p.string()?; info!(channel = %our_channel, %name, value = %String::from_utf8_lossy(value), "Setting environment variable"); @@ -397,6 +448,24 @@ impl ChannelsState { kind: ChannelUpdateKind::Request(channel_request), }) } + numbers::SSH_MSG_CHANNEL_SUCCESS => { + let our_channel = p.u32()?; + let our_channel = self.validate_channel(our_channel)?; + + self.channel_updates.push_back(ChannelUpdate { + number: our_channel, + kind: ChannelUpdateKind::Success, + }); + } + numbers::SSH_MSG_CHANNEL_FAILURE => { + let our_channel = p.u32()?; + let our_channel = self.validate_channel(our_channel)?; + + self.channel_updates.push_back(ChannelUpdate { + number: our_channel, + kind: ChannelUpdateKind::Failure, + }); + } _ => { todo!( "unsupported packet: {} ({packet_type})", @@ -416,6 +485,43 @@ impl ChannelsState { self.channel_updates.pop_front() } + /// Create a new channel + pub fn create_channel(&mut self, kind: ChannelOpen) -> ChannelNumber { + let our_number = self.next_channel_id; + self.next_channel_id = ChannelNumber( + self.next_channel_id + .0 + .checked_add(1) + .expect("created too many channels"), + ); + + assert_eq!(kind, ChannelOpen::Session, "TODO"); + + let our_window_size = 2097152; // same as OpenSSH + let our_max_packet_size = 32768; // same as OpenSSH + + self.packets_to_send + .push_back(Packet::new_msg_channel_open_session( + b"session", + our_number.0, + our_window_size, + our_max_packet_size, + )); + + self.channels.insert( + our_number, + ChannelState::AwaitingConfirmation { + our_window_size, + our_max_packet_size, + update_message: kind, + }, + ); + + debug!(channel_type = %"session", %our_number, "Opening channel"); + + our_number + } + /// Executes an operation on the channel. /// If the channel has already been closed, the operation is dropped. pub fn do_operation(&mut self, op: ChannelOperation) { @@ -440,8 +546,28 @@ impl ChannelsState { } ChannelOperationKind::Request(req) => { let packet = match req { - ChannelRequest::PtyReq { .. } => todo!("pty-req"), - ChannelRequest::Shell { .. } => todo!("shell"), + ChannelRequest::PtyReq { + want_reply, + term, + width_chars, + height_rows, + width_px, + height_px, + term_modes, + } => Packet::new_msg_channel_request_pty_req( + peer, + b"pty-req", + want_reply, + term.as_bytes(), + width_chars, + height_rows, + width_px, + height_px, + &term_modes, + ), + ChannelRequest::Shell { want_reply } => { + Packet::new_msg_channel_request_shell(peer, b"shell", want_reply) + } ChannelRequest::Exec { .. } => todo!("exec"), ChannelRequest::Env { .. } => todo!("env"), ChannelRequest::ExitStatus { status } => { @@ -540,9 +666,16 @@ impl ChannelsState { } fn channel(&mut self, number: ChannelNumber) -> Result<&mut Channel> { - self.channels + let state = self + .channels .get_mut(&number) - .ok_or_else(|| peer_error!("unknown channel: {number:?}")) + .ok_or_else(|| peer_error!("unknown channel: {number:?}"))?; + match state { + ChannelState::AwaitingConfirmation { .. } => { + Err(peer_error!("channel not fully opened: {number:?}")) + } + ChannelState::Open(channel) => Ok(channel), + } } } diff --git a/lib/ssh-protocol/src/lib.rs b/lib/ssh-protocol/src/lib.rs index 3b13692..0a4a952 100644 --- a/lib/ssh-protocol/src/lib.rs +++ b/lib/ssh-protocol/src/lib.rs @@ -154,6 +154,13 @@ impl ClientConnection { } } + pub fn channels(&mut self) -> Option<&mut ssh_connection::ChannelsState> { + match &mut self.state { + ClientConnectionState::Open(channels) => Some(channels), + _ => None, + } + } + pub fn is_open(&self) -> bool { matches!(self.state, ClientConnectionState::Open(_)) }