diff --git a/ssh-connection/src/lib.rs b/ssh-connection/src/lib.rs index e1ed445..74aa02e 100644 --- a/ssh-connection/src/lib.rs +++ b/ssh-connection/src/lib.rs @@ -399,13 +399,14 @@ impl ServerChannelsState { pub fn do_operation(&mut self, op: ChannelOperation) { op.trace(); - let channel = self - .channel(op.number) - .expect("passed channel ID that does not exist"); + let Ok(channel) = self.channel(op.number) else { + debug!(number = %op.number, "Dropping operation as channel does not exist, probably because it has been closed"); + return; + }; let peer = channel.peer_channel; if channel.we_closed { - debug!("Dropping operation as channel has been closed already"); + debug!(number = %op.number, "Dropping operation as channel has been closed already"); return; } @@ -534,20 +535,88 @@ impl ChannelOperation { #[cfg(test)] mod tests { - use ssh_transport::packet::Packet; + use ssh_transport::{numbers, packet::Packet}; - use crate::ServerChannelsState; + use crate::{ChannelNumber, ChannelOperation, ChannelOperationKind, ServerChannelsState}; + + fn assert_response(state: &mut ServerChannelsState, types: &[u8]) { + let response = state + .packets_to_send() + .map(|p| numbers::packet_type_to_string(p.packet_type())) + .collect::>(); + + let expected = types + .iter() + .map(|p| numbers::packet_type_to_string(*p)) + .collect::>(); + assert_eq!(expected, response); + } + + fn open_session_channel(state: &mut ServerChannelsState) { + state + .recv_packet(Packet::new_msg_channel_open_session( + b"session", 0, 2048, 1024, + )) + .unwrap(); + assert_response(state, &[numbers::SSH_MSG_CHANNEL_OPEN_CONFIRMATION]); + } + + #[test] + fn interactive_pty() { + let state = &mut ServerChannelsState::new(); + open_session_channel(state); + + state + .recv_packet(Packet::new_msg_channel_request_pty_req( + 0, b"pty-req", true, b"xterm", 80, 24, 0, 0, b"", + )) + .unwrap(); + state.do_operation(ChannelNumber(0).construct_op(ChannelOperationKind::Success)); + assert_response(state, &[numbers::SSH_MSG_CHANNEL_SUCCESS]); + + state + .recv_packet(Packet::new_msg_channel_request_shell(0, b"shell", true)) + .unwrap(); + state.do_operation(ChannelNumber(0).construct_op(ChannelOperationKind::Success)); + assert_response(state, &[numbers::SSH_MSG_CHANNEL_SUCCESS]); + + state + .recv_packet(Packet::new_msg_channel_data(0, b"hello, world")) + .unwrap(); + assert_response(state, &[]); + + state.recv_packet(Packet::new_msg_channel_eof(0)).unwrap(); + assert_response(state, &[]); + + state.recv_packet(Packet::new_msg_channel_close(0)).unwrap(); + assert_response(state, &[numbers::SSH_MSG_CHANNEL_CLOSE]); + } #[test] fn only_single_close_for_double_close_operation() { - let state = ServerChannelsState::new(); - //state.recv_packet(); + let state = &mut ServerChannelsState::new(); + open_session_channel(state); + state.do_operation(ChannelOperation { + number: ChannelNumber(0), + kind: ChannelOperationKind::Close, + }); + state.do_operation(ChannelOperation { + number: ChannelNumber(0), + kind: ChannelOperationKind::Close, + }); + assert_response(state, &[numbers::SSH_MSG_CHANNEL_CLOSE]); } #[test] - #[should_panic] - fn panic_when_data_operation_after_close() { - let state = ServerChannelsState::new(); - //state.recv_packet(); + fn ignore_operation_after_close() { + let mut state = &mut ServerChannelsState::new(); + open_session_channel(state); + state.recv_packet(Packet::new_msg_channel_close(0)).unwrap(); + assert_response(&mut state, &[numbers::SSH_MSG_CHANNEL_CLOSE]); + state.do_operation(ChannelOperation { + number: ChannelNumber(0), + kind: ChannelOperationKind::Data(vec![0]), + }); + assert_response(state, &[]); } } diff --git a/ssh-transport/src/packet.rs b/ssh-transport/src/packet.rs index c891e2b..dfee00d 100644 --- a/ssh-transport/src/packet.rs +++ b/ssh-transport/src/packet.rs @@ -148,6 +148,10 @@ pub struct Packet { impl Packet { pub const DEFAULT_BLOCK_SIZE: u8 = 8; + pub fn packet_type(&self) -> u8 { + self.payload[0] + } + pub(crate) fn from_full(bytes: &[u8]) -> Result { let Some(padding_length) = bytes.first() else { return Err(client_error!("empty packet")); diff --git a/ssh-transport/src/packet/ctors.rs b/ssh-transport/src/packet/ctors.rs index fc29b25..6bac0d6 100644 --- a/ssh-transport/src/packet/ctors.rs +++ b/ssh-transport/src/packet/ctors.rs @@ -78,6 +78,12 @@ ctors! { fn new_msg_request_failure(SSH_MSG_REQUEST_FAILURE;); // 90 to 127 Channel related messages + fn new_msg_channel_open_session(SSH_MSG_CHANNEL_OPEN; + session: string, + sender_channel: u32, + initial_window_size: u32, + maximum_packet_size: u32, + ); fn new_msg_channel_open_confirmation(SSH_MSG_CHANNEL_OPEN_CONFIRMATION; peer_channel: u32, sender_channel: u32, @@ -96,6 +102,22 @@ ctors! { fn new_msg_channel_eof(SSH_MSG_CHANNEL_EOF; recipient_channel: u32); fn new_msg_channel_close(SSH_MSG_CHANNEL_CLOSE; recipient_channel: u32); + fn new_msg_channel_request_pty_req(SSH_MSG_CHANNEL_REQUEST; + recipient_channel: u32, + kind_pty_req: string, + want_reply: bool, + term: string, + term_width_char: u32, + term_height_rows: u32, + term_width_px: u32, + term_height_px: u32, + term_modes: string, + ); + fn new_msg_channel_request_shell(SSH_MSG_CHANNEL_REQUEST; + recipient_channel: u32, + kind_shell: string, + want_reply: bool, + ); fn new_msg_channel_request_exit_status(SSH_MSG_CHANNEL_REQUEST; recipient_channel: u32, kind_exit_status: string, false_: bool, exit_status: u32); fn new_msg_channel_success(SSH_MSG_CHANNEL_SUCCESS; recipient_channel: u32);