diff --git a/Cargo.lock b/Cargo.lock index 3816320..26ceb9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lazy_static" version = "1.5.0" @@ -567,6 +573,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -599,6 +611,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -796,6 +820,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -806,12 +840,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f516ba..c2c805c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ ssh-protocol = { path = "./ssh-protocol" } tokio = { version = "1.39.2", features = ["full"] } tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } diff --git a/src/main.rs b/src/main.rs index 6abf7f4..47e6c0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use tokio::{ use tracing::{debug, error, info, info_span, Instrument}; use ssh_protocol::{ - connection::{ChannelOpen, ChannelOperationKind, ChannelRequestKind}, + connection::{ChannelOpen, ChannelOperationKind, ChannelRequest}, transport::{self, ThreadRngRand}, ChannelUpdateKind, ServerConnection, SshStatus, }; @@ -16,11 +16,16 @@ use tracing_subscriber::EnvFilter; #[tokio::main] async fn main() -> eyre::Result<()> { - tracing_subscriber::fmt() - .with_env_filter( - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), - ) - .init(); + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + + if std::env::var("FAKESSH_JSON_LOGS").is_ok_and(|v| v != "0") { + tracing_subscriber::fmt() + .json() + .with_env_filter(env_filter) + .init(); + } else { + tracing_subscriber::fmt().with_env_filter(env_filter).init(); + } let addr = std::env::var("FAKESSH_LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:2222".to_owned()); @@ -43,7 +48,7 @@ async fn main() -> eyre::Result<()> { error!(?err, "error handling connection"); } - info!(data = ?String::from_utf8_lossy(&total_sent_data), "Finished connection"); + info!(stdin = ?String::from_utf8_lossy(&total_sent_data), "Finished connection"); } .instrument(span), ); @@ -108,23 +113,47 @@ async fn handle_connection( } }, ChannelUpdateKind::Request(req) => { - match req.kind { - ChannelRequestKind::PtyReq { .. } => {} - ChannelRequestKind::Shell => {} - ChannelRequestKind::Exec { command } => { - if command == b"uname -s -v -n -r -m" { - state.do_operation(update.number.construct_op(ChannelOperationKind::Data( - b"Linux nixos 6.6.35 #1-NixOS SMP PREEMPT_DYNAMIC Fri Jun 21 12:38:50 UTC 2024 x86_64\r\n".to_vec() - ))); + let success = update.number.construct_op(ChannelOperationKind::Success); + match req { + ChannelRequest::PtyReq { want_reply, .. } => { + if want_reply { + state.do_operation(success); } } - ChannelRequestKind::Env { .. } => {} + ChannelRequest::Shell { want_reply } => { + if want_reply { + state.do_operation(success); + } + } + ChannelRequest::Exec { + want_reply, + command, + } => { + if want_reply { + state.do_operation(success); + } + + let result = execute_command(&command); + state.do_operation( + update + .number + .construct_op(ChannelOperationKind::Data(result.stdout)), + ); + state.do_operation(update.number.construct_op( + ChannelOperationKind::Request(ChannelRequest::ExitStatus { + status: result.status, + }), + )); + state.do_operation( + update.number.construct_op(ChannelOperationKind::Eof), + ); + state.do_operation( + update.number.construct_op(ChannelOperationKind::Close), + ); + } + ChannelRequest::ExitStatus { .. } => {} + ChannelRequest::Env { .. } => {} }; - if req.want_reply { - state.do_operation( - update.number.construct_op(ChannelOperationKind::Success), - ); - } } ChannelUpdateKind::Data { data } => { let is_eof = data.contains(&0x03 /*EOF, Ctrl-C*/); @@ -158,3 +187,46 @@ async fn handle_connection( } } } + +struct ProcessOutput { + status: u32, + stdout: Vec, +} + +const UNAME_SVNRM: &[u8] = + b"Linux ubuntu 5.15.0-105-generic #115-Ubuntu SMP Mon Apr 15 09:52:04 UTC 2024 x86_64\r\n"; +const UNAME_A: &[u8] = + b"Linux ubuntu 5.15.0-105-generic #115-Ubuntu SMP Mon Apr 15 09:52:04 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux\r\n"; +const CPUINFO_UNAME_A: &[u8] = b" 4 AMD EPYC 7282 16-Core Processor\r\n\ +Linux vps2 5.15.0-105-generic #115-Ubuntu SMP Mon Apr 15 09:52:04 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux\r\n"; + +fn execute_command(command: &[u8]) -> ProcessOutput { + let Ok(command) = std::str::from_utf8(command) else { + return ProcessOutput { + status: 1, + stdout: b"what the hell".to_vec(), + }; + }; + match command { + "uname -s -v -n -r -m" => ProcessOutput { + status: 0, + stdout: UNAME_SVNRM.to_vec(), + }, + "uname -a" => ProcessOutput { + status: 0, + stdout: UNAME_A.to_vec(), + }, + "cat /proc/cpuinfo|grep name|cut -f2 -d':'|uniq -c ; uname -a" => ProcessOutput { + status: 0, + stdout: CPUINFO_UNAME_A.to_vec(), + }, + _ => { + let argv0 = command.split_ascii_whitespace().next().unwrap_or(""); + + ProcessOutput { + status: 127, + stdout: format!("bash: line 1: {argv0}: command not found\r\n").into_bytes(), + } + } + } +} diff --git a/ssh-connection/src/lib.rs b/ssh-connection/src/lib.rs index 5688b97..aa14ed7 100644 --- a/ssh-connection/src/lib.rs +++ b/ssh-connection/src/lib.rs @@ -43,13 +43,10 @@ pub enum ChannelOpen { Session, } -pub struct ChannelRequest { - pub want_reply: bool, - pub kind: ChannelRequestKind, -} - -pub enum ChannelRequestKind { +pub enum ChannelRequest { PtyReq { + want_reply: bool, + term: String, width_chars: u32, height_rows: u32, @@ -57,14 +54,23 @@ pub enum ChannelRequestKind { height_px: u32, term_modes: Vec, }, - Shell, + Shell { + want_reply: bool, + }, Exec { + want_reply: bool, + command: Vec, }, Env { + want_reply: bool, + name: String, value: Vec, }, + ExitStatus { + status: u32, + }, } impl ChannelNumber { @@ -85,6 +91,8 @@ pub enum ChannelOperationKind { Success, Failure, Data(Vec), + Request(ChannelRequest), + Eof, Close, } @@ -216,7 +224,7 @@ impl ServerChannelsState { let channel = self.channel(our_channel)?; let peer_channel = channel.peer_channel; - let channel_request_kind = match request_type { + let channel_request = match request_type { "pty-req" => { let term = packet.utf8_string()?; let width_chars = packet.u32()?; @@ -233,7 +241,8 @@ impl ServerChannelsState { "Trying to open a terminal" ); - ChannelRequestKind::PtyReq { + ChannelRequest::PtyReq { + want_reply, term: term.to_owned(), width_chars, height_rows, @@ -244,12 +253,13 @@ impl ServerChannelsState { } "shell" => { info!(?our_channel, "Opening shell"); - ChannelRequestKind::Shell + ChannelRequest::Shell { want_reply } } "exec" => { let command = packet.string()?; info!(?our_channel, command = ?String::from_utf8_lossy(command), "Executing command"); - ChannelRequestKind::Exec { + ChannelRequest::Exec { + want_reply, command: command.to_owned(), } } @@ -259,7 +269,8 @@ impl ServerChannelsState { info!(?our_channel, ?name, value = ?String::from_utf8_lossy(value), "Setting environment variable"); - ChannelRequestKind::Env { + ChannelRequest::Env { + want_reply, name: name.to_owned(), value: value.to_owned(), } @@ -278,10 +289,7 @@ impl ServerChannelsState { self.channel_updates.push_back(ChannelUpdate { number: our_channel, - kind: ChannelUpdateKind::Request(ChannelRequest { - want_reply, - kind: channel_request_kind, - }), + kind: ChannelUpdateKind::Request(channel_request), }) } _ => { @@ -312,6 +320,27 @@ impl ServerChannelsState { self.packets_to_send .push_back(Packet::new_msg_channel_data(peer, &data)); } + ChannelOperationKind::Request(req) => { + let packet = match req { + ChannelRequest::PtyReq { .. } => todo!("pty-req"), + ChannelRequest::Shell { .. } => todo!("shell"), + ChannelRequest::Exec { .. } => todo!("exec"), + ChannelRequest::Env { .. } => todo!("env"), + ChannelRequest::ExitStatus { status } => { + Packet::new_msg_channel_request_exit_status( + peer, + b"exit-status", + false, + status, + ) + } + }; + self.packets_to_send.push_back(packet); + } + ChannelOperationKind::Eof => { + self.packets_to_send + .push_back(Packet::new_msg_channel_eof(peer)); + } ChannelOperationKind::Close => { // self.packets_to_send diff --git a/ssh-transport/src/lib.rs b/ssh-transport/src/lib.rs index f09cd9d..eddf12a 100644 --- a/ssh-transport/src/lib.rs +++ b/ssh-transport/src/lib.rs @@ -162,16 +162,17 @@ impl ServerConnection { } => { let kex = KeyExchangeInitPacket::parse(&packet.payload)?; - let require_algorithm = - |expected: &'static str, list: NameList<'_>| -> Result<&'static str> { - if list.iter().any(|alg| alg == expected) { - Ok(expected) - } else { - Err(client_error!( + let require_algorithm = |expected: &'static str, + list: NameList<'_>| + -> Result<&'static str> { + if list.iter().any(|alg| alg == expected) { + Ok(expected) + } else { + Err(client_error!( "client does not supported algorithm {expected}. supported: {list:?}", )) - } - }; + } + }; let key_algorithm = require_algorithm("curve25519-sha256", kex.kex_algorithms)?; let server_host_key_algorithm = diff --git a/ssh-transport/src/packet/ctors.rs b/ssh-transport/src/packet/ctors.rs index b443037..ed94dd9 100644 --- a/ssh-transport/src/packet/ctors.rs +++ b/ssh-transport/src/packet/ctors.rs @@ -88,6 +88,9 @@ 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_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); fn new_msg_channel_failure(SSH_MSG_CHANNEL_FAILURE; recipient_channel: u32); }